Monolith to Microservices Part 3: Our Solution

This post is part of a 3-part series on Expanse’s transition to a Microservices [1] Architecture built on Java and Spring Boot. In this series, we seek to share the issues we faced with the monolithic system, why we think the Spring Boot-based services will address them, and how we are affecting this change, with other technologists who love to design or think about systems.

This series is best for readers with some working knowledge about building and maintaining web applications, relational databases, SQL, and common interfaces between web applications and databases (e.g. ORMs).

In the last installments 1 and 2 of this series I talked a bit about why we made the decision to move to a microservices architecture, our requirements for our new architecture, and how we affected this change. In this last post, I will discuss the impact of our transition so far.

Putting everything together, in our transition we broke our monolithic Django application into Java-based services with shared data models and persistence mappings. We used an API gateway to incrementally cutover rewritten routes while continuing to present a single logical interface to customers. Requiring users to interface with multiple endpoints would be untenable; from their perspective, it had to look like nothing was happening. The user/authorization functionality of the existing Django application was broken out into a standalone service in order to share it with the new services. And finally, we added custom middleware to the Kong API gateway to reach out to the user/authorization service to authorize requests and augment them with the relevant user information so that downstream services could use it to properly handle queries.

In essence, we implemented the user/authorization service as a stripped down version of the current Django application. Just because you move to microservices does not mean that you have to reimplement everything.

The final implementation

Before rewriting pieces of the Django application, we performed an audit to bucket existing routes into services using the guidelines provided in the Microservices Architecture section of post 2. Throughout the rewrite process, we prioritized routes to rewrite based on the ones that had given us the most issues in the past. As of this publishing, this rewrite is ongoing, and it’s completely possible that some of the routes will never get rewritten into a Java application. This is fine with us. Some of these routes do not rely on a data model shared with our data ingest pipelines and have not given us any issues in Python. The beauty of our new system is that the API gateway frees us to implement our routes however we want.

The API gateway exposes a Prometheus scrape endpoint [2] for monitoring, hooks into our centralized logging stack [3], and attaches a distributed trace ID to all requests before forwarding them upstream. As discussed in part one, the ability to introspect into the handling of requests in a distributed system and to understand the health of different components is critical. These additional tools were necessary to achieve the same level of visibility we had with our monolith.

To enable developers to quickly create new services and to simultaneously adhere to high standards around style, testing, and development experience, we’ve invested in a company-wide shared POM file and shared maven archetypes for generating Spring Boot applications which include all of the necessary tooling: auto-generated Swagger documentation, private scrape endpoints for Prometheus, Liquibase for migrations, integration into CI/CD, etc. Upstream services will include the distributed trace ID in the logs statements related to each request.


In this series, I’ve discussed our efforts related to migrating our monolithic serving layer, based in Django and Python, to a microservices architecture, based in Spring Boot and Java. This change is made possible by using an API gateway and segmenting our user/authorization functionality into a standalone service. We believe that this shift will allow for greater engineering velocity as we continue to scale up our teams and reduce the risk of introducing bugs in the system when making changes, and prevent the need for a major system re-architecture in the future.


[1] I personally prefer the term services to microservices because microservices implies that they must be small. That said, I’ll use the microservices vernacular because it’s what most people are familiar with hearing and has better SEO ¯\_(ツ)_/¯