First, I’m going to assume you need access to the member list for some workflow, otherwise you may as well store a reference to the Association
in the Member
contract and avoid the list update completely.
Most ledger applications shouldn’t need to store archives, so thrashing the association list shouldn’t normally be an issue. Still, if you are updating the list sufficiently often the resulting contention may become an issue you need to resolve. In that case there are a few ways you can ameliorate problem.
Hundreds of associations is unlikely to be an issue, as I assume their relationship to Member
contracts is 1:N
not M:N
. Only in the latter case would you start seeing contention on the ledger. A few thousand contracts in independent workflows is not really something you should be too worried about. If the Association
contract is particularly large, then you might consider splitting the contract in two, linked by the contract id of the primary Association
contract. This is called a contract constellation, where you have a single logical contract split into multiple concrete contracts unrelated to the underlying data model (ie. privacy, performance, storage scalability, etc).
daml 1.2
module BatchUpdateDaml
where
template Association
with
organiser : Party
bigdata : Text
where
signatory organiser
controller organiser can
postconsuming DoUpdate : ContractId Association
with
newdata : Text
do
(membersId, members) <- fetchByKey @AssociationMembers (organiser, self)
newAssociation <- create this with bigdata = newdata
exercise membersId UpdateAssociation with newAssociation
pure newAssociation
template AssociationMembers
with
organiser : Party
association : ContractId Association
members : [Text]
where
signatory organiser
key (organiser, association) : (Party, ContractId Association)
maintainer key._1
controller organiser can
UpdateAssociation : ContractId AssociationMembers
with
newAssociation : ContractId Association
do
create this with association = newAssociation
UpdateAssociationMembers : ContractId AssociationMembers
with
newMember : Text
do
fetchByKey @Member (organiser, newMember)
create this with members = newMember :: members
template Member
with
organiser : Party
name : Text
where
signatory organiser
key (organiser, member) : (Party, Text)
maintainer organiser
This becomes slightly more complicated if the membership list changes often enough that you experience contention over the list updates. In this case you should consider reifying the membership updates and batching them.
daml 1.2
module BatchUpdateDemo
where
import DA.Optional
template Association
with
organiser : Party
bigdata : Text
where
signatory organiser
controller organiser can
postconsuming DoUpdate : ContractId Association
with
newdata : Text
do
(membersId, members) <- fetchByKey @AssociationMembers (organiser, self)
newAssociation <- create this with bigdata = newdata
exercise membersId UpdateAssociation with newAssociation
pure newAssociation
template AddMemberRequest
with
organiser : Party
association : ContractId Association
member : Text
where
signatory organiser
controller organiser can
Validate : Optional (ContractId Member)
with
org : Party
ass : ContractId Association
do
assert $ org == organiser
assert $ ass == association
lookupByKey @Member (organiser, member)
template AssociationMembers
with
organiser : Party
association : ContractId Association
members : [Text]
where
signatory organiser
key (organiser, association) : (Party, ContractId Association)
maintainer key._1
controller organiser can
UpdateAssociation : ContractId AssociationMembers
with
newAssociation : ContractId Association
do
create this with association = newAssociation
UpdateAssociationMembers : ContractId AssociationMembers
with
newMembers : [ContractId AddMemberRequest]
do
memberCids <- catOptionals <$> mapA (\amr -> exercise amr Validate with org = organiser; ass = association) newMembers
newMembers <- fmap (.name) <$> mapA fetch memberCids
create this with members = newMembers <> members
template Member
with
organiser : Party
name : Text
where
signatory organiser
key (organiser, name) : (Party, Text)
maintainer key._1
(Note as written the above has an easily corrected race-condition between updating the association list and updating the association data that can result in requests being stranded. Fixing it would have distracted from the batching itself).
Batching does require adding a DAML Trigger to pass all visible requests into UpdateAssociationMembers
. It also means adding a member is no longer an atomic operation, and adds a full round-trip latency to the operation. So this is something you should do only once you know you have a contention problem you need to solve. Still this approach should scale comfortably up to a few hundred contending updates per second — of course if that is a sustained rather than burst rate, at that level your other workflows will probably be experiencing starvation, and handling that will require application specific engineering.