Role Pattern usage based on examples: User Template with choices vs choices on the sub-templates?

consider the example from:

Here is the example with a test (I have removed the Alias template as it served no purpose in the example):

module MarketOriginal where

import DA.Date
import qualified Daml.Script as S
import qualified DA.List as L

-- MAIN_TEMPLATE_BEGIN
template User with
    username: Party
    following: [Party]
  where
    signatory username
    observer following
-- MAIN_TEMPLATE_END

    key username: Party
    maintainer key

    -- FOLLOW_BEGIN
    nonconsuming choice Follow: ContractId User with
        userToFollow: Party
      controller username
      do
        assertMsg "You cannot follow yourself" (userToFollow /= username)
        assertMsg "You cannot follow the same user twice" (notElem userToFollow following)
        archive self
        create this with following = userToFollow :: following
    -- FOLLOW_END

    nonconsuming choice NewSellOffer : ()
      with
        observers : [Party]
        title : Text
        description : Text
        price : Int
      controller username
        do
          now <- getTime
          create $ SellOffer {seller = username, date = toDateUTC now, ..}
          pure ()

    nonconsuming choice TakeSellOffer : ()
      with
        offer : ContractId SellOffer
      controller username
        do
          exercise offer DoTrade with tradePartner = username
          pure ()

    nonconsuming choice ConfirmPayment : ()
      with
        invoice : ContractId Invoice
      controller username
        do
          Invoice{..} <- fetch invoice
          assert $ owner == username
          create $ PaymentConfirmation
                      with
                        invoice = invoice
                        party = username
                        obligor = obligor
          pure ()

template SellOffer
  with
    observers : [Party]
    title : Text
    description : Text
    price : Int
    seller : Party
    date : Date
  where
    signatory seller
    observer observers

    nonconsuming choice DoTrade : ()
      with
        tradePartner : Party
      controller tradePartner
        do
          assert $ tradePartner `elem` observers
          archive self
          create $ Invoice {owner = seller, obligor = tradePartner, amount = price, description = title}
          pure ()

template Invoice
  with
    owner : Party
    obligor : Party
    amount : Int
    description : Text
  where
    signatory obligor
    observer owner

template PaymentConfirmation
  with
    invoice : ContractId Invoice
    party : Party
    obligor : Party
  where
    signatory party
    observer obligor

    nonconsuming choice ArchiveInvoice : ()
      controller obligor
      do
        archive invoice
        archive self


normalFlow = do
    [alice, bob] <- mapA S.allocateParty ["Alice", "Bob"]

    userAlice <- submit alice do
        S.createCmd User with
            username = alice
            following = []

    userBob <- submit bob do
        S.createCmd User with
            username = bob
            following = []

    submit alice do
        S.exerciseCmd userAlice NewSellOffer with
            observers = [bob]
            title = "My Title"
            description = "Some Description"
            price = 100
    
    sellOffers <- S.query @SellOffer bob
    assertMsg "Only one sell offer should exist" (length sellOffers == 1)

    let (soCid, soData) = L.head sellOffers

    submit bob do
        S.exerciseCmd userBob TakeSellOffer with
            offer = soCid

    invoices <- S.query @Invoice bob
    assertMsg "Only one invoice should exist" (length invoices == 1)

    let (invCid, invData) = L.head invoices

    submit alice do
        S.exerciseCmd userAlice ConfirmPayment with
            invoice = invCid

    payConf <- S.query @PaymentConfirmation bob
    assertMsg "Only one payment confirmation should exist" (length payConf == 1)

    return ()

This example shows the use of a “User” template that contains the TakeSellOffer and ConfirmPayment choices.

My question is around the purpose of centralizing choices/commands in the User Template:

  1. Is there a specific design decision around centralizing these choices into User?
  2. Why would you not place “TakeSellOffer” choice and place it into the SellOffer template?
  3. Why would you not place ConfirmPayment and place it into the Invoice template?
  4. Is this about UI optimizations? where the user contract is used as the central UI hub for the user?
  5. Is there benefits to centralizing under a “user” type of contract for transaction structures?
  6. Is there specific design reasons for not returning ContractId for NewSellOffer, TakeSellOffer, and ConfirmPayment choices?

Thanks!

Hi @StephenOTT !

I’ll try to answer each of your raised questions:

  1. The approach shown here tries to separate code from data. The main benefit of this approach over coupling the choices with the data is that upgrades of your Daml code will be much easier. I’ll get into that at the end.
  2. This approach works as well, however, upgrading your Daml code will become a lot more involved.
  3. Same as 2).
  4. The main reason is upgrading, but I could also imagine that the UI code gets a bit easier this way.
  5. I wouldn’t be aware of a better transaction structure this way.
  6. No, I don’t think so. You could just as well return them in the choices.

Imagine that you decided to have all your choices along with their respective data templates. Now if you wanted to add a new choice, you would have to archive all those active data templates, then recreated and potentially update them to offer this new choice.
If you went with the approach shown here, all your choices are in the User contract. To add a new choice, you only have to find the User role contract, archive it and recreate it with the new User template with the added choice. This is easier, because there are potentially a lot of SellOffer contracts around, while there is only one User contract for each user. Also, the data contained in the old and new User contract is most likely the same, so the transition will be a trivial archive followed by a create. After this, the user will have the new choice available.

2 Likes

@drsk thank you for the breakdown. Great response.

It is clear based on your description about the separation implications when it comes to “choices” vs data and the upgrade considerations.

Based on the docs for upgrading: Upgrading Daml Applications — Daml SDK 2.2.0 documentation

I see the examples use naming conversations of ...V1... and ...V2.... in the template names.

In “real-world” scenarios how does the version of the DAR (as per the daml.yaml) correspond with the template naming?

Assuming we have a Daml.yaml with version: 1.0.0 and we upgrade this to version: 1.1.0: How does this align with the changed templates? Assume SellOffer was upgraded, but SellOffer in v1.1.0 did not change the name of the template: how do you reference both versions of the templates (where they have the same name and same module name just different versions)?

ref Upgrading Daml Applications — Daml SDK 2.2.0 documentation

Thanks

Hi @StephenOTT,

These types of name collisions can be resolved as explained on this page of the documentation: Reference: Daml Packages — Daml SDK 2.2.0 documentation. The basic idea is you can rename modules when loading packages, through annotations in daml.yaml (or CLI options).

1 Like

@Gary_Verhaegen thank you. That’s what I was hoping to find!

Thanks @drsk for your detailed response.