Cost-Oriented Design - Microkernel
This is the first article in the budget-oriented design series โ the Microkernel design pattern, also known as plugin-based service design. This chapter focuses on introducing the architecture of this pattern, along with its advantages and disadvantages. This pattern balances independence and flexibility, making it a highly cost-effective design pattern. Of course, it also has its shortcomings, which we will discuss later.
Before introducing the Microkernel pattern and other various patterns, we need a baseline for comparison. So before diving in, let us review the architecture we are most familiar with โ monolithic architecture.
Monolithic Architecture
You shouldn't start a new project with microservices, even if you're sure your application will be big enough to make it worthwhile. โโMartin Fowler
When monolithic architecture is mentioned, we often scoff at it, as if it were an ancient architecture riddled with flaws. So let us take a look at what this ancient beast actually is.
A monolithic application contains many pieces of logic and services (e.g., user service, article service, comment service), all of which are tightly coupled. Once one service becomes unavailable, it can cause other services to fail as well. This is why it is often used as a contrast to microservice architecture, since microservices are independent and autonomous, and do not suffer from this kind of problem.
As shown in the diagram above, all the functionality in a monolithic architecture is typically concentrated within a single system or software application. Different functionalities depend on and interact with each other. Different functional modules may be developed by the same or different development team(s), then integrated under one software system.
The disadvantages of a monolithic system are obvious โ functionalities are not independent, they depend on each other, the system is difficult to scale, and as features and scope continue to evolve, it becomes bloated and hard to maintain.
However, this architecture has one major advantage โ it is easy to implement! Any complex architecture should ideally start as a monolithic service, which is why Martin Fowler holds the following views:
- Almost all successful microservice systems started as monolithic systems and were split into microservices during development because they became too large.
- Almost all systems that began with a microservices architecture encountered serious problems.
Now let us look at the focus of this article โ Microkernel architecture.
Microkernel Architecture
In fact, both the Microkernel architecture and the monolithic architecture produce a single standalone system, with all functionality residing within that system. The difference from monolithic architecture is that the Microkernel architecture consists of two parts โ the core module (core) and plugin modules (plugins).
The core system is the heart of the Microkernel. It provides the common core functionality shared across the system, and this core functionality rarely changes. The core provides the foundation for the entire system to run โ as long as the core is operational, the entire system can function properly. However, the core system does not guarantee feature completeness.
Plugin modules ensure the availability and correctness of each feature in the system. Each plugin is an independent module or subsystem that attaches to the core system to provide its respective functionality.
Each module must implement the same structure and contract in order to provide its functionality; otherwise, it cannot be executed. Between each module and the core system, only data that conforms to the interface specification is exchanged. Through this strong interface constraint, no matter how modules change, the core system never needs to be modified, thereby ensuring the overall system remains operational.
So how do individual modules organically combine with the core system to form pluggable functional modules? This brings us to an important component of the core system โ the registry. The registry is the bridge connecting the core system and the plugin system. Every module must register with the registry before it can be used, and when a module expires, it is dynamically removed from the registry. The registry structure can be a simple list, map, or other complex data structure. The registry can provide simple interface validation to ensure each module uses a contract-compliant interface, or it can simply handle registration and deregistration while delegating validation to other modules.
In practical development, we frequently encounter another issue: using third-party libraries. How can we ensure that third-party libraries comply with the interface contract? This is where the benefit of plugins becomes evident โ each plugin can have one or more Adapters to perform interface conversions. For more details, refer to the Adapter pattern from the GoF design patterns.
The Microkernel pattern is very common in system development; it simply does not have the same buzz as "microservices" or "service mesh" that have been popular in recent years. A well-known example is Eclipse, which uses a plugin model to provide various feature enhancements, while its core is only responsible for providing a robust IDE environment.
Cost Orientation
Conventions
As mentioned at the beginning, architects should keep the concept of cost in mind. So let us examine the cost comparison for the Microkernel pattern.
We use the number of $ symbols to represent cost levels โ more $ symbols indicate higher overall cost, and fewer indicate lower cost.
Beyond cost expenditure, as architects, we also need to consider many other factors, such as development agility, deployment cost, testing cost (yes, deployment and testing are important too โ they also cost money!), performance, scalability (future-proofing), and complexity. We will also provide an assessment of the entire development process.
Comparison
Compared to monolithic architecture, the Microkernel emphasizes the independence of each module, the uniformity of interfaces, and the immutability of the core. This ensures overall system availability while providing a flexible, pluggable component system. Since the final output of the entire system is a complete monolith, deployment and testing are also relatively straightforward.
However, precisely because the final output is still a large monolithic system, performance and scalability issues are inevitable.
The details are shown in the table below.
| Pattern | Agility | Deployment | Testing | Performance | Scalability | Complexity | Cost |
|---|---|---|---|---|---|---|---|
| Monolithic | โ | โ | โ | โ | โ | โ | $ |
| Microkernel | โ | โ | โ | โ | โ | โ | $$ |
Summary
The concept of the Microkernel has been around for a long time. Its influence can be seen in both system kernel design and distributed systems. From "small-scale" examples like HarmonyOS's microkernel architecture, to "large-scale" ones like service mesh architectures such as Istio or Linkerd, the concept of the Microkernel is evident throughout.
The Microkernel pattern avoids the chaotic internal structure of the monolithic pattern. By decoupling the core system from the component system, it ensures development fluency and feature independence, thereby providing development and testing flexibility while keeping costs nearly unchanged. However, due to its inherently "complete" output form, it still faces issues with performance and limited scalability.
In the following chapters, I will introduce other mainstream distributed service architectures. Stay tuned and feel free to leave comments!