Role Pattern usage based on examples: User Template with choices vs choices on the sub-templates?
consider the example from:
github.comdigital-asset/daml-katacoda/blob/61a468653dca6ebfc1b7bbc447d5231094311e26/triggers/2_an_online_market.md
Let's create a very simple model for a market in Daml. Click on the IDE tab and wait for it to
load, then open `daml/Market.daml`{{open}}. Copy paste the following Daml code:
<pre class="file"i data-filename="daml/Market.daml" data-target="append">
module Market where
import DA.Date
-- MAIN_TEMPLATE_BEGIN
template User with
username: Party
following: [Party]
where
signatory username
observer following
-- MAIN_TEMPLATE_END
key username: Party
maintainer key
This file has been truncated. show original
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:
- Is there a specific design decision around centralizing these choices into User?
- Why would you not place “TakeSellOffer” choice and place it into the SellOffer template?
- Why would you not place ConfirmPayment and place it into the Invoice template?
- Is this about UI optimizations? where the user contract is used as the central UI hub for the user?
- Is there benefits to centralizing under a “user” type of contract for transaction structures?
- 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:
- 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.
- This approach works as well, however, upgrading your Daml code will become a lot more involved.
- Same as 2).
- The main reason is upgrading, but I could also imagine that the UI code gets a bit easier this way.
- I wouldn’t be aware of a better transaction structure this way.
- 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.
@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).
@Gary_Verhaegen thank you. That’s what I was hoping to find!
Thanks @drsk for your detailed response.