DEV Community

Gabrielle Eduarda
Gabrielle Eduarda

Posted on

Modular Monoliths on AWS: Simplicity, Structure, and Scalability

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)