github.com/true-sqn/fabric@v2.1.1+incompatible/docs/source/write_first_app.rst (about)

     1  Writing Your First Application
     2  ==============================
     3  
     4  .. note:: If you're not yet familiar with the fundamental architecture of a
     5            Fabric network, you may want to visit the :doc:`key_concepts` section
     6            prior to continuing.
     7  
     8            It is also worth noting that this tutorial serves as an introduction
     9            to Fabric applications and uses simple smart contracts and
    10            applications. For a more in-depth look at Fabric applications and
    11            smart contracts, check out our
    12            :doc:`developapps/developing_applications` section or the
    13            :doc:`tutorial/commercial_paper`.
    14  
    15  This tutorial provides an introduction to how Fabric applications interact
    16  with deployed blockchain networks. The tutorial uses sample programs built using the
    17  Fabric SDKs -- described in detail in the :doc:`/developapps/application` topic --
    18  to invoke a smart contract which queries and updates the ledger with the smart
    19  contract API -- described in detail in :doc:`/developapps/smartcontract`.
    20  We will also use our sample programs and a deployed Certificate Authority to generate
    21  the X.509 certificates that an application needs to interact with a permissioned
    22  blockchain. The sample applications and the smart contract they invoke are
    23  collectively known as FabCar.
    24  
    25  We’ll go through three principle steps:
    26  
    27    **1. Setting up a development environment.** Our application needs a network
    28    to interact with, so we'll deploy a basic network for our smart contracts and
    29    application.
    30  
    31    .. image:: images/AppConceptsOverview.png
    32  
    33    **2. Explore a sample smart contract.**
    34    We’ll inspect the sample Fabcar smart contract to learn about the transactions within them,
    35    and how they are used by applications to query and update the ledger.
    36  
    37    **3. Interact with the smart contract with a sample application.** Our application will
    38    use the FabCar smart contract to query and update car assets on the ledger.
    39    We'll get into the code of the apps and the transactions they create,
    40    including querying a car, querying a range of cars, and creating a new car.
    41  
    42  After completing this tutorial you should have a basic understanding of how Fabric
    43  applications and smart contracts work together to manage data on the distributed
    44  ledger of a blockchain network.
    45  
    46  Before you begin
    47  ----------------
    48  
    49  In addition to the standard :doc:`prereqs` for Fabric, this tutorial leverages the Hyperledger Fabric SDK for Node.js. See the Node.js SDK `README <https://github.com/hyperledger/fabric-sdk-node#build-and-test>`__ for a up to date list of prerequisites.
    50  
    51  - If you are using macOS, complete the following steps:
    52  
    53    1. Install `Homebrew <https://brew.sh/>`_.
    54    2. Check the Node SDK `prerequisties <https://github.com/hyperledger/fabric-sdk-node#build-and-test>`_ to find out what level of Node to install.
    55    3. Run ``brew install node`` to download the latest version of node or choose a specific version, for example: ``brew install node@10`` according to what is supported in the prerequisites.
    56    4. Run ``npm install``.
    57  
    58  - If you are on Windows,  you can install the `windows-build-tools <https://github.com/felixrieseberg/windows-build-tools#readme>`_ with npm which installs all required compilers and tooling by running the following command:
    59  
    60    .. code:: bash
    61  
    62      npm install --global windows-build-tools
    63  
    64  - If you are on Linux, you need to install `Python v2.7 <https://www.python.org/download/releases/2.7/>`_, `make <https://www.gnu.org/software/make/>`_, and a C/C++ compiler toolchain such as `GCC <https://gcc.gnu.org/>`_. You can run the following command to install the other tools:
    65  
    66    .. code:: bash
    67  
    68      sudo apt install build-essentials
    69  
    70  Set up the blockchain network
    71  -----------------------------
    72  
    73  If you've already run through :doc:`test_network` tutorial and have a network up
    74  and running, this tutorial will bring down your running network before
    75  bringing up a new one.
    76  
    77  
    78  Launch the network
    79  ^^^^^^^^^^^^^^^^^^
    80  
    81  .. note:: This tutorial demonstrates the JavaScript versions of the FabCar
    82            smart contract and application, but the ``fabric-samples`` repo also
    83            contains Go, Java and TypeScript versions of this sample. To try the
    84            Go, Java or TypeScript versions, change the ``javascript`` argument
    85            for ``./startFabric.sh`` below to either ``go``, ``java`` or ``typescript``
    86            and follow the instructions written to the terminal.
    87  
    88  Navigate to the ``fabcar`` subdirectory within your local clone of the
    89  ``fabric-samples`` repo.
    90  
    91  .. code:: bash
    92  
    93    cd fabric-samples/fabcar
    94  
    95  Launch your network using the ``startFabric.sh`` shell script.
    96  
    97  .. code:: bash
    98  
    99    ./startFabric.sh javascript
   100  
   101  This command will deploy the Fabric test network with two peers and an ordering
   102  service. Instead of using the cryptogen tool, we will bring up the test network
   103  using Certificate Authorities. We will use one of these CAs to create the certificates
   104  and keys that will be used by our applications in a future step. The ``startFabric.sh``
   105  script will also deploy and initialize the JavaScript version of the FabCar smart
   106  contract on the channel ``mychannel``, and then invoke the smart contract to
   107  put initial data on the ledger.
   108  
   109  Install the application
   110  ^^^^^^^^^^^^^^^^^^^^^^^
   111  
   112  From the ``fabcar`` directory inside ``fabric-samples``, navigate to the
   113  ``javascript`` folder.
   114  
   115  .. code:: bash
   116  
   117    cd javascript
   118  
   119  This directory contains sample programs that were developed using the Fabric
   120  SDK for Node.js. Run the following command to install the application dependencies.
   121  It will take about a minute to complete:
   122  
   123  .. code:: bash
   124  
   125    npm install
   126  
   127  This process is installing the key application dependencies defined in
   128  ``package.json``. The most important of which is the ``fabric-network`` class;
   129  it enables an application to use identities, wallets, and gateways to connect to
   130  channels, submit transactions, and wait for notifications. This tutorial also
   131  uses the ``fabric-ca-client`` class to enroll users with their respective
   132  certificate authorities, generating a valid identity which is then used by
   133  ``fabric-network`` class methods.
   134  
   135  Once ``npm install`` completes, everything is in place to run the application.
   136  Let's take a look at the sample JavaScript application files we will be using
   137  in this tutorial:
   138  
   139  .. code:: bash
   140  
   141    ls
   142  
   143  You should see the following:
   144  
   145  .. code:: bash
   146  
   147    enrollAdmin.js  node_modules       package.json  registerUser.js
   148    invoke.js       package-lock.json  query.js      wallet
   149  
   150  There are files for other program languages, for example in the
   151  ``fabcar/java`` directory. You can read these once you've used the
   152  JavaScript example -- the principles are the same.
   153  
   154  Enrolling the admin user
   155  ------------------------
   156  
   157  .. note:: The following two sections involve communication with the Certificate
   158            Authority. You may find it useful to stream the CA logs when running
   159            the upcoming programs by opening a new terminal shell and running
   160            ``docker logs -f ca_org1``.
   161  
   162  When we created the network, an admin user --- literally called ``admin`` ---
   163  was created as the **registrar** for the certificate authority (CA). Our first
   164  step is to generate the private key, public key, and X.509 certificate for
   165  ``admin`` using the ``enroll.js`` program. This process uses a **Certificate
   166  Signing Request** (CSR) --- the private and public key are first generated
   167  locally and the public key is then sent to the CA which returns an encoded
   168  certificate for use by the application. These credentials are then stored
   169  in the wallet, allowing us to act as an administrator for the CA.
   170  
   171  Let's enroll user ``admin``:
   172  
   173  .. code:: bash
   174  
   175    node enrollAdmin.js
   176  
   177  This command stores the CA administrator's credentials in the ``wallet`` directory.
   178  You can find administrator's certificate and private key in the ``wallet/admin.id``
   179  file.
   180  
   181  Register and enroll an application user
   182  ---------------------------------------
   183  
   184  Our ``admin`` is used to work with the CA. Now that we have the administrator's
   185  credentials in a wallet, we can create a new application user which will be used
   186  to interact with the blockchain. Run the following command to register and enroll
   187  a new user named ``appUser``:
   188  
   189  .. code:: bash
   190  
   191    node registerUser.js
   192  
   193  Similar to the admin enrollment, this program uses a CSR to enroll ``appUser`` and
   194  store its credentials alongside those of ``admin`` in the wallet. We now have
   195  identities for two separate users --- ``admin`` and ``appUser`` --- that can be
   196  used by our application.
   197  
   198  Querying the ledger
   199  -------------------
   200  
   201  Each peer in a blockchain network hosts a copy of the `ledger <./ledger/ledger.html>`_. An application
   202  program can view the most recent data from the ledger using read only invocations of
   203  a smart contract running on your peers called a query.
   204  
   205  Here is a simplified representation of how a query works:
   206  
   207  .. image:: tutorial/write_first_app.diagram.1.png
   208  
   209  The most common queries involve the current values of data in the ledger -- its
   210  `world state <./ledger/ledger.html#world-state>`_. The world state is
   211  represented as a set of key-value pairs, and applications can query data for a
   212  single key or multiple keys. Moreover, you can use complex queries to read the
   213  data on the ledger when you use CouchDB as your state database and model your data in JSON.
   214  This can be very helpful when looking for all assets that match certain keywords
   215  with particular values; all cars with a particular owner, for example.
   216  
   217  First, let's run our ``query.js`` program to return a listing of all the cars on
   218  the ledger. This program uses our second identity -- ``appUser`` -- to access the
   219  ledger:
   220  
   221  .. code:: bash
   222  
   223    node query.js
   224  
   225  The output should look like this:
   226  
   227  .. code:: json
   228  
   229    Wallet path: ...fabric-samples/fabcar/javascript/wallet
   230    Transaction has been evaluated, result is:
   231    [{"Key":"CAR0","Record":{"color":"blue","docType":"car","make":"Toyota","model":"Prius","owner":"Tomoko"}},
   232    {"Key":"CAR1","Record":{"color":"red","docType":"car","make":"Ford","model":"Mustang","owner":"Brad"}},
   233    {"Key":"CAR2","Record":{"color":"green","docType":"car","make":"Hyundai","model":"Tucson","owner":"Jin Soo"}},
   234    {"Key":"CAR3","Record":{"color":"yellow","docType":"car","make":"Volkswagen","model":"Passat","owner":"Max"}},
   235    {"Key":"CAR4","Record":{"color":"black","docType":"car","make":"Tesla","model":"S","owner":"Adriana"}},
   236    {"Key":"CAR5","Record":{"color":"purple","docType":"car","make":"Peugeot","model":"205","owner":"Michel"}},
   237    {"Key":"CAR6","Record":{"color":"white","docType":"car","make":"Chery","model":"S22L","owner":"Aarav"}},
   238    {"Key":"CAR7","Record":{"color":"violet","docType":"car","make":"Fiat","model":"Punto","owner":"Pari"}},
   239    {"Key":"CAR8","Record":{"color":"indigo","docType":"car","make":"Tata","model":"Nano","owner":"Valeria"}},
   240    {"Key":"CAR9","Record":{"color":"brown","docType":"car","make":"Holden","model":"Barina","owner":"Shotaro"}}]
   241  
   242  Let's take a closer look at how `query.js` program uses the APIs provided by the
   243  `Fabric Node SDK <https://hyperledger.github.io/fabric-sdk-node/>`__ to
   244  interact with our Fabric network. Use an editor (e.g. atom or visual studio) to
   245  open ``query.js``.
   246  
   247  The application starts by bringing in scope two key classes from the
   248  ``fabric-network`` module; ``Wallets`` and ``Gateway``. These classes
   249  will be used to locate the ``appUser`` identity in the wallet, and use it to
   250  connect to the network:
   251  
   252  .. code:: bash
   253  
   254    const { Gateway, Wallets } = require('fabric-network');
   255  
   256  First, the program uses the Wallet class to get our application user from our file system.
   257  
   258  .. code:: bash
   259  
   260    const identity = await wallet.get('appUser');
   261  
   262  Once the program has an identity, it uses the Gateway class to connect to our network.
   263  
   264  .. code:: bash
   265  
   266    const gateway = new Gateway();
   267    await gateway.connect(ccpPath, { wallet, identity: 'appUser', discovery: { enabled: true, asLocalhost: true } });
   268  
   269  ``ccpPath`` describes the path to the connection profile that our application will use
   270  to connect to our network. The connection profile was loaded from inside the
   271  ``fabric-samples/test network`` directory and parsed as a JSON file:
   272  
   273  .. code:: bash
   274  
   275    const ccpPath = path.resolve(__dirname, '..', '..', 'test-network','organizations','peerOrganizations','org1.example.com', 'connection-org1.json');
   276  
   277  If you'd like to understand more about the structure of a connection profile,
   278  and how it defines the network, check out
   279  `the connection profile topic <./developapps/connectionprofile.html>`_.
   280  
   281  A network can be divided into multiple channels, and the next important line of
   282  code connects the application to a particular channel within the network,
   283  ``mychannel``, where our smart contract was deployed:
   284  
   285  .. code:: bash
   286  
   287    const network = await gateway.getNetwork('mychannel');
   288  
   289  Within this channel, we can access the FabCar smart contract to interact
   290  with the ledger:
   291  
   292  .. code:: bash
   293  
   294    const contract = network.getContract('fabcar');
   295  
   296  Within FabCar there are many different **transactions**, and our application
   297  initially uses the ``queryAllCars`` transaction to access the ledger world state
   298  data:
   299  
   300  .. code:: bash
   301  
   302    const result = await contract.evaluateTransaction('queryAllCars');
   303  
   304  The ``evaluateTransaction`` method represents one of the simplest interactions
   305  with a smart contract in blockchain network. It simply picks a peer defined in
   306  the connection profile and sends the request to it, where it is evaluated. The
   307  smart contract queries all the cars on the peer's copy of the ledger and returns
   308  the result to the application. This interaction does not result in an update the
   309  ledger.
   310  
   311  The FabCar smart contract
   312  -------------------------
   313  
   314  Let's take a look at the transactions within the FabCar smart contract. Open a
   315  new terminal and navigate to the JavaScript version of the FabCar Smart contract
   316  inside the ``fabric-samples`` repository:
   317  
   318  .. code:: bash
   319  
   320    cd fabric-samples/chaincode/fabcar/javascript/lib
   321  
   322  Open the ``fabcar.js`` file in a text editor editor.
   323  
   324  See how our smart contract is defined using the ``Contract`` class:
   325  
   326  .. code:: bash
   327  
   328    class FabCar extends Contract {...
   329  
   330  Within this class structure, you'll see that we have the following
   331  transactions defined: ``initLedger``, ``queryCar``, ``queryAllCars``,
   332  ``createCar``, and ``changeCarOwner``. For example:
   333  
   334  
   335  .. code:: bash
   336  
   337    async queryCar(ctx, carNumber) {...}
   338    async queryAllCars(ctx) {...}
   339  
   340  Let's take a closer look at the ``queryAllCars`` transaction to see how it
   341  interacts with the ledger.
   342  
   343  .. code:: bash
   344  
   345    async queryAllCars(ctx) {
   346  
   347      const startKey = 'CAR0';
   348      const endKey = 'CAR999';
   349  
   350      const iterator = await ctx.stub.getStateByRange(startKey, endKey);
   351  
   352  
   353  This code defines the range of cars that ``queryAllCars`` will retrieve from the
   354  ledger. Every car between ``CAR0`` and ``CAR999`` -- 1,000 cars in all, assuming
   355  every key has been tagged properly -- will be returned by the query. The
   356  remainder of the code iterates through the query results and packages them into
   357  JSON for the application.
   358  
   359  Below is a representation of how an application would call different
   360  transactions in a smart contract. Each transaction uses a broad set of APIs such
   361  as ``getStateByRange`` to interact with the ledger. You can read more about
   362  these APIs in `detail
   363  <https://hyperledger.github.io/fabric-chaincode-node/>`_.
   364  
   365  .. image:: images/RunningtheSample.png
   366  
   367  We can see our ``queryAllCars`` transaction, and another called ``createCar``.
   368  We will use this later in the tutorial to update the ledger, and add a new block
   369  to the blockchain.
   370  
   371  But first, go back to the ``query`` program and change the
   372  ``evaluateTransaction`` request to query ``CAR4``. The ``query`` program should
   373  now look like this:
   374  
   375  .. code:: bash
   376  
   377    const result = await contract.evaluateTransaction('queryCar', 'CAR4');
   378  
   379  Save the program and navigate back to your ``fabcar/javascript`` directory.
   380  Now run the ``query`` program again:
   381  
   382  .. code:: bash
   383  
   384    node query.js
   385  
   386  You should see the following:
   387  
   388  .. code:: json
   389  
   390    Wallet path: ...fabric-samples/fabcar/javascript/wallet
   391    Transaction has been evaluated, result is:
   392    {"color":"black","docType":"car","make":"Tesla","model":"S","owner":"Adriana"}
   393  
   394  If you go back and look at the result from when the transaction was
   395  ``queryAllCars``, you can see that ``CAR4`` was Adriana’s black Tesla model S,
   396  which is the result that was returned here.
   397  
   398  We can use the ``queryCar`` transaction to query against any car, using its
   399  key (e.g. ``CAR0``) and get whatever make, model, color, and owner correspond to
   400  that car.
   401  
   402  Great. At this point you should be comfortable with the basic query transactions
   403  in the smart contract and the handful of parameters in the query program.
   404  
   405  Time to update the ledger...
   406  
   407  Updating the ledger
   408  -------------------
   409  
   410  Now that we’ve done a few ledger queries and added a bit of code, we’re ready to
   411  update the ledger. There are a lot of potential updates we could make, but
   412  let's start by creating a **new** car.
   413  
   414  From an application perspective, updating the ledger is simple. An application
   415  submits a transaction to the blockchain network, and when it has been
   416  validated and committed, the application receives a notification that
   417  the transaction has been successful. Under the covers this involves the process
   418  of **consensus** whereby the different components of the blockchain network work
   419  together to ensure that every proposed update to the ledger is valid and
   420  performed in an agreed and consistent order.
   421  
   422  .. image:: tutorial/write_first_app.diagram.2.png
   423  
   424  Above, you can see the major components that make this process work. As well as
   425  the multiple peers which each host a copy of the ledger, and optionally a copy
   426  of the smart contract, the network also contains an ordering service. The
   427  ordering service coordinates transactions for a network; it creates blocks
   428  containing transactions in a well-defined sequence originating from all the
   429  different applications connected to the network.
   430  
   431  Our first update to the ledger will create a new car. We have a separate program
   432  called ``invoke.js`` that we will use to make updates to the ledger. Just as with
   433  queries, use an editor to open the program and navigate to the code block where
   434  we construct our transaction and submit it to the network:
   435  
   436  .. code:: bash
   437  
   438    await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom');
   439  
   440  See how the applications calls the smart contract transaction ``createCar`` to
   441  create a black Honda Accord with an owner named Tom. We use ``CAR12`` as the
   442  identifying key here, just to show that we don't need to use sequential keys.
   443  
   444  Save it and run the program:
   445  
   446  .. code:: bash
   447  
   448    node invoke.js
   449  
   450  If the invoke is successful, you will see output like this:
   451  
   452  .. code:: bash
   453  
   454    Wallet path: ...fabric-samples/fabcar/javascript/wallet
   455    Transaction has been submitted
   456  
   457  Notice how the ``invoke`` application interacted with the blockchain network
   458  using the ``submitTransaction`` API, rather than ``evaluateTransaction``.
   459  
   460  .. code:: bash
   461  
   462    await contract.submitTransaction('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom');
   463  
   464  ``submitTransaction`` is much more sophisticated than ``evaluateTransaction``.
   465  Rather than interacting with a single peer, the SDK will send the
   466  ``submitTransaction`` proposal to every required organization's peer in the
   467  blockchain network. Each of these peers will execute the requested smart
   468  contract using this proposal, to generate a transaction response which it signs
   469  and returns to the SDK. The SDK collects all the signed transaction responses
   470  into a single transaction, which it then sends to the orderer. The orderer
   471  collects and sequences transactions from every application into a block of
   472  transactions. It then distributes these blocks to every peer in the network,
   473  where every transaction is validated and committed. Finally, the SDK is
   474  notified, allowing it to return control to the application.
   475  
   476  .. note:: ``submitTransaction`` also includes a listener that checks to make
   477            sure the transaction has been validated and committed to the ledger.
   478            Applications should either utilize a commit listener, or
   479            leverage an API like ``submitTransaction`` that does this for you.
   480            Without doing this, your transaction may not have been successfully
   481            ordered, validated, and committed to the ledger.
   482  
   483  ``submitTransaction`` does all this for the application! The process by which
   484  the application, smart contract, peers and ordering service work together to
   485  keep the ledger consistent across the network is called consensus, and it is
   486  explained in detail in this `section <./peers/peers.html>`_.
   487  
   488  To see that this transaction has been written to the ledger, go back to
   489  ``query.js`` and change the argument from ``CAR4`` to ``CAR12``.
   490  
   491  In other words, change this:
   492  
   493  .. code:: bash
   494  
   495    const result = await contract.evaluateTransaction('queryCar', 'CAR4');
   496  
   497  To this:
   498  
   499  .. code:: bash
   500  
   501    const result = await contract.evaluateTransaction('queryCar', 'CAR12');
   502  
   503  Save once again, then query:
   504  
   505  .. code:: bash
   506  
   507    node query.js
   508  
   509  Which should return this:
   510  
   511  .. code:: bash
   512  
   513    Wallet path: ...fabric-samples/fabcar/javascript/wallet
   514    Transaction has been evaluated, result is:
   515    {"color":"Black","docType":"car","make":"Honda","model":"Accord","owner":"Tom"}
   516  
   517  Congratulations. You’ve created a car and verified that its recorded on the
   518  ledger!
   519  
   520  So now that we’ve done that, let’s say that Tom is feeling generous and he
   521  wants to give his Honda Accord to someone named Dave.
   522  
   523  To do this, go back to ``invoke.js`` and change the smart contract transaction
   524  from ``createCar`` to ``changeCarOwner`` with a corresponding change in input
   525  arguments:
   526  
   527  .. code:: bash
   528  
   529    await contract.submitTransaction('changeCarOwner', 'CAR12', 'Dave');
   530  
   531  The first argument --- ``CAR12`` --- identifies the car that will be changing
   532  owners. The second argument --- ``Dave`` --- defines the new owner of the car.
   533  
   534  Save and execute the program again:
   535  
   536  .. code:: bash
   537  
   538    node invoke.js
   539  
   540  Now let’s query the ledger again and ensure that Dave is now associated with the
   541  ``CAR12`` key:
   542  
   543  .. code:: bash
   544  
   545    node query.js
   546  
   547  It should return this result:
   548  
   549  .. code:: bash
   550  
   551     Wallet path: ...fabric-samples/fabcar/javascript/wallet
   552     Transaction has been evaluated, result is:
   553     {"color":"Black","docType":"car","make":"Honda","model":"Accord","owner":"Dave"}
   554  
   555  The ownership of ``CAR12`` has been changed from Tom to Dave.
   556  
   557  .. note:: In a real world application the smart contract would likely have some
   558            access control logic. For example, only certain authorized users may
   559            create new cars, and only the car owner may transfer the car to
   560            somebody else.
   561  
   562  Clean up
   563  --------
   564  
   565  When you are finished using the FabCar sample, you can bring down the test
   566  network using ``networkDown.sh`` script.
   567  
   568  
   569  .. code:: bash
   570  
   571    ./networkDown.sh
   572  
   573  This command will bring down the CAs, peers, and ordering node of the network
   574  that we created. It will also remove the ``admin`` and ``appUser`` crypto material stored
   575  in the ``wallet`` directory. Note that all of the data on the ledger will be lost.
   576  If you want to go through the tutorial again, you will start from a clean initial state.
   577  
   578  Summary
   579  -------
   580  
   581  Now that we’ve done a few queries and a few updates, you should have a pretty
   582  good sense of how applications interact with a blockchain network using a smart
   583  contract to query or update the ledger. You’ve seen the basics of the roles
   584  smart contracts, APIs, and the SDK play in queries and updates and you should
   585  have a feel for how different kinds of applications could be used to perform
   586  other business tasks and operations.
   587  
   588  Additional resources
   589  --------------------
   590  
   591  As we said in the introduction, we have a whole section on
   592  :doc:`developapps/developing_applications` that includes in-depth information on
   593  smart contracts, process and data design, a tutorial using a more in-depth
   594  Commercial Paper `tutorial <./tutorial/commercial_paper.html>`_ and a large
   595  amount of other material relating to the development of applications.
   596  
   597  .. Licensed under Creative Commons Attribution 4.0 International License
   598     https://creativecommons.org/licenses/by/4.0/