I am a big fan of microservice architectures!
Usually, each module is developed as a specific and independent service accessible thru a REST API. The advantage of such implementation is that it is easily scalable, and the system can use a load balancer to access different instances of a highly sollicitated service.
The main problem of the REST protocol is its synchronicity. If a service is slow or does not respond for any reason, the system freezes or some requests like PUT or POST are difficult to play again.
It is better to use a message bus for communication between services to avoid this problem. In this case, communications are much easier from a service side, they post message to the bus and consume messages from the bus, they don’t have to know where the instances of the other services are running. The loadbalancer is no longer useful as the system can start new instances of a service at any time that will consume messages from the bus automatically.
A message bus usage implies a complete asynchronous application. It has many advantages but also have an impact on the application development. Debugging a Microservice is a challenge, and the granularity of the service features is also very difficult to adjust.
Also, a Microservice architecture needs a constant monitoring to detect any service malfunction, and manage the services lifecycle.
In this configuration, each Microservice exposes an API and is connected to the messaging bus. It may access to the database directly to read configuration file if needed. Dedicated services will be used to read or store data from documents, depending of the configuration for each customer.
Strategic goals mean where we want to go with the application. They are a high level design and should not change until the application is delivered. In a Microservice architecture design, it is necessary to start with this global view to define the next steps, up to the lowest level and most and technical detail.
In our case, the goals are the following:
- Enable high customization level
- In the data representation
- In the interfaces
- In the workflow definition
- Support the complete laboratory business
- Open to any laboratory business, not only construction industry
- Support any industry standard
- Scalable and deployable locally
- More than one user and laboratory on the same server
- Local instance for data security and confidentiality
- Multiple display support
- Office computer, mobile, internal and external interface like customer web site, 3rd party integration.
From these goals, we can define some architectural principles
Principles are rules we make in order to align what we do from strategic goals and may change if necessary.
Here we have:
- Schemaless data definition
- Make data definition free from any existing and hard coded model.
- Consistent interfaces
- The layouts are standardized in their representation and design even if the content is different. There is no room for fancy design.
- Eliminate accidental complexity
- Aggressively get rid and replace unnecessary complex process, systems and integrations and focus on essential complexity
- Make simple modules working together and focus on loose coupling and high cohesion of these modules
- Backing services are treated as attached resources
- The database accesses are attached resources, accessed via a URL or other locator/credentials stored in the configuration service.
- Each distinct backing service is a resource. The app treats these databases as attached resources, which indicates their loose coupling to the deployment they are attached to.
- Execute the app as one or more stateless processes
- processes are stateless and share-nothing. Any data that needs to persist must be stored in a stateful backing service, typically a database.
- The memory space or filesystem of the process can be used as a brief, single-transaction cache. For example, downloading a large file, operating on it, and storing the results of the operation in the database. The app never assumes that anything cached in memory or on disk will be available on a future request or job.
- Store config in the environment
- An app’s config is everything that is likely to vary between deploys (staging, production, developer environments, etc). This includes:
- Resource handles to the database, Memcached, and other backing services
- Credentials to external services such as Amazon S3 or Twitter
- Per-deploy values such as the canonical hostname for the deploy
- Strict separation of config from code. Config varies substantially across deploys, code does not.
- The app stores config in environment variables (often shortened to env vars or env). Env vars are easy to change between deploys without changing any code. Unlike config files, there is little chance of them being checked into the code repo accidentally
From these principles, we will define some practices, which are more a description of their implementation.
Practices are how we ensure our principles are being carried out. They are a set of detailed, practical guidance for performing tasks. They will be often technology-specific and low level enough that any developer can understand them.
Here are some practices that underpin our principles, but more will come as we advnce in the description process.
- NoSQL database
- Standard REST/HTTP API or better GraphQL
- No integration databases
- Small independant services
- 80% code coverage
- Use container like docker for deployment