A Monolithic system is designed to produce one, self-contained, deliverable.
This deliverable will then be deployed in a whole bunch of different environments to be tested, validated, and finally, go to production and serve its users.
Monoliths are well-suited for a wide spectrum of solutions, especially small applications.
- It's the current status quo on software development, which means everybody is used to think, design and work on systems following this architecture.
- To check the health status of your application is incredibly easy and there are a plethora of tools to help you with that.
- Speaking on tools, on the developer's side, our favorite IDE's are all heavily optimized to work with monoliths: indexing, finding references, refactoring, debugging, etc.
- Last but not least: the deploy is pretty straightforward! Well, in most cases at least.
- Updating the application's technology stack gets harder and harder as the codebase grows.
- A CI/CD (Continuous Integration and Continuous Delivery - aka Continuous Deployment) flow takes longer as the app becomes more complex, harming the feedback cycle.
- Your system is so complete and full of functionalities that testing it takes forever, either manually or automatically.
- The size of the app also implies a bigger team, which implies the biggest problem in project management: communication.
- Last but not least, the whole team's productivity goes down as the project advances:
- The developer has too much code to handle and your IDE becomes a bottleneck.
- The product manager has difficulties in planning releases because everything is so tied.
- You have 600 feature branches that have to be synchronized, even if they are not directly related to each other.
- The last point also implies rather complex merges
- Scaling is hard: remember Pareto's 80/20? Well, if your users use 20% of the functionalities 80% of the time, as you get more users, you can't scale only the 20%, you have to scale 100% of the software in production.
- Domino effect: one bug can take down the entire system at once.
A Microservices Architecture is typically described as an approach to divide your application into small and independent services.
Done right, these small modules may be reusable and shared in multiple systems.
Think about each service as SaaS (Software as a Service) on its own when consumed by other services.
- CI/CD becomes easier, if you need to update service A, service B will keep running.
- Scalability where it needs to be: you can pinpoint the most used services and give them more RAM and CPU, which is also gonna save you some cash.
- A bug crashing service B doesn't take down service A, especially if you have implemented some good caching strategy in service A if it consumes some API in service B.
- You can have small, specialized teams for every service, which diminishes the communication problems.
- It is possible to use different technology stacks for each service and to consider the one that suits better the required features.
- Different microservices can be reused for many systems, e.g., you may have a microservice specifically to deal with payments and share it with all your applications.
- The health check is more difficult, you have to monitor every service and aggregate logs as well as track the requests passing by each microservice to debug them properly.
- It is no easy task to find the boundaries between services properly, thus a good understanding of the business domain is needed, a good approach is DDD as described in Domain-Driven Design: Tackling Complexity in the Heart of Software.
- As a distributed system, you have to deal with other issues like network latency and failures.
- Even with independent deploys, some level of coordination is necessary amongst the teams when major changes are made.
- Knowing when and how to migrate from a Monolith to a Microservice.
Choosing one or another depends a lot on context and project specifics.
As a general rule, when in doubt, start with a monolithic approach and move to microservices if needed.