.. |cbox| raw:: html Exercise 2: A proper phonebook Mochi component ============================================== .. important:: This exercise (as well as Exercise 3) does not need the :code:`thallium-tutorial-exercises` repository used in Exercise 1. .. important:: If you come from Exercise 1, make sure to deactivate the Spack environment before starting this exercise (we will build a new one, which we will also use in Exercise 3). This can be done with :code:`spack env deactivate` in any terminal in which you had activated the environment. You can check whether an environment is active using :code:`spack env status`. In this exercise, we will use the `Thallium microservice template `_ to develop a proper phonebook microservice. |cbox| Click on the green **"Use this template"** button and select **"Create a new repository"**. Give the repository a name (e.g. *"phonebook"*). Put the repository on "private" if you wish, then click on **"Create repository from template"**. |cbox| Click on **Settings > Actions > General**, and set the Workflow permissions to **"Read and write permissions"**, then click **Save** in the *Workflow Permissions* section (there are save buttons for each section that will save only the modifications made to that section). |cbox| Go back to the root of the code (in your browser), and edit :code:`initial-setup.json`. Change *"alpha"* to a service name of your choosing (this name will also be used as namespace for your API, so choose something short, e.g. **YP**, for yellow pages. In the following, we will assume this is the name you used). Change *"resource"* to the name of the resource we are going to manage, here *"phonebook"*. Click on the green **"Commit changes"** button. |cbox| Wait a little and refresh the page. You might see a brown dot indicating a workflow action is in progress. Or you might get a 404, which means the workflow completed: as part of the GitHub workflow that sets up your code, it will delete :code:`initial-setup.json`. .. note:: Other github workflows will run to test your code and upload a coverage report to *codecov.io* whenever you push commits to GitHub. These workflows will not work properly if you have made the repository private, so you may receive emails from GitHub about some failed workflows. Simply ignore them. |cbox| Clone your code in the :code:`mochi-tutorial` directory on your machine, then create a Spack environment and build the code like you did in Exercise 1, using the :code:`spack.yaml` file at the root of your new project (:code:`spack env create ex2 spack.yaml`, :code:`spack env activate ex2`, :code:`spack install`). Create a :code:`build` directory, *cd* into it, and build the code (:code:`cmake .. -DENABLE_TESTS=ON` then :code:`make`). You may want to use the flag :code:`-DENABLE_TESTS=ON` when calling cmake to make sure that the tests are also built. You can run the tests with :code:`make test`. .. note:: The :code:`cmake` will download `Catch2 `_ as part of the build process, :code:`make` will build it. The template provides the implementation of two RPCs, *"hello"* and *"sum"*. These RPCs are defined in a way that is very different from Exercise 1. Instead of using lambdas, they are defined as member functions of the :code:`ProviderImpl` class in :code:`src/ProviderImpl.hpp`. .. note:: The architecture of this project uses the PImpl technique (pointer to implementation), where user-facing classes (e.g. :code:`Client`, :code:`PhonebookHandle`, etc.) only have a pointer field along with public member functions, and the actual data associated with an instance is in a private class (e.g. :code:`ClientImpl`, :code:`PhonebookHandleImpl`). While this technique adds a small overhead in function calls, it also better decouples the API of your service from its actual implementation. |cbox| :code:`include/YP/Client.hpp` contains the :code:`Client` class, which will be used to register the RPCs and contact servers. There is nothing to modify in this file. |cbox| :code:`include/YP/PhonebookHandle.hpp` declares a :code:`PhonebookHandle` class, which represents a phonebook managed by a remote server. This file is also where the relevant client interface will be defined. Go ahead and add declarations for an :code:`insert` and a :code:`lookup` member functions. |cbox| :code:`src/ClientImpl.hpp` contains the definition of the :code:`ClientImpl` structure, which hides the actual data associated with a :code:`Client` instance. Go ahead and add two :code:`tl::remote_procedure` fields to represent the :code:`insert` and :code:`lookup` RPCs, and initialize them in the constructor. |cbox| :code:`src/PhonebookHandle.cpp` contains the implementation of the :code:`ResourceHandle`'s member functions. Go ahead and implement the :code:`insert` and :code:`lookup` functions. You can copy the :code:`computeSum` function and adapt it accordingly. .. note:: The template proposes that all the RPCs wrap their result in a :code:`RequestResult` instance. You can view this class like a variant that will either store a :code:`T` instance or an error string if the operation failed. If you implement your own service, feel free to handle errors differently. .. note:: The template handles non-blocking RPCs by adding an optional :code:`AsyncRequest` pointer to each member function. Feel free to remove it (along with the code path that deals with it) from your :code:`insert` and :code:`lookup` functions. You can circle back to them later if you want, as an exercise. |cbox| At this point, feel free to compile your code to make sure it builds fine. You won't be able to test it yet since there is no server-side implementation of our RPCs, so let's focus on the server library next. |cbox| :code:`include/YP/Backend.hpp` contains the definition of a backend, i.e. the abstract class that a phonebook implementation must inherit from. Add the proper :code:`insert` and :code:`lookup` pure virtual methods to this structure, following the model of the :code:`computeSum` function. |cbox| :code:`src/dummy/DummyBackend.hpp` and :code:`src/dummy/DummyBackend.cpp` contain a *"dummy"* implementation of such a backend in the form of a :code:`DummyPhonebook` class. Add an :code:`std::unordered_map` field to this class, as well as the :code:`insert` and :code:`lookup` functions and their implementation. |cbox| :code:`src/ProviderImpl.hpp` contains the implementation of our provider. While it still follows the Pimpl idiom, with the :code:`Provider` class containing a pointer to a :code:`ProviderImpl` instance, you will notice that RPC functions are actually defined as member functions of the :code:`ProviderImpl` class. This is because in Thallium, providers can expose their own member functions as RPC. Go ahead and add the two :code:`tl::remote_procedure` fields for your :code:`insert` and :code:`lookup` RPCs. Don't forget to add their initialization in the constructor and to deregister them in the destructor! |cbox| Still in :code:`src/ProviderImpl.hpp`, implement the two :code:`insertRPC` and :code:`lookupRPC` member functions by taking example on the :code:`computeSumRPC` member function. |cbox| At this point, you can make sure your code builds fine. Your microservice is ready! If you have time, feel free to look into the tests folder, in particular the :code:`PhonebookTest.cpp` file, and edit it (replacing calls to the :code:`computeSum` RPC) to try out your new functionalities and test them (:code:`make test`). |cbox| In practice, the next steps at this point would be to (1) add more tests, (2) remove everything related to the "hello" and "sum" RPCs (because obviously a phonebook is not a calculator), and (3) implement more complex backends by copying the code of the **"dummy"** backend and changing it to use external libraries or more complicated implementations.