Dear Reader,
I spent large parts of March on writing the article Extracting Microservices from a Modular Monolith. This article is also the basis for my talk about the same topic at the Embedded Online Conference 2025. Don’t miss this amazing conference!
My knowledge about microservices comes from three resources:
The Microservices Guide is a collection of articles written by Martin Fowler and other experts.
Sam Newman, Monolith to Microservices: Evolutionary Patterns to Transform Your Monolith, 2020.
Vaughn Vernon and Tomasz Jaskula, Strategic Monoliths and Microservices: Driving Innovation Using Purposeful Architecture, 2022.
All resources target enterprise software and cloud services. You can apply many ideas to embedded systems but probably with some adaptation.
I haven’t read all of the articles and books. I have picked out the information most relevant to my work. In this newsletter, I want to share some of the articles from the collection Microservices Guide. I may cover parts of the books in later newsletters.
Enjoy reading,
Burkhard 💜
My Content
Extracting Microservices from a Modular Monolith
In this very long article, I explain why and how to extract microservices from a modular monolith based on the ports-and-adapters architecture. This article is the basis for my talk at the Embedded Online Conference 2025. You can already find a 2-minute teaser video there.
An adapter with its port is especially well-suited for the extraction as a microservice, because they share the same traits: single responsibility; clear business value; small, technology-agnostic interface and behaviour-driven interface; independent deployment. The one difference is that a microservice always runs in its own process, whereas an adapter is provided as a library or plugin.
You should never extract a microservice, because it’s the cool thing to do. You should have a good reason or better two or more: not core business; team scalability; different user privileges; different usage types with different usage patterns; performance; resilience.
After establishing the theory, I walk you through two practical examples:
Extracting the microservice for updating the system. This is the ideal example. System updates are not at all the core business of any OEM. OEMs could buy a ready-made solution with a standard interface from a third party. Another reason for extraction is that system updates require root privileges, whereas the normal application could run as normal user. The EU Cyber Resilience Act turns “could run” into “must run” as normal user.
Extracting the microservice for diagnosing machine failures. At the beginning, only one reason is obvious. Machine diagnosis is done by a technician, who focuses on open and short circuits. At the end, two more reasons will become clear: resilience and performance.
This extraction needs some preparation steps, as the diagnosis functionality is probably distributed over the HMI Adapter, the Core and the Machine Adapter of the monolith. Once this functionality is separated in its own vertical slice of the monolith, you can first extract the Machine Adapter and then the Diagnosis Adapter as a microservice.
In my newsletter Solutions on Modules, I pointed out the crucial role of microservices to sort out the epic mess that embedded Linux is. The following diagram from my talk at EOC 2025 illustrates my point.
The combined API of all microservices separates the OEM’s world (above the API) from the world of the hardware and software vendors (below the API). The API hides the complexity of the embedded Linux system from the OEMs. OEMs can focus on their core business, which certainly does not include building embedded Linux systems, implementing system updates with RAUC, Mender or OsTree, or providing a remote desktop with VNC or TeamViewer.
I am convinced that the hardware vendors with the most complete solutions platform (the world below the API) will win. A de-facto standardised API makes hardware - SoMs, SoCs and SBCs - interchangeable. Hardware is quickly becoming a commodity and software the differentiator. Different SoMs or SoCs are just adapters implementing the low-level port of the Embedded Linux System.
Basics About Microservices
James Lewis and Martin Fowler, Microservices - a definition of this new architectural term, 2014
The definition by Lewis and Fowler is hands-down the best definition of microservices I have found (emphasis mine).
In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms […] These services are built around business capabilities and independently deployable by fully automated deployment machinery.
James Lewis and Martin Fowler
Lewis and Fowler identify the following characteristics of microservices:
Componentisation via Services. Services make for good components, as they are independently deployable and have a published interface. As microservices communicate with each other or with applications over a lightweight mechanism, you must explicitly define the messages making up the interface. As communication between processes or over a network always comes with a delay, the interface should have few high-level functions instead of many low-level functions. This leads to small, behaviour-driven and technology-agnostic interfaces, which is nothing else but the definition of a port in the ports-and-adapters architecture.
Organised around business capabilities. A microservice cuts out a vertical slice from a monolith: from the HMI, over the business logic to the communication with an external system. The microservice for diagnosing machine failures from my article is an example. The Diagnosis Service helps the OEM repair a machine quickly and reduce its downtime: a clear business value. A two-pizza team can implement such a service independently. The team could be internal or external and work in parallel to the OEM’s core team. Ideally, the OEM can buy the service from a third party.
Products not projects. The team building a microservice should consider it as a product they can sell to several OEMs and is responsible for it over its full life cycle. The microservice for updating the system is a good example.
Smart endpoints and dumb pipes. This characteristic is ensured by using communication over a lightweight message bus like D-Bus or gRPC. I prefer D-Bus over gRPC. As the standard communication mechanism, D-Bus is already available on embedded Linux systems. D-Bus is a text-based protocol, whereas gRPC is a binary protocol.
Decentralised governance. Despite the grand wording, the meaning of “decentralised governance” is pretty simple. A team can use any programming language and tool to develop a microservice. Its choice stays hidden behind the technology-agnostic interface.
Decentralised data management. Each microservice should have full control over its own database. Otherwise, you must solve tricky consistency problems. Don’t go down that path!
Infrastructure automation. Teams should use continuous delivery to build, test and deploy microservices.
Design for failure. Users must still be able to perform most of their work, if a service fails. If, for example, the Diagnosis Service or Update Service fail, the user should still be able to operate the machine. Design for failure is also known as resilience.
Evolutionary design. As long as you change the service interface according to the open-closed principle (open for extension but closed for modification), you can upgrade or replace a service implementation. The consumers of the microservice won’t notice the change. They can use new features of the microservices at their own pace.
Martin Fowler, Monolith First, 2015
Fowler argues that you should first build a monolith and then “gradually peel off microservices at the edges”.
[…] you shouldn’t start a new project with microservices, even if you’re sure your application will be big enough to make it worthwhile. [… Instead, ] you should build a new application as a monolith initially, even if you think it’s likely that it will benefit from a microservices architecture later on.
Martin Fowler
Fowler gives two reasons why a monolith-first strategy works better than a microservices-first strategy.
When you start a new product development (a greenfield project), your prime objective is to get quick feedback from users about the viability of your product. The fastest way is to build a monolith. Microservices would slow you down.
Microservices only work well if their interfaces stay stable, that is, if you have found the right abstraction. This rarely happens at the beginning of a project. It takes some learning and requires some refactorings, which are easier in a monolith than over multiple services.
I would rephrase Fowler’s strategy as: First build a modular monolith based on the ports-and-adapters architecture and then extract microservices based on a very good reason.
Stefan Tilkov, Don’t start with a monolith when your goal is a microservices architecture, 2015
Tilkov disagrees with Fowler’s monolith-first approach and calls for a microservices-first approach for greenfield projects.
I’m firmly convinced that starting with a monolith is usually exactly the wrong thing to do.
Starting to build a new system is exactly the time when you should be thinking about carving it up into pieces.
Stefan Tilkov
Tilkov’s main reason against the monolith-first approach is extremely hard, if not impossible, to extract microservices from a monolith. This is certainly true for a big ball of mud (BBoM). As - according to Tilkov - teams almost inevitably end up with such a tightly coupled mess of a monolith despite their best intentions, teams should start with microservices. Teams can then benefit from the main advantage of microservices.
Microservices’ main benefit, in my view, is enabling parallel development by establishing a hard-to-cross boundary between different parts of your system. By doing this, you make it hard – or at least harder – to do the wrong thing: Namely, connecting parts that shouldn’t be connected, and coupling those that need to be connected too tightly.
Stefan Tilkov
Tilkov’s arguments don’t convince me. Most teams starting a new project don't know which components they should put into a microservices and which not. Teams and individuals only have this knowledge if they have built very similar systems a couple of times. This may be common for web applications or enterprise software (definitely not my area of expertise), but it’s certainly rare for embedded systems.
I think that a microservices-first approach would lead to a distributed big ball of mud instead of a monolithic big ball of mud. It is much more difficult to sort out a mess of tightly coupled microservices than a mess of tightly coupled components in a monolith. Coming up with a suite of loosely coupled microservices demands as much discipline as building a modular monolith based on the ports-and-adapters architecture.
Although Tilkov’s arguments don’t convince me, he is right about the main benefit of microservices: “enabling parallel development by establishing a hard-to-cross boundary between different parts of your system”.
My conclusion from Monolith First versus Microservices First
When I start the greenfield development of an operator terminal for a machine, my architecture combines a modular monolith based on the ports-and-adapters architecture with microservices. The monolith implements the core operation of the machine like maximising the yield of the sugar-beet harvest. It delegates the non-core business to the microservices.
From past projects, I know that microservices for updating the system, user authentication, factory installation, diagnosing the machine, calibrating the machine, managing alarms, billing the customer and some more are normally required. Some microservices can be reused without any changes, some need some adaptions to the concrete situations, and some are not needed. More opportunities for extracting microservices will arise over time.
My goal is neither a pure monolithic architecture nor a pure microservices architecture. I want the best of both worlds.
The core team can delegate the development of microservices to other internal or external teams or even buy microservices from third-party vendors. Multiple teams can work in parallel. Consequently, the OEM can reduce the time-to-market significantly.
The core team implements the core business domain in the modular monolith, which is easier to change than microservices. Hence, the team can experiment quicker based on user feedback.
Martin Fowler, Microservices Trade-Offs, 2015
Fowler identifies the following benefits for microservices:
Strong module boundaries. A monolith makes it easy for developers to go around the module boundaries imposed by high-level behaviour-driven interfaces. Respecting the interfaces is up to the developers’ discipline. In contrast, microservices force developers to go through the official interface. There are no shortcuts, because the microservice runs in its own process and is owned by a different team. Hence, it is fair to say that microservices enforce strong module boundaries better than modular monoliths. Of course, the teams must find the right boundaries.
Independent deployment. Microservices have a single purpose, have a stable high-level interface and run in their own process. They are made for continuous delivery. Even if they fail, they won’t bring down the whole system. The failure can be fixed with the delivery of an update.
Technology diversity. Teams can develop microservices with different programming languages, different frameworks, different library versions and different data stores - without affecting other teams. However, they shouldn’t go over board with the diversity.
But microservices also come with costs:
Distribution. Microservices turn your monolith running in a single process into a distributed system of multiple processes communicating with each other and possibly running on different computers. The first problem is performance. If a service calls many small functions on many other services, the latency of these calls adds up pretty quickly. The second problem is reliability. Inter-process or calls can always fail. Debugging these failures is much harder than for in-process function calls.
Eventual consistency. Each microservice has its own data store. If a user changes data stored by several services, the overall system is in an inconsistent state until all services have received the change. Eventually, the system will be in a consistent state again. The services must deal with these temporal inconsistencies.
Operational complexity. Deploying many different microservices regularly and at different times is much more complex than deploying a monolith. Continuous delivery with its fully automated deployment is a must for microservices.
Architects must balance the benefits and costs of microservices in the context of their special system.
Great post! Most of what you've stated and what you consolidated I fully agree with.
I'd like to add that Memfault provides OTA capabilities for embedded Linux for some time as well (https://memfault.com/embedded-linux).
I've worked with microservice architectures in "embedded backends" as well as in "traditional backends" in the web domain. In my opinion the main difference is the need for and the ability to scale services. In "embedded backends" there is usually no need for scaling services horizontally
(you cannot magically make hardware available in a device) and it's not possible to scale services vertically (the compute and memory resources in a device are fixed). I'd highly recommend to have a look at https://microservices.io/patterns/microservices.html cause it makes it pretty obvious with what complexitiy you have to deal with when developing services in "traditional backends" with vertical scalability requirements (running several service instances concurrently, ensuring reliable communication between them, ensuring business transaction lead to data consistency in error cases). In my opinion the argument that vertically scaling services are more reliable than non-scaling services or modular monoliths is extremly weak. In my experience the opposite was true. In "traditional backends" it makes a lot of sense to apply Domain Driven Design cause business transactions spanning several bounded contexts (services/modular monolith apps) are usual. In case service communication between services in "embedded backends" needs to be reliable and/or data consistency needs to be very good intuitivelly I'd favour remote procudure calls (e.g. gRPC) over messaging bassed busses (using MQTT, ZeroMQ) these days. From experience I can tell that messaging technologies need to be considered with care.
I've used RPC based communication in the web domain with a modular monolith as well. The scalability requirements have not been massive and there was a need for strict data consistency. It worked out pretty well cause the skills and discipline in the team was good.