Mesoservice pattern or ‘pluralitas non est ponenda sine neccesitate’
Nowadays, microservice pattern [1] is a hot topic, which has a tremendous influence in all sort of software engineering projects. In projects that deal with state (some sort of store — data store, event store, etc…), invariably, microservice pattern leads to a multitude of fine-grained services following the database per service pattern [2]. Once these fine-grained services are established, they pose the challenging of implementing queries that need to retrieve distributed data, which in turn leads to the Command Query Responsibility Segregation (CQRS) pattern [3]. Additionally, maintaining data consistency between such fine-grained services is another challenge since distributed transactions is not available generally, which in turn leads to the Saga pattern [4] (in which the “distributed transaction” is implemented using a set of local transactions for doing and compensating).
Sparse voices [5,6,7] have been heard against a strictly view of the microservice pattern. Such voices are not in the defense of the monolithic pattern (anti-microservice pattern), on the other hand, they highlight drawbacks of the microservice pattern and the consequences of its application even though they do not propose an alternative pattern.
Towards an alternative pattern, an initial step is the recognition that although levels of analysis are not necessarily mutually exclusive, there are three general levels into which may fall: micro-level, meso-level or middle-range, and macro-level [8]. A more general step is to consider the Ockham’s Razor:
pluralitas non est ponenda sine neccesitate
which translates as ‘entities should not be multiplied unnecessarily’. These two steps lead to the term mesoservice pattern, in which functional related microservices are grouped into a larger service, a mesoservice.
Next sections discuss major viewpoints that lead to the mesoservice pattern, and, finally, the mesoservice pattern is briefly presented.
The philosophical viewpoint
A naive application of the Ockham’s Razor leads to the attempting to minimize the number of entities of a given application (services), which clearly contributes to the overall reliability. Consequently, too many fine-grained services (microservices) is not a good option.
The optimization viewpoint
Taking into account modular software, [10] argued that there is no magical rule to divide a huge application into modules (services in the present context). Nevertheless, it suggested the following graph to show the influence of the number of modules in the total cost of a software.
Cost x Number of Modules [10]
In accordance with [5], it is reasonable that the division of one application into too many fine-grained services (according to the microservice pattern) has a huge impact on the total cost. However, instead of a positive impact — as preconized by the microservice pattern — it could be a negative impact if the value on the abscissa (x-axis) is not located in the minimal-cost region.
Indeed, the graph is a U-curve optimization, in which is not mandatory to find the perfect optimum to capture most of the value. However, it is clear the extreme case in which each module has 10–100 LOC (following microservice pattern) has a significant higher cost. One can argue that with automation the integration cost is linear, nonetheless, it is still different of zero (recall Fallacy 6. There is one administrator).
As the division (sometimes called partition) of an application can be used to reduce total cost of the application, it emerges the necessity to define criteria to that division. The most accepted criteria are: (1) communicational cohesion — when parts of a module are grouped because they operate on the same data (sometimes called partition by nouns or resources) and (2) functional cohesion — when parts of a module are grouped because they all contribute to a single well-defined task of the module (sometimes called partition by verb or use case).
The fallacies of distributed computing viewpoint
In spite of cloud services (better and cheaper network services), the fallacies of distributed computing are inherent to the microservice pattern.
[7] highlighted that the complexities of communication are often ignored since in a microservice pattern the number of services that collaborate for a given business function can be high and network calls can fail (Fallacy 1. The network is reliable). Moreover, as the number of services is high the latency of the network (Fallacy 2. Latency is zero) as well as the serialization and deserialization of exchanged messages (Fallacy 7. Transport cost is zero) can decrease overall performance, in particular, at peak utilization.
Even more, [7] pointed out that the complexities of state are often ignored since the challenge of understanding the shape of data, and how it evolves, is handled by the application usually. Besides the fine-grained services add a lot of complexity in the effort to maintain data consistency, see Saga pattern [4], which converges to the Fallacy 6. There is one administrator.
In 2014, [5] discussed the microservice pattern relating it to the term nanoservice, which is an antipattern where a service is too fine-grained. A nanoservice (microservice) is a service whose overhead (communications, maintenance, and so on — the fallacies of distributed computing) outweighs its utility. Interestingly, for practical purposes, [5] shared that a microservice was a simple application with 10 to 100 LOC. Furthermore, [5] pointed out two ways to solve the nanoservice problem: (1.) to group related nanoservices into a larger service and (2.) to redistribute its functionality among other services. Finally, [5] pointed out to weight the advantage of using a service versus building the same functionality as a library that can be used within services.
Finally, [6] concluded:
due to the given burdens of cost and organizational maturity, microservices will likely never reach the Adopt phase
Mesoservice pattern
Context
A server-side enterprise application must support a variety of different clients or 3rd parties through an API. It integrates with other applications via either SOAP services, REST services or message brokers. The application handles requests (HTTP requests and messages) by executing business logic; accessing a store; exchanging messages with other systems; and returning a JSON response. There are logical components corresponding to different functional areas of the application.
Problem
While the monolithic pattern represents a risk due the size of the enterprise application, the microservice pattern also represents a risk due the number of modules to be “managed”.
Forces
Contract-first
- No “embedded” elements in the REST APIs and collections are always paginated
- It does not use Hypermedia APIs
- Contract-first approach using Swagger for REST/Message microservices
- Contract-first approach using Swagger for REST/Message mesoservices (the Swagger of a mesoservice is the composition of the Swagger of its microservices)
- Contract-first approach using GraphQL schema for the GraphQL service (also defined by a Swagger), describing the business functionality of the application
- API Schema checking and code generation based on the contracts (Swagger and GraphQL schema)
API Gateway
- API Gateway friendly since all APIs have a Swagger to be exposed through an API Gateway
- Authentication (client application and user) provided by the API Gateway through OAuth2 (authorization code — former implicit method — and password method)
- Coarse-grained authorization provided by the API Gateway
- Fine-grained authorization provided by the mesoservices based on a JWT generated by the API Gateway and propagated between services.
- An API Gateway that provides functionality for APIs based on websockets is preferred (e.g., WSO2 API Manager) since they enable push to the clients using this kind of technology (MQTT over websocket was used with success)
CQRS pattern based on GraphQL since queries are separated from the mutations
- Queries that need to retrieve data owned by multiple mesoservices are easily implemented in the GraphQL service
- A given mutation is prescribed to be owned by sole one given mesoservice (reducing the need for the application of the saga pattern)
GraphQL enables optimization of the data loading based on client needs
State/Store
- Mesoservices applies the database per service pattern for a given set of “tables” that make business sense
- It is commonly based on ORM technologies (e.g., JPA) but not restricted
- It is common to use a shared library with common domains tables, e.g., countries, states, etc…
- In a classical relational data store, a lock in the main entity (SELECT … FOR UPDATE NOWAIT) is recommended for all mutations. This technique avoids expensive lock waits, consequently, it increases the scalability of the overall solution
Commonly Java-based, but Java is not mandatory (polyglot development)
Libraries are defined for fine-grained reusable behavior (e.g., common domain tables retrieval, common business calculations, etc…)
Containerization
- Each mesoservice is defined as a container application (usually a Docker container running a OpenJDK JVM over Alpine operating system configured through confd)
Less memory requirements
- Java services based on SpringBoot have a huge minimal memory footprint (1GB, [11,12]), therefore, mesoservices pattern contributes to reduce the overall infrastructure needed since the pattern reduces the number of services
Intercommunication
- Intercommunication between GraphQL service and Mesoservices are REST/HTTP based for queries (using a circuit breaker, usually Hystrix) and REST/message-based for mutations
- Intercommunication between microservices in a given mesoservice are based on memory calls
- Intercommunication between mesoservices can be REST/message based and REST/HTTP based (using a circuit breaker, usually Hystrix). The former is the preferred
- REST/message based applies a publish/subscribe message exchange pattern. It uses REST over JMS 1.0 subscription plus database lock synchronization, Apache Kafka groupid or JMS 2.0 shared subscription (createSharedConsumer/createSharedDurableConsumer) in order to guarantee that only one consumer process a given message.
Solution
Analyze the application striving for communicational and functional cohesion, afterwards, declare a set of mesoservices (each one focused on a subset of the data model) of the application and describe them using Swagger/GraphQL schema. Define an architecture that structures the application as the set of mesoservices exposing through controllers the Domain-Driven Design implementations. Mesoservices communicate using either synchronous protocols such as HTTP/REST or asynchronous protocols such as AMQP/MQTT (always using a circuit breaker, usually Hystrix). Services can be developed and deployed independently of one another. Each service has its own database in order to be decoupled from other services (shared libraries are only allowed for reading). Each mesoservice owns its transactions, and cross mesoservice data consistency is maintained using the saga pattern.
Resulting Context
Benefits
- Functional Alignment
- Easier Integration
- Lower Development and Maintenance costs
- Reduced infrastructure needs
Drawbacks
- It is hard to define the granularity of the mesoservices (it requires business knowledge)
- Contract-first put emphasis on the definition of the contracts
Conclusion
The mesoservice pattern was tested with success in two projects (more than 10000 hours each one). As the first attempting for the definition of the mesoservice pattern, there is room for improvements as well as more discussion. However, the main goal of the present text is to promote discussion around the topic.
Moreover, applying again the Ockham’s Razor:
Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away. Antoine de Saint-Exupéry
Two last comments: (1.) there is room for improvement in the GraphQL service applying modularization (see [13] for an example) and (2.) one can argue that cloud services enable microservices architectures [14] but it is still important to reflect carefully on how the fallacies of the distributed computing applies for each case.
References
[1] Pattern: Microservice Architecture
[2] Pattern: Database per service
[3] Pattern: Command Query Responsibility Segregation (CQRS)
[4] Pattern: Saga
[5] Services, Microservices, Nanoservices — oh my!
[6] Microservices to Not Reach Adopt Ring in ThoughtWorks Technology Radar
[7] The Death of Microservice Madness in 2018
[8] Blalock, Hubert M (1979), Social Statistics, New York: McGraw-Hill
[9] Fallacies of Distributed Computing Explained
[10] Pressman, Roger S (2005), Software Engineering: A Practitioner’s Approach, 6/e, New York: McGraw-Hill
[11] Microservices in Java — A Second Look
[12] SpringBoot Memory Performance
[13] GrAMPS
[14] How I decided to use Serverless/Nanoservices Architecture with AWS to make CAPI