Continual Assembly
Overview
The Assembly system is Continual’s low-level framework for building processes from a component specification.
In general, our design strategy involves creating service interfaces that are stitched together explicitly in the process. This gets us most of the decoupling we want in the system but leaves out dependency injection magic. Our service’s constructors receive a service container and a configuration object. They’ll then ask the service container for all dependencies by name. (Generally that name comes from the configuration passed to the service constructor.)
Services and the Service Container
In the Assembly framework, a Service is a Java interface that requires a class to start, stop, and return current run status.
Assembly’s top-level object is the ServiceContainer. This object holds some number of named Service instances. The ServiceContainer can be started and stopped. On start, the ServiceContainer starts its contained services. On stop, the ServiceContainer signals each service to stop. A caller can run awaitTermination() to wait for all services in the container to report that they’ve stopped.
During startup, each Service is presented with a reference to the ServiceContainer as well as its own specific configuration data. The service constructor can therefore find the other services it needs to run. For example, an HTTP API service component may want to find an IdentityManagement service by name.
💡 Assembly doesn’t allow for a component search by interface. Objects are always named and explicitly connected that way.
Configuration
The Assembly framework allows you to configure your system using a JSON document that specifies a list of service components, additional configuration information, and runtime profiles. The ServiceContainer class knows how to read this document, typically called services.json, to build a container instance.
Services
The most important segment of the JSON configuration is the services array. Each object within this array represents a Service instance with the following fields:
Field | Description |
---|---|
class | The class used to construct the service instance. |
name | The instance’s name in the service container. |
enabled | If false, the service object is ignored. Defaults to true. |
The entire JSON object is provided to the class constructor allowing you to make additional settings for the service instance.
Evaluation
Depending on the service component implementation, most configuration fields can be defined using references to other data including the environment and the Java system command line.
For example, to use an environment variable for the SMTP server in the example above:
Config
Another top-level object in the services configuration is the config object. Data stored in this object can be referred to in service configurations:
Profiles
Finally, the top-level profiles object can be used to select among configurations at startup. Each sub-object of an active profile is merged over the service it names. For example, suppose we want to disable the FooUser service when the system starts with profile “noFoo” active:
Running a Service Container System
Applications can use the Assembly framework in a variety of ways, but the most typical use, at least for Continual-built systems, is to use a main class that subclasses io.continual.services.Server and passes main()‘s arguments to runFromMain().
For systems built this way, command line arguments are as follows:
- -s: specify a services JSON file. Defaults to
services.json
and is expected to be found somewhere in theCLASSPATH
- -p: specify a comma-separated list of profiles to activate. If this argument is not included, default becomes active if it exists in the configuration.
When the system starts, the services file is processed, all enabled service components are instantiated, and then all enabled service components are started.
The process continues to run until all services exit. If a termination signal is received (e.g. SIGTERM
), the service process requests that all service components terminate.
Building a Service Component
The Assembly framework includes a Builder utility that constructs objects given a class name and configuration data. Once the builder locates the class specified in the configuration, it looks for a suitable constructor. The following methods are considered, in order:
A static initializer method that expects a JSON object of config data:
A non-static initializer after constructing with a no-arg constructor:
A static initializer method that expects data as well as the service container:
A non-static initializer after constructing with a no-arg constructor, this time expecting data as well as the service container:
A constructor that expects data as well as the service container:
A constructor that expects just the data class: