Change and structured approach
If the monolith has been growing organically, it's not likely that all its modules will be cleanly structured. Some structures may exist, but maybe they're not the correct ones for our desired microservices division.
To adapt the service, we will need to make some internal changes. These internal changes could be done iteratively until the service can be cleanly divided.
These three approaches can be combined to generate full migration. The effort involved in each is not the same, as an easily divisible service will be able to make the move faster than a replacement of badly-documented legacy code.
In this phase of the project, the objective is to have a clear roadmap, that should analyze the following elements:
- An ordered plan of what microservices will be available first, taking into account how to deal with dependencies.
- An idea of what the biggest pain points are, and whether working on them is a priority. Pain points are the elements that are worked with frequently and the current way of dealing with the monolith makes them difficult.
- What are the difficult points and the cans of worms? It's likely that there'll be some. Acknowledge that they exist and minimize their impact on other services. Note that they may be the same as the pain points, or not. The difficult points may be old systems that are very stable.
- A couple of quick wins that will keep the momentum of the project going. Show the advantages to your teams and stakeholders quickly! This will also allow everyone to understand the new mode of operation you want to move to and start working that way.
- An idea of the training that teams will require and what the new elements are that you want to introduce. Also, whether there are any skills lacking in your team – it's possible that you may plan to hire.
- Any team changes and ownership of the new services. It's important to consider feedback from the teams, so they can express their concerns over any oversights during the creation of the plan.
For our specific example, the resulting plan will be as follows:
- As a prerequisite, a load balancer will need to be in front of the operation. This will be responsible for channeling requests to the proper microservice. Then, changing the configuration of this element, we will be able to route the requests toward the old monolith or any new microservice.
- After that, the static files will be served through their own independent service, which is an easy change. A static web server is enough, though it will be deployed as an independent microservice. This project will help in understanding the move to Docker.
- The code for authentication will be replicated in a new service. It will use a RESTful API to log in and generate a session, and to log out. The service will be responsible for checking whether a user exists or not, as well as adding them and removing them:
- The first idea was to check each session retrieved against the service, but, given that checking a session is a very common operation, we decided to generate a package, shared across the externally faced microservices, which will allow checking to see whether a session has been generated with our own service. This will be achieved by signing the session cryptographically and sharing the secret across our services. This module is expected not to change often, as it's a dependency for all the microservices. This makes the session one that does not need to be stored.
- The Users Backend needs to be able to allow authentication using OAuth 2.0 schema, which will allow other external services, not based on web browsers, to authenticate and operate, for example, a mobile app.
- The Thoughts Backend will also be replicated as a RESTful API. This backend is quite simple at the moment, and it will include the search functionality.
- After both backends are available, the current monolith will be changed, from calling the database directly, to use the RESTful APIs of the backends. After this is successfully done, the old deployment will be replaced with a Docker build and added to the load balancer.
- The new API will be added externally to the load balancer and promoted as externally accessible. The company making the mobile app will then start integrating their clients.
Our new architecture schema is as follows:
Note that the HTML frontend will use the same APIs that are available externally. This will validate that the calls are useful, as we will use them first for our own client.
This action plan can have measurable times and a schedule. Some technology options can be taken as well—in our case, the following:
- Each of the microservices will be deployed in its own Docker container (https://www.docker.com/). We will set up a Kubernetes cluster to orchestrate the different services.
- We decided to make the new backend services in Flask (https://palletsprojects.com/p/flask/), using Flask-RESTPlus (https://flask-restplus.readthedocs.io/en/stable/) to generate a well-documented RESTful app and connect to the existing database with SQLAlchemy (https://www.sqlalchemy.org/). These tools are Python, but take a lighter approach than Django.
- The backend services will be served using the uWSGI server (https://uwsgi-docs.readthedocs.io/en/latest/).
- The static files will be served using NGINX (https://www.nginx.com/).
- NGINX will also be used as a load balancer to control the inputs.
- HTML frontend will continue to use Django (https://www.djangoproject.com/).
The teams are OK with proceeding with these tech stacks, and are looking forward to learning some new tricks!