The Lego Monolith — A Monolith Microservice Proof of Concept

Tim Whitney
Slalom Build
Published in
7 min readMar 4, 2016

by Tim Whitney

Overview

Over the last several years, microservice architectures have gotten a lot of press. However, for many organizations a full microservice architecture will present many organizational and technological problems. In order to be “tall enough” to use a full microservices architecture an organization should have the following basic capabilities:

  1. Rapid provisioning of new servers. Ideally cloud native.
  2. Monitoring. Concepts such as correlated tracing, circuit breaking and self-healing services require a high degree of maturity in your monitoring capabilities.
  3. Rapid application deployment. Continuous Deployment. A DevOps culture.
  4. Autoscaling. Ability to scale a single service when load increases.
  5. Governance around service contracts.

Meeting these hurdles may not be feasible for many organizations. The Lego Monolith architecture has many of the benefits of a full-bore microservice architecture without having to meet a long list of prerequisites.

The lego monolith architecture contains several bounded service contexts within a single repository and deployable application.

Lego Monolith Architecture

Many service applications already make use of n-tier architectures to segregate controllers, services, and persistence layers. Here’s a sample diagram to illustrate how this can be done in a standard monolithic application:

n-tier

With a proper dependencies setup (e.g. in Maven or Gradle), this architecture makes it impossible to do something like call a data access object directly from a REST endpoint. It is still possible to call one service from another, or from a service to a data access object that is completely outside of the scope of that service. These service-to-service calls introduce tight coupling between services. This is one of the main problems that microservices and bounded contexts try to solve.

The following diagram demonstrates a single application with two bounded contexts processing a request to create a sale.

Lego Monolith high level architecture diagram

Here’s what’s happening in the diagram:

  1. An external request is received to create a sale.
  2. A request is made from sales-data-impl to product-web to verify that there is enough inventory to fulfill the sale.
  3. The sale is created.
  4. An asynchronous message is published to the queue with the sale information.
  5. The product service is listening for sale created messages and adjusts the product inventory.

Lego Monolith Operational Benefits over traditional monoliths

  1. Decoupled Design. Each service lives in its own bounded context that can only communicate with other services over decoupled interfaces, e.g. REST or SOAP.
  2. Choice of language. Not to the same extent as a true microservice architecture, however each service can be built in any JVM language. e.g. Java, Groovy, Scala, Kotlin, etc.
  3. Choice of persistence. Each service should own the data source. e.g. traditional RDBMS, document DBs, flat files, etc.
  4. Asynchronous. A message queue can be used for asynchronous communication between service contexts.
  5. Ease of transition. The lego monolith is a good starting point and individual services can easily be extracted and run as a separate deployable unit with minimal effort.

Lego Monolith Development Benefits over microservice architectures

  1. Domain evolution. It is hard to organize a larger architecture into a set of microservices with an evolving domain. Having a single codebase and a single repository will allow an easier evolution of your services and domain.
  2. Refactoring. Moving code from one service to another will be far simpler than if you have many separate services in separate repositories.
  3. Context switching. If one or a small number of teams owns the entire application, working on multiple services is simpler and more intuitive. Both from an operational standpoint of switching between repositories and from a mental enegery standpoint.

Implementation

The proof of concept is implemented with Spring Boot with a Gradle build on Java 8. It can be run through Gradle, as a self-container JAR file, or deployed to an application server such as Tomcat.

The following shows the project modules in IntelliJ IDEA:

Gradle Dependency Setup

The root build.gradle file contains subproject dependencies and applies a variety of plugins.

The parent-web build.gradle file contains compile time dependencies to the individual web projects that need to be individually started.

The dependencies within a service are illustrated below for the product modules:

The parent-web module contains the main Spring Boot Application entry point.

Spring Boot Application Configuration

This starts each of the services (sales, support, product) separately. It also includes an ActiveMQ module for purposes of the POC, but should be external in a production environment. The Spring Boot profiles are handled in a more manual way than in a standard Spring Boot project to enable each bounded service to have its own configuration such as the port and datasource configured independently. This is the heart of what makes this POC work. The main takeaways are:

  • Namespace the individual Spring Boot profiles so that configuration doesn’t leak from one service to another since the entire application is running on a single classpath.
  • Don’t use the standard Spring Boot command line argument to pass the active profile, otherwise Spring Boot will automatically set that as the profile.

Each individual service contains its own Spring Boot configuration.

This extends Spring’s AnnotationConfigEmbeddedWebApplicationContext in order to support annotation driven configuration. In the POC, the Spring Boot configuration is consistent across sales, support, and product services, however, there is no reason that this has to be the case. The ProductApplication uses Spring Boot’s Auto Configuration. The @ComponentScan is configured to only look at packages within the context of this service, otherwise all the Spring annotated beans would be autowire candidates since the entire application is running within a single classpath.

Sales Service Example

This is the code behind the create sale diagram above.

Sample REST request:

This invokes the SalesController create method:

The method maps the SaleVO to a Sale object and calls the SalesService create method. It returns the created SaleVO object once the Sale has been created.

The service method first calls the ProductDAO to GET the product from the product service in order to check inventory:

The DAO is using Spring’s RestTemplate to make a GET request.

If the inventory is sufficient then the SalesService calls the SalesDAO to create the Sale.

The SalesDAO uses Spring’s JpaRespository so it’s just an interface that extends JpaRepository.

Once the Sale has been saved to the database (in-memory H2 database in this example), a message is published to the message queue with the information about the Sale.

This is a utility that serializes the object as JSON and publishes it to the given destination.

The ProductService is listening for messages published to this destination using the Spring @JmsListener annotation on the processCompletedSale method.

This method will be invoked asynchronously and relies on eventual consistency to update the product’s inventory.

The source code for the POC is available on GitHub.

Downsides and Caveats

  1. Choice of technology/language. In a true microservice architecture, individual services can be implemented in a variety of languages, e.g. Java, Node, Python, etc. The Spring Boot POC restricts the choices to only languages that run on the JVM and that Spring Boot supports.
  2. Autoscaling. It is not possible for a single service to scale faster than the other constituent services. However, this architecture supports easy extraction of a single service so that it’s able to run as a separate deployable unit.
  3. Governance. Considering that individual services still need to talk to each other, this is still an issue that can’t be abandoned. However, this issue is present in almost all distributed service oriented architecture systems.
  4. Domain model sharing. Models are shared in this proof of concept. However, there’s no reason that each bounded service couldn’t use contract first design and share nothing.
  5. Monitoring. If multiple services are exposed in a public API then the lego architecture does not remove the need for advanced monitoring, such as tracing. However, if most of the APIs are only used internally within the application, then more standard application monitoring can be used.

Conclusion

Due to the high bar that must be crossed to properly implement microservices, adoption of the lego monolith architecture can bring many benefits to a traditional monolithic application architecture such as a true decoupled design, asynchronicity, and a level of freedom of language and persistence that is not possible with other architectures.

About the Author

Tim Whitney is a Practice Area Lead with Slalom’s Cross-Market delivery center in Chicago. He helps clients with architecture solutions and engineering thought leadership. Follow Tim on LinkedIn.

--

--

Tim Whitney
Slalom Build

Tim is a Practice Area Lead with Slalom Build in Chicago. He helps clients with architectural vision, product development, and engineering thought leadership.