When building a software application, one of the important architecture decisions is how we organize the application.
Should we build everything as one application?
Or should we divide the application into multiple smaller services?
These approaches are commonly known as Monolithic Architecture and Microservices Architecture.
What Is a Monolith?
A monolithic application is an application where multiple business functionalities are developed and deployed as one unit.
For example, an e-commerce application may contain:
- User management
- Product management
- Order management
- Payment
- Inventory
- Notifications
All of these can exist inside one ASP.NET Core application.
flowchart TD
User --> Application
Application --> UserModule
Application --> ProductModule
Application --> OrderModule
Application --> PaymentModule
Application --> InventoryModule
Application --> Database
The application is deployed as one unit.
Simple ASP.NET Core Monolith
Imagine one ASP.NET Core project containing controllers such as:
UsersController
ProductsController
OrdersController
PaymentsController
InventoryController
All of them run inside the same application.
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
private readonly AppDbContext _context;
public OrdersController(AppDbContext context)
{
_context = context;
}
[HttpPost]
public async Task CreateOrder(Order order)
{
_context.Orders.Add(order);
await _context.SaveChangesAsync();
return Ok(order);
}
}
The Order functionality is part of the same application as the other business functionalities.
Advantages of a Monolith
- Simple to develop initially.
- Simple to deploy.
- Easy to debug locally.
- Easy to make transactions across modules using one database.
- Less network communication between business modules.
- Suitable for many small and medium-sized applications.
Problems With a Large Monolith
As the application grows, the codebase can become very large.
For example:
flowchart TD
Application --> UserModule
Application --> ProductModule
Application --> OrderModule
Application --> PaymentModule
Application --> InventoryModule
Application --> ReportingModule
Now imagine that hundreds of developers are working on the same application.
Changes in one area can affect other areas.
The entire application may also need to be built and deployed even when only one module changes.
What Are Microservices?
Microservices architecture divides an application into smaller independently deployable services.
Each service generally owns a specific business capability.
For example:
flowchart LR
User --> API
API --> UserService
API --> ProductService
API --> OrderService
API --> PaymentService
API --> InventoryService
Each service can be developed, deployed and scaled independently.
Example With ASP.NET Core
We could have separate ASP.NET Core applications:
UserService
ProductService
OrderService
PaymentService
InventoryService
For example, OrderService may expose:
POST /api/orders
GET /api/orders/{id}
PaymentService may expose:
POST /api/payments
Each service has its own responsibility.
Database Design in Microservices
A common microservices approach is for each service to own its data.
flowchart LR
OrderService --> OrderDatabase
PaymentService --> PaymentDatabase
InventoryService --> InventoryDatabase
UserService --> UserDatabase
This reduces direct database coupling between services.
For example, PaymentService should generally not directly modify tables owned by OrderService.
Instead, services communicate through APIs or messaging.
Monolith vs Microservices
| Monolith | Microservices |
|---|---|
| One deployable application | Multiple independently deployable services |
| Usually simpler initially | More infrastructure and operational complexity |
| Modules share the same application process | Services communicate over the network |
| Often uses a shared database | Services can own separate databases |
| Scaling is commonly at application level | Individual services can be scaled independently |
| Simpler local development | Distributed debugging can be more complex |
Real-World Example
Imagine an e-commerce application where the product catalog receives much more traffic than the payment functionality.
In a monolith:
flowchart LR
Users --> Application
Application --> ProductModule
Application --> PaymentModule
Application --> OrderModule
Application --> Database
We may need to scale the entire application even though only product search requires additional capacity.
With microservices:
flowchart LR
Users --> ProductService
Users --> OrderService
Users --> PaymentService
ProductService --> ProductDatabase
OrderService --> OrderDatabase
PaymentService --> PaymentDatabase
ProductService can be scaled independently.
Service Communication
Microservices need to communicate with each other.
For example, when an order is created, the OrderService may need payment processing.
sequenceDiagram
User->>OrderService: Create order
OrderService->>PaymentService: Process payment
PaymentService-->>OrderService: Payment result
OrderService-->>User: Order result
This introduces network communication that does not exist between modules inside a traditional monolith.
What Happens When a Service Fails?
Suppose PaymentService is unavailable.
flowchart LR
User --> OrderService
OrderService --> PaymentService
PaymentService --> Failure
The OrderService now needs to decide what to do.
Depending on the business requirement, it might:
- Return a temporary failure.
- Retry the request.
- Put the operation into a queue.
- Keep the order in a pending state.
Microservices therefore introduce distributed-system problems such as network failures, timeouts, retries and partial failures.
Can a Monolith Be Scaled?
Yes.
A common misunderstanding is that monolith means the application cannot scale.
A well-designed monolithic ASP.NET Core application can run on multiple servers.
flowchart LR
User --> LoadBalancer
LoadBalancer --> App1
LoadBalancer --> App2
LoadBalancer --> App3
App1 --> Database
App2 --> Database
App3 --> Database
So the choice between monolith and microservices is not simply about scalability.
It also involves team structure, deployment requirements, business boundaries, operational complexity and system requirements.
Modular Monolith
There is also an approach between a traditional monolith and microservices: a modular monolith.
The application is deployed as one unit, but the internal code is divided into well-defined business modules.
flowchart TD
Application --> UserModule
Application --> OrderModule
Application --> PaymentModule
Application --> InventoryModule
This can provide clear boundaries without immediately introducing the operational complexity of distributed services.
When Microservices Can Become Complicated
Suppose we have 50 services.
flowchart LR
API --> Service1
API --> Service2
API --> Service3
API --> Service4
API --> Service5
Service2 --> Service6
Service3 --> Service7
Now we need to manage:
- Service deployment
- Network communication
- Authentication
- Monitoring
- Logging
- Service discovery
- Retries
- Timeouts
- Distributed tracing
- Data consistency
Therefore, microservices should not be introduced simply because they are popular.
Important Interview Question
Should every application use microservices?
No.
The architecture should be selected based on the actual requirements.
A small application with a small development team may benefit from a modular monolith rather than immediately introducing many distributed services.
A large organization with independently evolving business domains and different scaling requirements may have reasons to use microservices.
Key Takeaways
- A monolith is generally deployed as one application.
- Microservices divide an application into independently deployable services.
- Monoliths are often simpler to develop and operate initially.
- Microservices can allow independent deployment and scaling.
- Microservices introduce network and distributed-system complexity.
- A monolith can still be horizontally scaled.
- A modular monolith can provide strong internal boundaries without distributed deployment.
- Microservices should be introduced because of actual system and business requirements, not simply because they are popular.
Conclusion
Monolith and microservices are architectural approaches, and both can be used to build successful systems.
A monolith keeps functionality within one deployable application, while microservices divide functionality into independently deployable services.
Microservices can provide independent scaling and deployment, but they also introduce additional operational and distributed-system complexity.
The right architecture depends on the application's size, business boundaries, team structure, deployment requirements and scalability needs.
Next, we will look at how different components communicate with each other: Synchronous vs Asynchronous Communication.