Architecting Microservices as Tenants on a Monolith

Kaustubh Chandrabhan

10 min(s) read
Graphics
You must be this tall to use Microservices - Martin Fowler

Microservices architectural pattern has gained massive prominence and widespread adoption over the last decade or so due to the many advantages it offers. It enables decentralized structures at the people level as well as at the technical level and improves the ability of systems to effectively respond to the demands of growth, both of features and scale. The microservices pattern definitely offers higher elasticity to the system it is powering to respond to changes in a dynamic environment.

On the other hand, a monolithic system is akin to a centralized structure where the complexity keeps increasing by the day. The addition of more lines of codes, new modules, new packages, new dependencies, etc. leads to a system where the elasticity to change is decreasing. Enhancements tends to become more cumbersome and expensive and it is easier for more crufts to surface in the codebase. The burden of bureaucracy also tends to be higher on the monolith for obvious reasons.

developer_difficulty.png

Microservices are expensive

More recently, there has been a lot of discussion around the cost of microservices. The in-memory method calls in a monolith between modules becomes the more expensive network calls in microservices. Separate deployment makes it easy to run into a fleet of hundreds of servers in organizations running several microservices in production. Then there are overheads introduced from more complex monitoring, devops, etc.

While the ability to run and scale smaller independent systems introduces the leverage of agility, the higher infrastructural and operational overheads renders the choice of microservices impractical in a vast majority of use cases.

During the proliferation phase, the microservices pattern seemed like the silver bullet and organizations of all sizes were adopting it overlooking the overheads. In the current times, it seems as if the reverse is happening. It is becoming fashionable to suggest that microservices is not a suitable solution for the problem at hand and that monolith is ‘majestic’ or start with a monolith.


The advantages of microservices, albeit the cost

The microservices pattern can revolutionize the way a product is developed, deployed and enhanced even for ecosystems that might not have hit the threshold of scale. Keeping aside the cost and concerns around the operational overhead, microservices as a pattern stands to quickly outshine a monolithic approach by remaining more adaptable to new requirements. The enablement of parallel development by establishing well enforced boundaries greatly adds to the agility of the development team.

The adage is to start with a monolith and branch out to microservices, using the approach known as the Strangler Fig pattern.


Don’t start with a monolith if the goal is microservices

To paraphrase Simon Tilkov

I’m firmly convinced that starting with a monolith is usually exactly the wrong thing to do. Starting to build a new system is exactly the time when you should be thinking about carving it up into pieces. I strongly disagree with the idea that you can postpone this.

Building a greenfield monolith that is expected to grow out into a Strangler Fig and expecting to cut it off when the time is right will add stress to the design of the monolith in the first place.

Simon Tilkov continues

You might be tempted to assume there are a number of nicely separated microservices hiding in your monolith, just waiting to be extracted. In reality, though, it’s extremely hard to avoid creating lots of connections, planned and unplanned. In fact the whole point of the microservices approach is to make it hard to create something like this.

A new approach for microservices, sans the overheads

Zango is a new Python web application development framework, implemented on top of Django, that allows leveraging the key advantages of the micorservices pattern without incurring any additional operational overheads.

Under the hood, Zango is a monolith that allows hosting multiple independently deployable applications or microservices on top of it. Each application is logically isolated from the other sibling apps at the data layer, the logic layer, the interface layer as well as in deployment, ensuring complete microservices type autonomy.

One of the key goals of Zango's design is to make sure that deployment of one app does not require downtime for any other sibling app or the underlying monolith. We do so by creating logically separated areas, enforcing rules and policies at the framework level and leveraging the plugin architectural pattern for executing the app's logics.

In addition, to meet the requirement of differential scaling, Zango also allows easy branching off of one or more services to deployment on a separate cluster without needing any code level refactoring.

Zango has been open sourced recently, but it has been in production to support hundreds of applications in global companies.

Let’s understand the architecture in detail.

Zango comes with Postgres SQL database and the organization of the datastore is central to the framework. We utilize the multi-tenancy feature of Postgres to logically segregate the storage for each microservice. Each microservice will have a unique schema in the database where all the tables and associated objects for that microservice will be housed. The image below shows the organization at the database level.

db_schema.png

Policies are enforced by the framework to prevent unauthorized access to data. For e.g. application logic implemented in microservice 1 that attempts to access the data for microservice 2 will be restricted by the default policies.

Additionally the common schema in the database will have a table to function as the registry of microservices. In this registry we store the generic details of the microservices, including:

  • Primary ID
  • Name
  • Domain URL
  • DB Schema Name

With the organization of data storage for the microservices in place, we can now look into how Zango handles the codebase, ensuring we meet the key requirements:

  • A lot of microservices can be created on a single monolith deployment
  • Deployment of one microservice must not require any downtime for other microservices
  • Errors in one microservice must not propagate to outside the boundaries of the microservice

To meet these requirements, we leverage the Plugin Architectural pattern. Each microservice is implemented as a plugin. Caching techniques ensure the efficient loading of the plugin codebase and re-loading only when new releases are available for a particular microservice.

Inside the request-response cycle

On receiving an incoming request, Zango first attempts to identify which microservice it is intended for, using the domain url from the request and the matching with the ones stored in the registry. If a matching microservice is found, an attempt to find a matching route for the URI in the microservice’s codebase. The framework enforces certain rules for defining the routes. If a matching route is found, the controller associated with the route is called to generate the response. If the match is not found, 404 is returned.

The framework also sets the path to the particular schema in the database that belongs to the microservice. By doing so, there will be no requirement to explicitly set the schema context in the codebase of the microservice and the developer gets the sense that he is working with a single tenant database only.

The overall architecture involves a lot of other nuances to handle finer details around access control and authorization framework, differential throttling, packages layer to enable a packages ecosystem, etc. which is beyond the scope of this introduction.

We believe that the possibility of implementing a microservices architecture without additional cost burden can bring a lot of advantages to a greenfield web application project. The power of differential scaling on the same deployment as well as the ease with which the microservices can be extracted fully into an independent deployment will significantly strengthen the teams

Zango is available under an open source AGPL license here. It is implemented on top of Django and requires basic Django understanding to start developing applications on it.










@ 2024 Healthlane Technologies pvt. Ltd
Resources
Docs
Blog
Channels