daml/docs/source/getting-started/app-architecture.rst
Rohan Jacob-Rao 0f5d93e0c3
Include create-daml-app as a template project for daml new (#5259)
* Initial commit with create-daml-app master

* Include create-daml-app in build rule

* Make daml.yaml a template in version and project name

* Remove git attributes

* Remove license and azure config

* Remove scripts

* Don't overwrite config files in build rule

* Template version numbers in package.json, to be replaced by the assistant

* Rename to package.json.template

changelog_begin
changelog_end

* Add copyright headers

* Do template substitutions in all .template files

And don't special case daml new create-daml-app (so it treats it as a
regular template).

* Add create-daml-app to integration tests

* Remove WIP warning

* Move towards setup that works on head

* Make local copies of the TS libs in the templates tarball

* Hardcode project name for now

* Use isExtensionOf

* Remove service worker

* remove robots.txt (don't even know what it is)

* Revert "Make local copies of the TS libs in the templates tarball"

This reverts commit 1289581fb4a82af3ab534baf978a2c6ed895d538.

* Retemplatize TS lib versions. For head builds these will be installed using npm

* Remove daml/ledger from resolutions for daml-ts

* Comment about test secret

* Remove special create-daml-app assistant command and test that won't work anymore

* Remove redundant imports and export

* Remove old create-daml-app tests

* Remove yarn.lock

* Clean up integration test (just daml new and build atm)

* Add daml/ledger as a resolution for daml-ts

* Remove top level package.json

* Update daml.js version

* Use new import scheme for generated TS

* Update readme with new codegen and build steps

* Use start-navigator in daml.yaml

* Increase a couple of timeouts in tests (either sandbox or TS lib is a bit slower?)

* Update GSG intro with new build steps

* Remove daml2ts -p flag and --start-navigator flag from GSG instructions

* Don't use start-navigator flag in ui tests

* Temporary readme describing how to manually test the create-daml-app template

* Update code samples in app arch section of GSG

* Update code samples in testing doc

* Remove copied create-daml-app code

* Indent docs markers to be more subtle

* Update visible code in Messages (after) section

This needs to be kept up to date properly somehow.

* Update text to useLedger

* Restore code/ui-before copies until the Bazel magic is figured out

We need to make the template code a dependency in the Bazel rule as
otherwise we can't find the files in the docs build.

* Update create-daml-app/readme and make templates/readme more detailed

* Use jsx comments for docs markers so they don't show up in the app
2020-04-02 00:30:07 +00:00

170 lines
9.4 KiB
ReStructuredText

.. Copyright (c) 2020 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
.. SPDX-License-Identifier: Apache-2.0
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.
Overall, the social networking app is following the :ref:`recommended architecture of a fullstack DAML application <recommended-architecture>`. Below you can see a simplified version of the architecture represented in the app.
.. image:: ./images/gsg_architecture.svg
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.
.. literalinclude:: code/daml/User.daml
:language: daml
:start-after: -- MAIN_TEMPLATE_BEGIN
:end-before: -- MAIN_TEMPLATE_END
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 the list of users they are following.
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 users that a particular user is following 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 Bob is following Alice (only if Alice is the ``following`` list in his user contract).
For this to be true, Bob must have previously started to follow Alice, 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 follow users, called a *choice* in DAML.
.. literalinclude:: code/daml/User.daml
:language: daml
:start-after: -- FOLLOW_BEGIN
:end-before: -- FOLLOW_END
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 ``Follow`` choice does: after checking some preconditions, it archives the current user contract and creates a new one with the new user to follow added to the list. Here is a quick explanation of the code:
- The choice starts with the ``nonconsuming choice`` keyword followed by the choice name ``Follow``.
- The return type of a choice is defined next. In this case it is ``ContractId User``.
- After that we declare choice paramteres with ``with`` keyword. Here this is the user we want to start following.
- The keyword ``controller`` defines the ``Party`` that is allowed to execute the choice. In this case, it is the ``username`` party associated with the ``User`` contract.
- The ``do`` keyword marks the start of the choice body where its functionality will be written.
- After passing some checks, the current contract is archived with ``archive self``.
- A new ``User`` contract with the new user we have started following is created (the new user is added to the ``following`` list).
This information should be enough for understanding how choices work in this guide. More detailed information on choices can be found in :doc:`our docs </daml/reference/choices>`).
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 <https://www.typescriptlang.org/>`_.
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 (a ``.dar`` file).
The ``daml codegen ts`` command then takes this file as argument to produce a number of TypeScript packages in the output folder.
::
daml build
daml codegen ts .daml/dist/create-daml-app-0.1.0.dar -o daml-ts
Now we have a TypeScript interface (types and companion objects) to our DAML model, which we'll use in our UI code next.
The UI
======
On top of TypeScript, we use the UI framework `React <https://reactjs.org/>`_.
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.
.. literalinclude:: code/ui-before/App.tsx
:language: tsx
:start-after: // APP_BEGIN
:end-before: // APP_END
An important tool in the design of our components is a React feature called `Hooks <https://reactjs.org/docs/hooks-intro.html>`_.
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 <daml-react/index.html>`_ to query the ledger for contracts, create new contracts, and exercise choices. This is the library you will be using the most when interacting with the ledger [#f1]_ .
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 <https://reactjs.org/docs/context.html>`_ 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.
.. literalinclude:: code/ui-before/MainView.tsx
:language: tsx
:start-after: // USERS_BEGIN
:end-before: // USERS_END
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.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 follow the current user have their contracts revealed.
This behaviour is due to the observers on the ``User`` contract being exactly in the list of users that the current user is following.
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 ``Follow`` choice of the ``User`` template.
.. literalinclude:: code/ui-before/MainView.tsx
:language: tsx
:start-after: // FOLLOW_BEGIN
:end-before: // FOLLOW_END
The ``useLedger`` hook returns an object with methods for exercising choices.
The core of the ``follow`` function here is the call to ``ledger.exerciseByKey``.
The *key* in this case is the username of the current user, used to look up the corresponding ``User`` contract.
The wrapper function ``follow`` is then passed to the subcomponents of ``MainView``.
For example, ``follow`` is passed to the ``UserList`` component as an argument (a `prop <https://reactjs.org/docs/components-and-props.html>`_ in React terms).
This gets triggered when you click the icon next to a user's name in the *Network* panel.
.. literalinclude:: code/ui-before/MainView.tsx
:language: tsx
:start-after: // USERLIST_BEGIN
:end-before: // USERLIST_END
This should give you a taste of how the UI works alongside a DAML ledger.
You'll see this more as you develop :doc:`your first feature <first-feature>` for our social network.
.. rubric:: Footnotes
.. [#f1] FYI Behind the scenes the DAML React hooks library uses the `DAML Ledger React library <daml-ledger/index.html>`_ to communicate with a ledger implementation via :doc:`HTTP JSON API </json-api/index>`.