Having addressed the topic of databases, I would like to delve into another aspect that I consider worth describing—the choice of service architecture. I considered those with which I have production experience:
Monolith,
Modular Monolith,
Microservices.
Each of these has its pros and cons—undoubtedly, they have been the subject of countless articles, analyses, comparisons, etc. From my side, I will present a table in which I analyze and compare the advantages and disadvantages of each discussed architecture.
Criterion | Monolith | Modular Monolith | Microservices |
Maintainability | Easier to maintain for small teams when there are no issues with task overlap or deployment. It can become problematic as the team expands. | Similar to a monolith, the modular structure facilitates code management, but introducing changes for more complex teams can be problematic. | More challenging to manage for smaller teams—it requires managing multiple services and communication between them. However, as the team expands, it is easier to organize work. |
Security | Easier to secure because everything operates in one place. | Similar to a monolith, everything operates in one place. Consider an additional layer for communication between modules. | More complex—security between services must be maintained. |
Legal Compliance | Centralized data simplifies audits and compliance. Harder to restrict data access for individual team members (data minimization principle). | Data can still be centralized, and access can be restricted within individual modules. | Data can be distributed across different services, making it easy to restrict individual data access. |
Agility | Limited—every change requires deploying the entire application. | Modules can be developed independently, but ultimately deploying a module requires deploying the entire application, similar to a monolith. | Individual services can be deployed and scaled independently. |
A Few Words of Commentary
From the perspective of an average "code writer," coding is practically the same in all three architectures. Conditional statements, loops, and database entities look the same. The most significant differences I noticed were in scaling and database management.
Microservices
Beyond the situations mentioned above, the advantage of microservices is evident in organizing work for larger teams—they can work in parallel without interfering with each other, and deployments are independent. Additionally, when a part of the offered services gains popularity, it can be scaled both horizontally and vertically, depending on requirements, without affecting other services. This flexibility has made microservices very trendy—many companies are trying to transition from monoliths to microservices because they want to scale, facilitate team work, and clearly delineate responsibilities. This also enforces an appropriate approach to coding, such as separating the database—we only have access to the absolutely necessary minimum of entities. If we need something that belongs to another microservice's domain, we must make an appropriate call (e.g., via API) and obtain the resource that way.
From a data protection perspective, it is also easier to manage access. According to the principle of minimization, a developer should have access to data that directly belongs to their service. However, each additional database for a given microservice requires a separate approach and security measures, which is a significant challenge.
In general, however, I believe that microservices introduce a lot of order (which I like!) and discipline, but unfortunately, they also bring a whole set of challenges related to network handling, contract stability, and API versioning. Over time, as the microservices structure grows, these types of problems will increase.
Monolith
Monolithic architecture is a classic approach to building systems, especially effective for smaller teams. Personally, I don't think hordes of people will work on this project :) Maintaining such an application will be relatively simple, and tasks do not overlap significantly.
In terms of security, a monolith has a significant advantage—all components operate in one place, making it easier to secure the application as a whole. The lack of a need to secure communication between multiple services simplifies authentication and authorization processes. On the other hand, centralizing the system means that potential security vulnerabilities can have a greater impact on the entire application.
A monolith is also advantageous from a regulatory compliance perspective. Since data is stored in one place, audits and meeting legal requirements are easier to achieve. However, restricting access to specific data sets can be challenging—it's difficult to precisely control which modules and users should have access to certain information, which can be a challenge in the context of data minimization principles.
The biggest drawback of a monolith is limited agility. Every change in the system requires redeploying the entire application, slowing down development processes and potentially causing issues with independently developing individual functionalities. As the number of teams and, most importantly, system functions grows, this lack of flexibility becomes increasingly noticeable, often prompting organizations to transition to a modular monolith or microservices.
A pure monolith allows for a more relaxed approach to architecture, package organization, etc. This makes sense when the team can maintain discipline. Unfortunately, from experience, I know that this is not easy. It only takes one new team member with a slightly different approach to building the domain, and with one innocent data connection without using the appropriate facade or dedicated service, chaos begins to be introduced, which will be difficult to reverse.
Modular Monolith
A modular monolith is an architecture that combines the advantages of monoliths and microservices, offering greater flexibility and the ability to independently develop individual modules.
In terms of maintainability, a modular monolith facilitates code management because modules can be developed independently. However, for more complex teams, this can lead to task overlap and coordination issues. Introducing changes can be problematic because ultimately deploying a module requires deploying the entire application.
Regarding security, a modular monolith operates similarly to a monolith because everything operates in one place. However, an additional security layer for communication between modules should be considered to ensure secure data exchange.
In terms of regulatory compliance, a modular monolith offers flexibility in data management. Data can be centralized or restricted within individual modules, making access control and compliance with regulations easier. Access to data for individual team members can also be restricted, aligning with the principle of data minimization.
As for agility, a modular monolith offers greater flexibility than a classic monolith because modules can be developed independently. However, ultimately deploying a module requires deploying the entire application, limiting the ability to independently deploy and scale.
Summary
Choosing between these three styles was not easy. Ultimately, I did not choose microservices due to the additional network complexity. I know that honing these skills is useful, but not everything at once :) I want to avoid introducing too many new elements at the start, and I plan to gradually introduce microservices, starting with separating one or two modules, so naturally, the time will come for that. Microservices will also be an opportunity to learn and write them in other programming languages such as Kotlin or Scala, maybe even outside the JVM family :)
The choice between a monolith and a modular monolith seems obvious. A modular monolith has an advantage over a 'regular' monolith in better code organization. It enforces the use of separate modules and placing code dedicated to specific functionalities in designated segments. I know that errors leading to chaos can also occur here, but it is easier to manage within a single module than in an entire monolithic application. This ensures better organization and ease of implementing new functionalities and modifying existing solutions. Gathering code in one place, but in separate modules, makes it easier to manage the overall functionality and complexity of the system.
By choosing a modular monolith, I intend to create an application that will be easier to maintain and manage, while also having the ability to gradually introduce microservices as the project develops. This will allow me to effectively manage system complexity and flexibly adapt to changing requirements and learning directions.
What's Next?
Having decided on a modular monolith, the natural choice is to use the Spring Modulith framework, which allows defining modules as logical components of the application, and the Application Module Tester can test whether dependencies between modules comply with the architecture. It supports event-driven communication, so there is no need to call appropriate methods from other modules. Finally, it facilitates testing by running only individual modules.
Of course, I realize that almost the same effects can be achieved without using this additional framework. ArchTests can be used to test dependencies between packages to enforce the appropriate order. Spring Events were added to Spring in version 4.2, which was in 2015 (although, of course, they have been further developed over time, and Spring Modulith introduced retry capabilities and better integration with monitoring), but finaly it is not nothing new. Nevertheless, since it is easier to use these tools in Spring Modulith, why not try it? :)
The first task will be the basic implementation of a user. The beginning is uncomplicated, but it will allow me to calmly introduce Spring Modulith, touch on a few modules, which will help tame events and potential testing. I hope the next entry will strictly concern this implementation :) I can't wait.