This month, my colleagues and I open-sourced an internal architecture governance platform: ArchGuard, and we have carried out a series of legacy system migrations:
- From Maven to Gradle. The reason is flexible custom tasks, as well as built-in incremental builds.
- Dependency library updates.
- The system goes from microservices to monoliths.
- Build Specifications and Corresponding Specification Instrumentation
- Continuous delivery. Combined with a series of DevOps open-source infrastructures such as GitHub Action and Docker Hub, it can be fully automated.
Among them, the most interesting story is: from microservices to monolithic architecture. Because it is an anti-mainstream form or an anti-mainstream technical architecture.
Challenges of legacy monolith microservice systems
After we went through a series of internal meetings, the decision was made to open-source ArchGuard. Then, looking at the codebase, we found a series of challenges:
- Too many services/modules. This system, which has been developed in-house for many years, consists of multiple microservices + a code base,
- Knowledge is lacking. There was not much development documentation left before, and I didn’t understand the series of technical decisions made at that time, which needed to be drawn from the history of Git.
- Complex deployment architecture. The same tool is relatively complex compared to the deployment method of Jenkins/Sonarqube.
- Not necessarily a reasonable division of services. We need to deploy a series of services, but only the Scanner (Arch Scanner) needs such features as elastic scaling.
So, let’s rethink what the granularity of a reasonable backend service (microservice) should be? So, with reference to what microservices have been summarized in the past? And how many microservices make more sense? A previous conclusion, similar to:
- The number of microservices does not exceed the number of developers.
- Satisfy Conway’s Law. Microservices are aligned with development teams.
- The principle of two pizza teams – the number of people in the development team is maintained at 3 \~ 12 people. A microservice is maintained by only one development team.
- Low coupling, high cohesiveness. A single service relies on fewer other services. For example, two services call each other, and the coupling degree is relatively high, so they can be considered as one service.
- The benefits outweigh the costs. Does the cost of creating a service outweigh the benefits of being a separate service?
- You don’t necessarily need microservices. Consider adopting a DDD (Domain Driven Design) layered architecture to facilitate future splitting into microservices.
- In this scenario, the above series of rules are almost violated. So, I went back to 6 above and adopted the layered architecture pattern of DDD. Each resource/aggregate/service is managed under its own package (except common):
├── Application.kt ├── clazz ├── code ├── common ├── config ├── evaluation_bak ├── evolution ├── method ├── metrics ├── module ├── packages ├── qualitygate ├── report ├── report_bak ├── scanner ├── scanner2 └── system_info
Since it is a merged code, the code is in _bakaddition scanner2seemingly duplicated code or the code in the migration.
Why is a monolith better for the moment?
Going back many years ago, Martin Fowler wrote the piece ” Monolithic First ” to tell people that you shouldn’t adopt microservices when your team’s microservice capabilities and technology are not mature enough. The scene here is not the same as the one above. For the final form of the system, the singleton is not necessarily suitable for this system, but for us at present, the singleton is the most suitable. Reasons such as:
- The monolithic deployment architecture determines the application architecture. Use Docker, although Saas is also friendlier. However, as a fledgling open-source project, there is no funding to support a SaaS service of this scale.
- The end-user is the developer. The user of the software itself may become the developer, so if it can be started once, it should be started once.
- Developer experience takes precedence. Open source and developer-oriented make ArchGuard a system that prioritizes developer experience. If a developer involved in the ArchGuard project has to switch between multiple projects, the experience is very poor. In the open-source community, it has always been a single priority, such as Gradle, Spring, etc.
- First-time deployment speed.
- setup speed.
In general, as an open-source application/tool, the software engineering model is limited by its collaborative model. Therefore, the conventional software development architecture does not necessarily apply, and we need some better models.
So, do we have other options?
Is our target architecture a monolith?
- In a sense, for now, it is. However, if not properly maintained, it can quickly devolve into a blob of mud architecture. To recap, in a multi-warehouse/multi-module microservice system, the main physical differences between it and a monolithic system are:
- Microservices use inter-process calls, while monoliths use intra-process calls.
- Microservices eventually have multiple product packages, while there is only one single or plug-in area.
- Therefore, as long as we use a similar form to build a monolithic application, it can become a microservice architecture in terms of deployment form. Simply put, it is:
- Within the codebase, calls between packages (packages, services) use HTTP calls instead of function calls.
- Split the codebase at build time, generate multiple service artifacts, and deploy them with custom build scripts.
As a result, the system is placed in a critical state. So that people can make different choices according to their needs. For example, in the case of SaaS, this can become the form of microservices, and in the case of monolithic deployment, it can become a monolithic state. The only trouble is that developers need to have enough knowledge of the build system and design sufficient automated testing facilities.
How to migrate monolith?
Next, we started merging multiple repositories, some of which
Merge that preserves the history of commits. It is mainly combined with git-filer-repo to filter and select paths.
A complete set of build configurations. Unify Application. properties etc.
Use the same dependency version. Due to different ages, the selected dependency versions are also different, and you need to try to unify them before you can merge the code.
resolve conflicts. Because only the contents of the src directory. It is necessary to reset the package name if there is a dispute with it. Similar problems, there are Application duplication, Bean conflict, and Service conflict.
As far as the migration process is concerned, it’s not complicated, it’s just time-consuming.
Are there other options?
In a similar scenario, if a developer has multiple microservices and single-machine deployment is not a concern, Monorepo is a better choice to put the code of all microservice projects in one repository.
After all, Google can put all the code repositories together, what can’t we do. Of course, the technical principles Google uses are different. However, it can provide a strong enough reason.
Looking back, would a monolith be a more appropriate choice for a small team? What about such a big team?