Modelling Business Data relationships such as Roles, People, and Activities

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:

  1. Organization
  2. Business Units / BU (Many BUs per Org)
  3. BU Owner (1 owner per BU) (with a Person)
  4. 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:

  1. Person A has their Person contract updated.
  2. The Training session was associated with Person A Contract. But that contract has been archived.
  3. You want to query for Person A and what they have been trained for.

Example:

  1. You gave a BU owner associated with a Person.
  2. The BU Owner changes to a new person.
  3. 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?

Thanks!

1 Like

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

  1. does not change any keys
  2. …
  3. 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.

  1. OK
  2. None of the keys change.
  3. Query the same PersonBUTrainings index above, but filtering for orgParty and buName instead of orgParty and personParty

@bernhard thanks! So this introduces a new aspect: “Index Contracts” / Pre-Aggregating contracts instead of aggregation of contracts on query.

Is there other “types”/styles of these contracts that are common?

@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.