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: the DAML model and the React/TypeScript frontend. We generate TypeScript code to bridge the two. Let’s start by looking at the DAML model, which defines the core logic of the application.

The DAML Model

In your terminal, navigate to the root create-daml-app directory and run:

daml studio

This should open the Visual Studio Code editor at the root of the project. (You may get a new tab pop up with release notes for the latest SDK - just close this.) Using the file Explorer on the left sidebar, navigate to the daml folder and double-click on the User.daml file.

The DAML code defines the data and workflow of the application. Both are described in the User contract template. Let’s look at the data portion first.

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 part of the DAML model is the operation to add friends, called a choice in DAML.

    nonconsuming 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)
        archive self
        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 archives the current user contract and creates a new one with the extra friend added to the list.

There is some boilerplate to set up the choice (full details in the DAML reference):

  • We make contract archival explicit by marking the choice as nonconsuming and then calling archive self in the body (choices which aren’t nonconsuming archive or consume the contract implicitly).
  • The return type is ContractId User, a reference to the new contract for the calling code.
  • The new friend: Party is passed as an argument to the choice.
  • The controller, the party able to exercise the choice, is the one named on the User contract.

Let’s move on to how our DAML model 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 during 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.

Let’s see an example of a React component. All components are in the ui/src/components folder. You can navigate there within Visual Studio Code using the file explorer on the left sidebar. We’ll first look at App.tsx, which is the entry point to our application.

const App: React.FC = () => {
  const [credentials, setCredentials] = React.useState<Credentials | undefined>();

  return credentials
    ? <DamlLedger
        token={credentials.token}
        party={credentials.party}
        httpBaseUrl={httpBaseUrl}
        wsBaseUrl={wsBaseUrl}
      >
        <MainScreen onLogout={() => setCredentials(undefined)}/>
      </DamlLedger>
    : <LoginScreen onLogin={setCredentials} />
}

An important tool in the design of our components is a React feature called Hooks. Hooks allow you to share and update state across components, avoiding having to thread it through manually. We take advantage of hooks in particular to share ledger state across components. We use custom DAML React hooks to query the ledger for contracts, create new contracts, and exercise choices. This library uses the HTTP JSON API behind the scenes.

The useState hook (not specific to DAML) here keeps track of the user’s credentials. If they are not set, we render the LoginScreen with a callback to setCredentials. If they are set, then we render the MainScreen of the app. This is wrapped in the DamlLedger component, a React context with a handle to the ledger.

Let’s move on to more advanced uses of our DAML React library. The MainScreen is a simple frame around the MainView component, which houses the main functionality of our app. It uses DAML React hooks to query and update ledger state.

const MainView: React.FC = () => {
  const username = useParty();
  const myUserResult = useStreamFetchByKey(User, () => username, [username]);
  const myUser = myUserResult.contract?.payload;
  const allUsers = useStreamQuery(User).contracts;

The useParty hook simply returns the current user as stored in the DamlLedger context. A more interesting example is the allUsers line. This uses the useStreamQuery hook to get all User contracts on the ledger. (User here is an object generated by daml codegen ts - it stores metadata of the User template defined in User.daml.) Note however that this query preserves privacy: only users that have added the current user have their contracts revealed. This behaviour is due to the observers on the User contract being exactly the user’s friends.

A final point on this is the streaming aspect of the query. This means that results are updated as they come in - there is no need for periodic or manual reloading to see updates.

Another example, showing how to update ledger state, is how we exercise the AddFriend choice of the User template.

  const [exerciseAddFriend] = useExerciseByKey(User.AddFriend);

  const addFriend = async (friend: Party): Promise<boolean> => {
    try {
      await exerciseAddFriend(username, {friend});
      return true;
    } catch (error) {
      alert("Unknown error:\n" + JSON.stringify(error));
      return false;
    }
  }

The useExerciseByKey hook returns the exerciseAddFriend function. The key in this case is the username of the current user, used to look up the corresponding User contract. The wrapper function addFriend is then passed to the subcomponents of MainView. For example, addFriend is passed to the UserList component as an argument (a prop in React terms). This gets triggered when you click the icon next to a user’s name in the Network panel.

              <UserList
                users={friends}
                onAddFriend={addFriend}
              />

This should give you a taste of how the UI works alongside a DAML ledger. You’ll see this more as you develop your first feature for our social network.