12 Factor App
The design of Usage Engine has been highly influenced by cloud native design principles. To get an understanding of what that means in practice, we will go through the 12 factors outlined by the 12 Factor App methodology, design principles often referred when building software for the cloud. More on the 12 Factor App can be found on https://12factor.net/.
Codebase
One codebase tracked in revision control, many deploys
This principle dictates that there should be a one-to-one mapping between code base and application. Usage Engine is a platform, which is the application, built from a single code base. On top of the Usage Engine platform there are solutions. These solutions are built from separate codebases and deployed as individual applications.
Dependencies
Explicitly declare and isolate dependencies
The Usage Engine platform and solutions deployed on it utilize an advanced packaging system to manage build- and runtime dependencies. Fine grained runtime isolation on class level is used using the well adopted OSGi modularity framework.
Config
Store config in the environment
Usage Engine has been carefully designed to separate environment configuration from the application itself. In practice this means that things like database connections, different system properties, resource allocation as well as deployment specific parameters related to solutions can be defined in values files separate from the Helm charts used to package the application and solutions.
Backing Services
Treat backing services as attached resources
Given the clear separation between configuration and implementation, Usage Engine also makes no difference to whether consumed services are run locally or provided by third party. An example is the system database, which can be anything from an in-process Derby-database through a Postgres hosted in the same Kubernetes cluster to a fully external database service offered by the cloud provider (like AWS RDS) or a central Oracle database operated by a DBA team.
Build, Release, Run
Strictly separate build and run stages
Usage Engine uses a separation of the build and execution stages, both for the platform application itself but also for solutions. For solutions, the workflow package concept provides this separation. A solution is designed in the development environment, tested, and then deployed without modifications into a runtime/production environment together with environment specific configuration. To change solution implementation, the process is restarted from the beginning, i.e., the development environment.
Processes
Execute the app as one or more stateless processes
Usage Engine platform and solutions are executed as processes, hosted in Kubernetes Pods, and orchestrated, scaled and lifecycle controlled by the Kubernetes scheduler. State that needs to survive a restart of such a Pod is persisted to database or other persistent storage.
Port Binding
Export services via port binding
The Usage Engine platform and its solutions are self-contained and does not rely on runtime injection of a webserver into the execution environment to create network-facing services. The solutions do however have a dependency to the availability of the Usage Engine platform service to be able to download resources, which is a design decision. This is not violating the Port binding factor though as the contract with the execution environment is fulfilled by the binding of IP ports. Note that batch-based solutions are different in nature and are not serving user requests or input as online solutions are. The port binding factor still applies for batch-based solutions in how the batch jobs are configured and scheduled through an API, managed by Kubernetes API server.
Concurrency
Scale out via the process model
Usage Engine makes use of the process concurrency model with autoscaling mechanisms to achieve scale out. Through the ECD concept, certain workloads and tasks are assigned to certain process types, individually configured, and scaled to suite the requirements of their respective task in the best way.
Disposability
Maximize robustness with fast start-up and graceful shutdown
Usage Engine processes are disposable, meaning they can be started or stopped at a moment’s notice. They use graceful termination by acting on SIGTERM signals to make sure executing jobs and workflows are shut down in a controlled manner without losing data.
Dev/prod Parity
Keep development, staging, and production as similar as possible
The Dev/prod parity factor states that development and production environments should have as small a gap as possible. How well this is fulfilled is up to the team operating the environment, which in the case of Usage Engine is typically the customer.
What Usage Engine provides though is the means to facilitate the process of keeping environments as similar as possible. By packaging both the platform application and solutions in Helm charts with separate parameterization, it is easy to keep environments up to date. Usage Engine provides all the tools, including pre-built templates, to setup fully automated CI/CD pipelines.
Logs
Treat logs as event streams
All Usage Engine log data is routed to standard out where it becomes available through the Kubernetes ‘logs’ function. It is encouraged to collect all log data using log collection service like Fluent and forward it to a centralized storage like Elasticsearch for further analysis. This process is described in the user documentation.
Usage Engine by default formats log data as structured JSON events to make it suitable for analysis in Elasticsearch or similar tool.
Admin Processes
Run admin/management tasks as one-off processes
Usage Engine provides the purpose-built command line tool ‘mzcli’ to enable running admin processes as one-off tasks.