Why Modular Monoliths Work So Well on AWS
AWS is designed to support large-scale distributed architectures — but that doesn’t mean every system should start that way.
For small and medium-sized platforms, a modular monolith provides several benefits within AWS:
Simplicity: A single deployable artifact simplifies ECS, EC2, or Lambda deployment pipelines.
Observability: Centralized logging, tracing, and metrics are easier to manage through CloudWatch and X-Ray.
Security: IAM policies can secure the entire workload without needing to coordinate dozens of microservices.
Cost efficiency: You pay for fewer running containers or instances.
Evolution: When scale demands it, modules can be extracted into separate services without redesigning the entire system.
In short, AWS gives you the tools to scale; a modular monolith lets you delay complexity until it’s truly necessary.
Architecture Overview
A typical modular monolith on AWS might follow this structure:
Application: A .NET API containing multiple business modules (e.g., Patients, Professionals, Billing, Identity).
Infrastructure as Code: Defined through Terraform, AWS CDK, or SST to manage all resources.
Runtime Environment:
Amazon ECS with Fargate for container orchestration
Elastic Load Balancer (ALB) for traffic distribution
AWS Secrets Manager for storing credentials and configuration
Data Layer:
Amazon RDS (PostgreSQL or SQL Server) for relational persistence
Optional Amazon S3 for object storage
Communication and Events:
Internal modules communicate in-process
External integrations use SQS or EventBridge for asynchronous messaging
CI/CD:
GitHub Actions or AWS CodePipeline builds, tests, and deploys a single container image
Monitoring:
CloudWatch Logs for application and infrastructure logs
AWS X-Ray for tracing request flow through modules
This setup keeps the infrastructure lightweight while supporting high availability and autoscaling.
Example: Deploying a .NET Modular Monolith on ECS Fargate
Build and containerize the app:
docker build -t myapp:latest .
docker tag myapp:latest .dkr.ecr.us-east-1.amazonaws.com/myapp:latest
docker push .dkr.ecr.us-east-1.amazonaws.com/myapp:latest
Provision AWS resources (simplified example using Terraform):
resource "aws_ecs_cluster" "app_cluster" {
name = "modular-monolith-cluster"
}
resource "aws_ecs_task_definition" "app_task" {
family = "myapp"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "512"
memory = "1024"
container_definitions = jsonencode([{
name = "myapp"
image = ".dkr.ecr.us-east-1.amazonaws.com/myapp:latest"
portMappings = [{ containerPort = 80 }]
}])
}
resource "aws_ecs_service" "app_service" {
name = "myapp-service"
cluster = aws_ecs_cluster.app_cluster.id
task_definition = aws_ecs_task_definition.app_task.arn
desired_count = 2
launch_type = "FARGATE"
network_configuration {
subnets = var.private_subnets
security_groups = [aws_security_group.app_sg.id]
}
load_balancer {
target_group_arn = aws_lb_target_group.app_tg.arn
container_name = "myapp"
container_port = 80
}
}
Configure CloudWatch logging and alarms to monitor CPU, memory, and application health.
Enable autoscaling policies so ECS adjusts the number of tasks dynamically based on traffic.
Observability and Operations
Running a modular monolith on AWS doesn’t mean sacrificing visibility.
In fact, it can make observability simpler:
CloudWatch Logs can aggregate logs from all modules.
AWS X-Ray provides detailed traces through different layers of your application.
Amazon CloudWatch Metrics and Alarms let you monitor response times, error rates, and throughput.
AWS OpenTelemetry and Grafana can extend insights into performance at the code level.
Because all modules share the same process, tracing and debugging are more direct and coherent than across multiple services.
Integrating AWS Services into a Modular Monolith
A modular monolith still benefits from AWS’s extensive ecosystem. Examples include:
Email and Notifications: Use Amazon SES or SNS through clean abstractions.
File Management: Store files or reports in Amazon S3, referenced by module identifiers.
Caching: Use Amazon ElastiCache (Redis) for performance and session storage.
Background Jobs: Use Amazon SQS or AWS Lambda triggered from internal modules for asynchronous processing.
Each integration should live inside the Infrastructure layer of your modules, keeping the domain logic independent from AWS SDKs or configurations.
Evolution: From Modular Monolith to Microservices
When certain modules outgrow the main application — for instance, if a Payment or Reporting module begins to demand independent scaling — they can be extracted into standalone services.
This transition is straightforward because:
The boundaries are already defined.
Communication contracts already exist.
Infrastructure patterns (build, deploy, monitor) are established in AWS.
A modular monolith allows a gradual, risk-free migration to microservices when needed, rather than a full rewrite.
Conclusion
Deploying a modular monolith on AWS combines the operational simplicity of a monolith with the architectural discipline of microservices.
It offers:
Clear separation of business domains
Easier testing and deployment
Lower operational cost and maintenance effort
A smooth path to distributed architecture when scale requires it
Good architecture is not defined by how many services you have, but by how cleanly your system evolves.
On AWS, a modular monolith is often the most effective way to start — simple to operate, structured to grow, and ready to scale when the time comes.
Top comments (0)