Self-contained Systems: A different approach to microservices
Closeup of wood blocks stack game image via Shutterstock
Microservices are the current hype in the software architecture community. However, microservices can be used in many different scenarios to reach different goals. Self-contained Systems (SCS) represent one approach to achieving some of those goals while avoiding many common pitfalls.
What are microservices?
Microservices are not well defined. For the purpose of this article, microservices  are considered another way to modularize software. Usually, modules partition a system to simplify design and development. But once the software is built, it becomes just one unit that is deployed as a whole. This is different for microservices: each microservice can be deployed by itself. However, this is only possible if a technology is used which supports that — e.g. if each microservice is a Docker container. Then, each deployment would replace the Docker container with a different version that is then started and replaces the old Docker container. This also means microservices can use any technology and platform that seem useful — as long as it runs in a Docker container.
There are many different reasons why teams might turn to microservices as an alternative architecture. For example, smaller teams might use microservices because Continuous Delivery is much easier with smaller deployment units. They can also be scaled individually instead of scaling a large deployment monolith.
Large projects might use microservices to scale agile processes. Each team can autonomously develop and deploy its microservices without a lot of coordination with other teams. They can bring features into production independently. Also, almost all technical decisions can be made inside one team. Hardly any coordination is needed anymore — neither concerning technology nor concerning new features. That way much larger systems can be built because communication is often the limiting factor.
SCS are different from microservices at a fundamental level: They are well defined. The SCS website  includes this definition. SCS are web applications — including the code to render the web interface, the business logic as well as the data and the database. This sets them apart from microservices that are often considered to just provide a REST interface — so the UI is a separate entity. Also, microservices might or might not provide the data.
Because each SCS has its own web interface, SCSs should not have a shared UI. That way a change or a new business feature can usually be contained to one SCS. No matter whether the change would just require a small tweak to the logic or a lot of modifications to not just the logic but also the data structures, database, and UI — all of this is included in the SCS. So the change can be contained in a single SCS.
SCS might also provide an API to call the logic from the outside. However, the API is optional. It might be used to access the logic e.g. from a mobile app. However, in that case, a change might require changes to the SCS and the mobile app — so it becomes more complicated to implement the change.
A system that just provides an API is not an SCS by definition. It might still be considered a useful architecture, of course, but it would not be called SCS. As you can see, the definition for a Self-Contained System excludes quite a few types of microservices systems and is therefore very precise.
Limiting each change to a single SCS is an important goal. As mentioned above, microservices and SCS are independent deployment units. So if a change or a new feature just requires one SCS to be updated, only one SCS needs to be deployed. This is much easier than deploying a multitude of microservices and coordinating changes across various microservices.
Investing all the effort to make modules of a system individually deployable makes little sense if developers end up deploying more than one module for each change. Also, deployments that cover multiple microservices are quite hard. If microservices communicate through an interface that needs to be updated, care must be taken not to break the communication during the update process. That requires backward compatibility or versioning of interfaces, which adds to the complexity of the system.
SCS and organization
Organization is core to the definition of a SCS. One of the rules is: A SCS should be owned by one team. If the SCS is rather small, a team might also work on multiple SCS. But multiple teams might never work on a single SCS because that would require too much communication and coordination. It just doesn’t make any sense to separate a system into decoupled modules but then have several teams work on one module.
Thus, a feature would ideally be implemented by a single team in a single SCS, so teams can work on features in isolation and decide about the implementation and the architecture.
But each SCS can also use different technologies. Not just frameworks but also programming languages and even databases. This means the technical decision can be made by each team. It goes without saying that standards can still be set and enforced. But even then, a team might decide to introduce an update or a bug fix for a library it uses without having to organize meetings with all the other teams.
Ideally, Self-Contained Systems should be as decoupled as possible and not communicate with each other at all. However, the SCS are still part of a larger system. Therefore, some kind of integration is needed.
Communication to other SCS can be done in a different way:
- Of course, SCS sometimes might need to call each other. Preferably, that should be done asynchronously. Asynchronous communication has many advantages: It decouples the systems and avoids error cascade. If asynchronous communication is used, a temporary unavailability is no big issue.
- SCS can also communicate synchronously if this is necessary. However, the systems then are more tightly coupled and error cascades might occur — particularly if synchronous request happens during request processing. At other times, this might still work fine. You might even argue that this would actually be asynchronous communication because even if the communication is delayed for some time, both systems just work fine
It is important to note that asynchronous communication does not really depend on the protocol . Asynchronous communication can be easily implemented using messaging protocols like AMQP or JMS. However, it is also possible to use REST for asynchronous communication, e.g. by polling data from an Atom feed. The Atom format is usually used to subscribe to blogs. To get updates, the feed is polled. Thus, it is possible to do asynchronous communication over a synchronous protocol.
During request processing, an SCS should not communicate with any other system synchronously. However, it might still need data from other systems. It seems to be impossible get the data asynchronously because they are needed during the processing of the request. To enable asynchronous communication it might be necessary to replicate the data. That way, data would be copied to the other system. There would be no need to call another system if this data is needed to process a request — it is already available in the other system.
Of course, this means that data is stored redundantly. This might cause inconsistencies. Some changes might not have made their way to all replicas. There are several ways to deal with this issue:
- One system should be the source of truth for each data item. That way, it is possible to replace any data with the latest version from the original source.
- Event sourcing advocates focus on the events and treat databases as just a snapshot of the state after all events have been processed. If the data is corrupt, it can be recreated by reprocessing the events.
The additional effort for replicating data and the potential inconsistencies are the price that must be paid for the decoupling. Decoupling is an important goal for any microservices system and therefore it might make sense to also replicate data. However, it would still be possible to get data from other SCS synchronously. Synchronous communication is not forbidden — it is just the least preferred option.
Cutting a system into SCSs can be done by a vertical decomposition. The system has to be separated into multiple parts that each have a UI, logic, and database. This might be done by implementing a group of use cases in each SCS. Each SCS is then responsible for the complete implementation of the use cases — including UI, logic, and database. For example, in an e-commerce application, choosing the payment method and choosing the method of delivery are two related use cases. They might end up in one SCS. The SCS then contains the data to implement these use cases, i.e. rates for the different delivery methods. Obviously, this data would later be needed to actually delivery the order. Therefore, the data might be replicated to other SCSs that implement these parts of the system.
It might also make sense to look at a customer journey. In an e-commerce system, a customer will usually first search for products, put product in a shopping cart, then register or log in, and finally, check out. Later on, he / she might be looking at the current status of his / her order. Each of these stages might be implemented in an SCS — again with data, logic, and UI.
These approaches ensure that the separation into SCS results in a clear separation into modules that are each responsible for a part of the domain logic. This architecture also increases the chance that changes are local to one SCS. Usually, a change will only cover related use cases and those are implemented in one SCS.
Who is doing it?
There is a considerable number of projects that use SCS and companies that implemented the approach successfully . These include Otto, #3 in the e-commerce business, after Amazon and eBay. Otto created a new e-commerce website that provides 85 percent of the revenue with SCSs. Each team at Otto implements one step in the customer journey in one SCS.
Another example is Kühne + Nagel – a logistics company with more than 60.000 employees and more than $20bn in revenue. SCSs have proven that they work in practice and solve many crucial issues that a lot of organizations face nowadays.
Of course, there are challenges when implementing SCS. The focus on UI integration means that a common foundation must be defined. If links are used to integrate SCS, the UIs of the SCSs can be quite diverse. However, if transclusion is used to integrate parts of a web page from multiple sources, a common set of CSS might have to be defined. The same is true if the system should have a common look and feel. Common assets on a shared asset server might be an option. Care has to be taken to keep the SCSs as independent from each other as possible. Sharing assets is a very strong dependency.
At the end of the day, this is not very different from what other means of communication (like REST) require: A definition of an interface to ensure that all systems can actually talk to each other. The only difference is that in the case of SCSs, it is about common UI elements and a shared UI.
Sometimes the SCS concept cannot be fully implemented because data is stored in backend systems. Ideally, an SCS would include all logic and data in its own system. However, the backend system might be beyond the reach of the project. This means that the data and parts of the logic would remain in the backend system. This seems like a contradiction: Shouldn’t all the logic be in the SCS and therefore the frontend? The pragmatic approach is to use as many ideas from the SCS approach as possible, i.e. the focus on UI integration, asynchronous communication, and data replication. These can also be applied to the integration with backend systems. Maybe you cannot get all logic in the front end but the approach will still help you to understand how backend systems can be handled.
Self-Contained Systems provide a way to split a system into individually deployable units, essentially microservices. They focus on loose coupling, enabled by data replication, asynchronous communication and UI integration. This helps build systems from actually decoupled services, and therefore focuses on the strengths of microservices. Experience shows that this approach is working quite well for large and small projects alike.
If you want to take a closer look at Self-Contained Services, this website  contains a lot of material. It is licensed under a Create Commons license (CC BY-SA 3.0) and can therefore easily be used for your own purposes. The website is hosted on Github  – so feel free to submit issues and pull requests.
Links and literature
: Microservices: Flexible Software Architectures, Eberhard Wolff, 2016, ISBN 978-1523361250
: Microservices Primer: A Short Overview, Eberhard Wolff, 2016, download for free at http://microservices-book.com/primer.html