App Architecture¶
In this section we’ll look at the different components of our social network app. The goal is to familiarise you enough to feel comfortable extending the code with a new feature in the next section.
There are two main components in the code - the DAML model and the React/TypeScript frontend - with generated TypeScript code to bridge the two. Let’s start by looking at the DAML model, as this sets the core logic of the application.
The DAML Model¶
Using the Visual Studio Code editor, navigate to the daml
subdirectory.
There is a single DAML file called User.daml
with the model for app users.
The core data is at the start of the User
contract template.
template User with
username: Party
friends: [Party]
where
signatory username
observer friends
There are two important aspects here:
1. The data definition (a schema in database terms), describing the data stored with each user contract.
In this case it is an identifier for the user and their current list of friends.
Both fields use the built-in Party
type which lets us use them in the following clauses.
2. The signatories and observers of the contract. The signatories are the parties whose authorization is required to create or archive instances of the contract template, in this case the user herself. The observers are the parties who are able to view the contract on the ledger. In this case all friends of a user are able to see the user contract.
Let’s say what the signatory
and observer
clauses mean in our app more concretely.
A user Alice can see another user Bob in the network only when Alice is a friend in Bob’s user contract.
For this to be true, Bob must have previously added Alice as a friend, as he is the sole signatory on his user contract.
If not, Bob will be invisible to Alice.
Here we see two concepts that are central to DAML: authorization and privacy. Authorization is about who can do what, and privacy is about who can see what. In DAML we must answer these questions upfront, as they fundamentally change the design of an application.
The last thing we’ll point out about the DAML model for now is the operation to add friends, called a choice in DAML.
choice AddFriend: ContractId User with
friend: Party
controller username
do
assertMsg "You cannot add yourself as a friend" (friend /= username)
assertMsg "You cannot add a friend twice" (friend `notElem` friends)
create this with friends = friend :: friends
DAML contracts are immutable (can not be changed in place), so the only way to “update” one is to archive it and create a new instance.
That is what the AddFriend
choice does: after checking some preconditions, it creates a new user contract with the new friend added to the list.
The choice
syntax automatically includes the archival of the current instance.
Next we’ll see how our DAML code is reflected and used on the UI side.
TypeScript Code Generation¶
The user interface for our app is written in TypeScript. TypeScript is a variant of Javascript that provides more support in development through its type system.
In order to build an application on top of DAML, we need a way to refer to our DAML templates and choices in TypeScript. We do this using a DAML to TypeScript code generation tool in the DAML SDK.
To run code generation, we first need to compile the DAML model to an archive format (with a .dar
extension).
Then the command daml codegen ts
takes this file as argument to produce a number of TypeScript files in the specified location.
daml build daml codegen ts .daml/dist/create-daml-app-0.1.0.dar -o daml-ts/src
We now have TypeScript types and companion objects in the daml-ts
workspace which we can use from our UI code.
We’ll see that next.
The UI¶
On top of TypeScript, we use the UI framework React. React helps us write modular UI components using a functional style - a component is rerendered whenever one of its inputs changes - with careful use of global state.
The latter is especially interesting as it’s how we handle ledger state in our application.
We use a state management feature of React called Hooks.
You can see the capabilities of the DAML React hooks in create-daml-app/ui/src/daml-react-hooks/hooks.ts
.
For example, we can query the ledger for all contracts visible to the logged-in user, create new contracts, and exercise choices.
Let’s see some examples of DAML React hooks.
const username = useParty();
const myUserResult = useFetchByKey(User, () => username, [username]);
const myUser = myUserResult.contract?.payload;
const allUsers = useQuery(User).contracts;
This is the start of the component which provides data from the current state of the ledger to the main screen of our app.
The declarations within MainView
all use DAML hooks to get information from the ledger.
For instance, allUsers
uses a query to get the User
contracts on the ledger.
However, the query respects the privacy guarantees of a DAML ledger: the contracts returned are only those visible to the currently logged in party.
This explains why you cannot see all users in the network on the main screen, only those who have added you as a friend (making you an observer of their User
contract).