While implementing Microservices architecture, the “granularity” of a service has always been the subject of more than a few debates in the industry. Analysts, developers and solution architects still ponder over defining the most apt size of a service/component (The term “Service” and “Component” are used interchangeably in the discussion that follows). Such discussion usually ends up with two principal adversaries:
- Single-level components
- Two-level components
Single-level, “Atomic” components: An “Atomic” component consists of a single blob of code, together with a set of defined interfaces (inputs and outputs). In the typical case, the component has a few (two or three) inputs and outputs. The Service-code of each Atomic component typically runs in a separate process. Figure 1 shows an Atomic component.
Two-level, “Composite” components: A composite-service consists of a single ‘outer’ service, with a set of interfaces. This outer service further contains one or more ‘inner’ components that are used in the implementation of the main, outer component. The Composite-service runs in a separate process by default, while each of the inner components run in a separate thread of the Composite component process. Proponents of this approach point to the fact that by componentizing the implementation of the composite component, one has greater flexibility and more opportunities to reuse implementation artifacts within Microservice implementations. Figure 2 illustrates a Composite component.
Atomic Microservices are as simple as they get. It’s just a single blob of code, in a programming language of your choice. Depending on the underlying Microservices infrastructure, you may have to implement a threading model yourself, or you may be able to leverage the threading model of the underlying Microservices framework (for instance, Sessions provide a single-threaded context in the case of a JMS-based Microservices platform). Overall, Atomic Microservices offer a relatively low level of complexity for development, being as it were a single logical module.
On the contrary, Composite Microservices have an almost romantic appeal for many developers, who are enchanted with the concept of “reusing” multiple smaller inner-components to implement a larger single component. Unfortunately, although this approach is good in theory it has several drawbacks in practice. For starters, the execution model is complicated, since the underlying framework has to be able to identify the separate threaded contexts for the Inner components that comprise the single composite component. This carries significant performance overhead and complicates the platform framework. For reference, in the early 2000’s, the BPEL (Business Process Execution Language) in vogue followed this approach, which proved to be very heavyweight in practice. Another issue with composite components is that there is no simple model for deployment; since composite components are more difficult to auto-deploy as agents across the network, unlike Atomic components.
Provided that the services run as separate processes, in our experience the Atomic components represent a better choice for Microservice-project implementations.