WARNING: Sapper is still under development. Breaking changes can occur at any time.
Sapper is CLI tool that enables you to rapidly create, extend, and update C++ microservices. It is based on an ever growing template repository --- the brick repo --- with a focus on cloud native technology from which you can compose your microservices, e.g.:
- http and gRPC handler
- postgreSQL database
- JWT based authentication
- docker and kubernetes
- support for AWS, Azure, and GCP
- ...
Besides using C++, the microservices that can be created with Sapper are constrained by the following:
Sapper and the microservices created with it have a built in support for Linux on AMD64. No efforts are planned to support other platforms. On Windows, the usage of wsl is recommended.
The structure of each microservice follows the hexagonal -- aka ports & adapters -- architecture:
Layer | Desription | Depends on |
---|---|---|
ports | Domain entities and interfaces that the core needs to work with. | - |
core | Contains all business logic without directly depending on any infrastructure (e.g. databases). | ports |
adapters | Implementation of the interfaces defined in the ports layer. Adapters should be independent from each other, but often rely on third party libraries. | ports, external libraries |
app | The main application that brings everything together: Reads configuration, instantiates the core, instantiates the adapters, injects the adapters into the core, calls the handler. | core, adapters |
Dependencies on 3rd party libraries are managed by the conan package manager. Thus, each microservices has a conanfile.txt in its root folder.
Each microservice contains a Unix Makefile in its root folder that allows to build, test, and deploy the service. Sapper uses the GNU make command during some operations. By default CMake and CTest are used for building and testing a microservice, respectively.
The Microservice Essentials library implements or facilitates the implementation of cross cutting concepts in microservices such as request handling, observability, error handling, and many more. The cmd and many of the adapters depend on this library, which in turn has no transitive dependencies by design.
-
Make sure that Conan v1.x, CMake, make, and a C++ toolchain (e.g. clang, gcc) is available in your linux build environment. If that's the case, the following commands run without problems:
conan --version cmake --version make --version gcc --version #alternative 1 clang --version #alternative 2
-
Install Go by e.g. running:
wget https://go.dev/dl/go1.20.4.linux-amd64.tar.gz rm -rf /usr/local/go tar -C /usr/local -xzf go1.20.4.linux-amd64.tar.gz export PATH=$PATH:/usr/local/go/bin go version
-
Install Sapper:
go install github.com/seboste/sapper@latest export PATH=$PATH:~/go/bin sapper version
-
Create your first Sapper C++ microservice:
sapper service add my-service cd my-service sapper brick add handler-http sapper brick add observability-structured-logging sapper brick add repo-in-memory make build
INFO: hit enter when prompted to specify parameters. It is recommended to execute the commands in a git repository directory and commit the changes after executing each line. In that way, changes made by Sapper can be reconstructed easily.
The just created service has a http handler that exposes two example REST endpoints, one for setting and one for getting an example entity. Logs are written to standard output in JSON format. The entities are managed in memory by a simple C++ map.
-
Run and test your service:
export LOG_LEVEL=TRACE make run
The service is now up and running. You can now call the service from a different terminal:
curl -X PUT localhost:8080/Entity/test -H "Content-Type: application/json" -d'{"id":"test","string_property":"str","int_property":42}' curl localhost:8080/Entity/test
An example entity has been stored in and retrieved again from the service. As tracing has been turned on, extensive logs have been written to the console.
-
Add your business code to the core and adapt the ports and adapters to your needs. Voilà, your first C++ microservice is ready.
A new Sapper C++ microservice can be created by running the
sapper service add <servicename>
command. This will create a new directory containing the initial hexagonal microservice structure along with a conanfile.txt
to manage dependencies, a CMakeLists.txt
, and a Makefile
. Many source files
include code lines that define so called sapper sections:
//<<<SAPPER SECTION BEGIN **SOME_SECTION_NAME**>>>
...
//<<<SAPPER SECTION END **SOME_SECTION_NAME**>>>
Sapper uses them to identify code areas that may need to be replaced, merged, appended, or prepended by when adding new sapper bricks to the microservice. Modify them only with caution as it might impede sapper.
You can build and execute the service with the following commands:
cd <servicename>
sapper service build .
make run
As this microservice only contains example ports and a simple example core, the executable returns immediately. You need at least one handler and a repository (aka a DB) for your microservice to do something somewhat meaningful.
New features (e.g. handlers, databases, notifiers, security, observability, deployment, ...) can be added to a Sapper C++ microservices by adding so called bricks. Use the following command to get a complete list of available bricks including the unique identifier, the version, and a brief description of each brick:
sapper brick list
Then add the desired brick to your microservice:
sapper brick add <brickname>
and enter values for parameters when prompted. New code from the brick library is added to the microservice's codebase (typically by adding another adapter) and integrated into the microservices codebase (typically by adding a few lines of code to the main.cpp
in the app
folder and adding dependencies to 3rd party libs to the conanfile.txt
).
INFO: Bricks assume that the ports are unchanged by the developer, i.e. the microservice works on that example entity mentioned earlier. Thus, it is recommended to first add the desired bricks to your microservice and then adapt the code to your needs and not the other way around. You can still add bricks later, but adding some of the files may fail and more manual work may be required.
INFO: CI ensures that the microservice can be built successfully out of the box when adding a single brick to the initial microservice. However, it is not guaranteed that all possible combinations of bricks can be built successfully (e.g. due to dependency clashes). Some manual fixes may be required.
A regular maintenance task for developers is to update the dependencies. For security reasons and because frequent small increments typically are less error prone and work intense than infrequent big increments, this task should be done often. Sapper can facilitate this process by running the command
sapper service upgrade .
It will build the service, identify which dependencies are not up to date, and then in a bisection method update each dependency until the highest dependency library version is identified for which the build process without code changes is successful.
Sapper obtains the brick library from a github repository. This is https://github.com/seboste/sapper-bricks by default. Sapper allows to replace or extend the brick library or replace individual bricks by managing remotes. Call
sapper remote --help
for more details. This can for example be useful for organizations that want to provide custom C++ microservice templates to be used by all teams of that organization. Please refer to https://github.com/seboste/sapper-bricks's README.md for details on how to create your own Sapper bricks.
INFO: Sapper can only be as good as the underlying brick library. If you create bricks that may be useful to the general public, please consider contributing by creating a pull request to https://github.com/seboste/sapper-bricks.
For a complete description of the commands and subcommands please refer to the tool's help:
sapper --help
sapper <command> --help