So, this is my first post since 2014! I’m diving back into the world of blogging while sharing my journey through software development. I hope you enjoy this as much as I do!
I’ve always been an advocate for new technologies, encouraging others, including senior leaders, to get familiar with cloud infrastructure and backend development. Despite my efforts, adoption was slow, and even experienced developers frequently asked questions about these topics. So, I decided to put everything in writing, once and for all.
Microservices
This is a simple view of a “microservice” or distributed system (if using Clean Arch):
As you can see, you have different applications, each with its own layers.
These microservices could serve different purposes. Let’s say:
- Microservice 1 is for selling tickets for a concert.
- Microservice 2 handles the payment process.
- Microservice 3 takes care of the shipment process.
Each microservice is designed to tackle a specific function, making it easier to manage and scale the application. This clear separation of concerns allows teams to work independently on their respective services, enhancing development speed and efficiency.
At this point, you may ask, how do these microservices even communicate? How does the payment microservice know about a sold ticket? You’re right to wonder, and this is one of the main reasons I started this blog, to demystify the communication aspect of microservices and modular monoliths.
Message Broker
A message broker acts as a delivery service, transporting messages from one point to another and ensuring that different microservices can effectively communicate without being tightly coupled. Popular message brokers include RabbitMQ, Azure Service Bus, and Apache Kafka. To simplify this, think of them as your local postal service, delivering letters —or, in this case, messages —to interested recipients. This decoupling allows each microservice to operate independently, making it easier to scale and maintain the overall system.
Quick facts
To be very clear from the start, microservices require high-end development skills and a solid understanding of distributed systems. They tend to have a very steep learning curve and communication is key here, especially as teams grow and services become more complex.
This is why I’m starting off with modular monoliths. They present a unique challenge for developers and serve as a fantastic foundation for transitioning into microservices later on.
With a modular monolith, you can:
- Organize Your Code: Use the principles of Domain-Driven Design (DDD) and “Clean Architecture” to create distinct modules, making your code easier to manage and understand.
- Simplify Testing and CI/CD: Streamline your testing and deployment processes, resulting in a more efficient workflow.
- Gradually Move to Microservices: If you find the need to transition, you can extract modules into independent services as your team and project grow.
The main difference is that microservices are independent applications, and in the .NET world, each of these applications typically lives in its own “solution” —a container for all related code, projects, and dependencies.
In contrast, a modular monolith includes all modules within a single application but separates them by their bounded contexts or domains.
So, what about Modular Monoliths?
A modular monolith encapsulates all of these microservices into a single solution but with independent modules. Each module serves a specific purpose; for example, the Ticketing microservice becomes a module called “Ticketing,” with all its layers (Application, Domain, Infrastructure, and Presentation). The Payment microservice also functions as its own module. Each module has its own “Domain” or “Bounded Context.” I will cover this in more detail in a future post when I dive into DDD, Domain-Driven Design.
Here is a simple schematic of a modular monolith application:
Why Modular Monoliths?
Decoupled Architecture:
Modular monoliths maintain a decoupled architecture while residing in a single solution. By utilizing message brokers like RabbitMQ or Azure Service Bus, modules can communicate independently, mimicking the microservices approach. This allows for scalability and flexibility while retaining the simplicity of a single application.
Simplified CI/CD:
In a modular monolith, because everything exists within a single solution, you can technically operate with a single CI/CD pipeline. This setup reduces the complexity of managing multiple pipelines, making deployment and maintenance more streamlined. However, if you’re already using separate pipelines for specific tasks, like code analysis with SonarQube or SonarCloud, there’s no need to change them. The main point here is that, unlike microservices, you have the option to deploy your application as a single cohesive unit.
Easier Debugging:
With all modules in one codebase, tracing issues is more accessible. Developers can easily access related code, resulting in faster debugging and quicker resolutions.
Consistent Development Environment:
Working within the same application context enables developers to gain a better understanding of the entire system. This encourages knowledge sharing and resource collaboration, which is particularly beneficial for newcomers.
Reduced Overhead:
Microservices introduce complexities such as network latency and inter-service communication management. A modular monolith alleviates these concerns while allowing modules to communicate effectively through message brokers, minimizing operational overhead.
Focused Team Collaboration:
Teams can collaborate more efficiently when developing a unified application. This enhances communication and alignment on project goals, which is particularly crucial for smaller teams or developers who are just starting out.
Gradual Transition to Microservices:
Modular monoliths provide a solid foundation for future scalability. You can gradually extract modules into microservices as needed, allowing for an evolutionary approach to architecture without overwhelming the team.
Conclusion
Start with simplicity —begin with a modular monolith before moving to microservices. A modular monolith offers a balanced approach, allowing you to maintain a decoupled architecture within a single codebase. This makes it easier to adapt and scale as your needs grow without the overhead of managing multiple services right from the start. That’s a wrap for my first blog post in years! I hope you enjoyed it, and stay tuned for the next one, where I’ll dive into actual code and explore building a modular monolith step-by-step.
Thank you!
As this marks my first blog post since 2014, I want to take a moment to express my gratitude to those who have profoundly impacted my journey as a developer.
A special thanks to Steve “ardalis” Smith, Milan Jovanovic, David Fowler (this guy does not need a link, he’s the man!), Jimmy Bogard, Urs Enzler, Carl Franklin, and many others. Your insights and shared knowledge have inspired me and contributed not only to my professional growth but also to my personal development.