I have been trying to model business data relationships with daml and have been running in data join / data relationship constraints for when you want to use the data for historical look ups:
Consider a scenario such as:
Organization
Business Units / BU (Many BUs per Org)
BU Owner (1 owner per BU) (with a Person)
Training Session (Given by the BU Owner and given to a Person)
An organization creates BUs and BU Owners. Owners provide training sessions to people. A Owner is a role and is associated with a person. A person has a profile of data (name, email, phone number, etc)
If you were to inspect the training session contract, it would be signed by the BU owner. The BU owner is signed by the BU and the BU owner is associated with a Person contract representing the person details that were in place at the time.
My modelling issue is making sense of (and explaining to others) the relationship structure and how to pass signatories so contracts can be updated in the future.
Example:
Person A has their Person contract updated.
The Training session was associated with Person A Contract. But that contract has been archived.
You want to query for Person A and what they have been trained for.
Example:
You gave a BU owner associated with a Person.
The BU Owner changes to a new person.
You want to see all Training sessions run by the BU
Is there complex business data daml examples available showing lifecycle usage and sensible reporting?
Is there some common rules/patterns to follow for business data modelling and modelling the real-world joins between that data?
I would use contract keys to model the relationships. If you think of a template as a table and contracts as rows in that table, the contract key is by analogy the primary key of that table. You can reference data using that key and get hold of it using the fetchByKey and lookupByKey operations on the ledger. So you can use them like foreign keys on other templates.
The major difference where the analogy breaks down is that the ledger doesn’t enforce referential integrity. You need to do that yourself by only offering choices that don’t break referential integrity.
You can also query for all contracts with a given foreign/primary key using the JSON API using the field level filters.
So for you specific use-case, I’d do something like
template Org
with
party : Party
where
signatory orgParty
key party : Party
maintainer kay
template Person
with
party : Party
-- Put other stuff here
where
signatory party
key party : Party
maintainer party
template BU
with
orgParty : Party
ownerParty : Party
name : Text
where
signatory orgParty
observer ownerParty
key (orgParty, name) : (Party, Text)
maintainer key._1
-- You can now get hold of the Org and Person using
-- fetchByKey @Org orgParty
-- fetchByKey @Person ownerParty
template TrainingSession
with
orgParty : Party
buName : Text
sessionName : Text
trainer : Party -- BU Owner at the time the training was given
trainee : Party -- Receiver of the training
where
signatory orgParty, trainer
observer trainee
key (orgPary, buName, sessionName) : (Party, Text, Text)
maintainer key._1
-- You can get hold of the Person, BU and Org contracts here using the keys
Now let’s look at your examples
does not change any keys
…
You are trying to query for data that has been archived. That’s only possible by streaming ledger history. If you want to do this cleanly keep an index on ledger that you update each time a training is given
template PersonBUTrainings
with
orgParty : Party
buName : Text
personParty : Party
received : [(Party, Text, Text)] -- list of keys of `TrainingSession` contracts
where
signatory orgParty
key (orgParty, buName, personParty) : (Party, Text, Party)
maintainer key._1
Now to get all the person’s trainings, query for all PersonBUTrainings with matching orgParty and personParty.
OK
None of the keys change.
Query the same PersonBUTrainings index above, but filtering for orgParty and buName instead of orgParty and personParty
@bernhard imagine that you created Typed data class based keys for each of your templates.
Would you recommend (or not) using those data/records in the joined templates? Example the PersonBUTrainings template has the byname, but the purpose of having buName is really to be able to look up by BU, which is combination of the Name and org. It duplicates some of the data but you do not need to carry knowledge about how to build a key.