AWS Microservices Architecture: Patterns, Trade-Offs, and Setup
Microservices split a single application into independently deployed services, each responsible for one specific function. On AWS, the building blocks are well-supported: API Gateway, Lambda, ECS, SQS, and SNS. But microservices also introduce real operational costs that are easy to underestimate before you commit to the architecture.
This page explains what the pattern is, when it makes sense, how a request flows through a microservices system on AWS, and how to choose between Lambda, ECS, EKS, and App Runner for each service.
The honest framing: microservices solve real problems at scale. They also create new ones. Understanding both sides is how you avoid building the wrong thing.
What microservices architecture means in plain English
Think of a restaurant. In a tiny cafe, one person takes orders, makes the food, runs it to the table, and handles the cash register. That works fine when there are five tables. When the restaurant gets busy, everything slows down because one person is the bottleneck for everything.
A larger restaurant hires separate staff: a host, waiters, chefs, a dishwasher, a cashier. Each role can be scaled independently — hire a second chef during rush hour without changing anything else. If the dishwasher calls in sick, the kitchen still runs. The downside: coordinating a team requires more communication, handoff points can fail, and managing fifteen people is harder than managing one.
Microservices apply the same idea to software. In a traditional application, all the code runs as one deployable unit — a monolith. One process handles product browsing, order processing, email notifications, and everything else. In a microservices architecture, each of those responsibilities becomes a separate service with its own deployment, its own database, and its own scaling configuration. They communicate over the network instead of through in-process function calls.
The benefit: if the notification service crashes, orders still process. If the product catalog needs to handle a traffic spike, you scale just that service. Teams can own and deploy their service independently without coordinating every release. These benefits are real, but they require a more complex infrastructure to realise. Designing for high availability across independent services is harder than designing a single resilient process, because you now have network calls, partial failures, and distributed state to manage.
When to use microservices — and when not to
Microservices are not the right default. Most projects start too small and too uncertain to benefit from the overhead.
Microservices are a good fit when:
- Different parts of the system have meaningfully different scaling requirements
- Multiple teams need to deploy independently without coordinating releases
- A specific component needs a different runtime or technology from the rest
- A failure in one part of the system should not take down the whole application
- The product is mature enough that the domain boundaries are well understood
A monolith is the better choice when:
- You are building something new and don’t yet know where the natural service boundaries are
- Your team is small — fewer than five engineers
- Traffic is modest and uniform; no component needs to scale independently
- You want to move fast without managing distributed systems complexity
- You are still validating the product idea
The monolith-first rule Build a well-structured modern cloud application as a monolith with clean internal boundaries. Extract services when a concrete problem — not a theoretical future concern — justifies the overhead. Most successful microservices architectures started as monoliths that were split apart once the team understood the domain.
How microservices architecture works on AWS
A typical request flow through a microservices system on AWS:
- Client request. A browser or mobile app sends an HTTP request to an Application Load Balancer or API Gateway, which is the public-facing entry point for all services.
- Authentication. A Lambda authorizer or Cognito authorizer validates the request before it reaches any backend service.
- Routing. The load balancer or API Gateway routes the request to the correct service based on the path:
/products/*goes to the Product Service,/orders/*goes to the Order Service. - Service compute. Each service runs on its own compute. Lambda suits event-driven or bursty services. ECS/Fargate suits steady-traffic containers. EKS suits Kubernetes-managed workloads.
- Async messaging. When the Order Service completes a purchase, it publishes an event to SNS. The Notification Service and the Inventory Service each subscribe and react independently, without the Order Service knowing or caring which consumers exist. This is event-driven architecture applied to microservices.
- Per-service data ownership. Each service reads and writes to its own database. The Order Service has its own RDS instance. The Product Service has its own. No service queries another service’s database directly.
- Logs, metrics, and tracing. Each service emits structured logs to CloudWatch. Distributed tracing connects the dots across services so you can follow a single request through the entire system and find where it slowed down or failed.
Microservices vs monolith on AWS
| Dimension | Monolith | Microservices |
|---|---|---|
| Deployment model | Single deployable unit | Independent deployments per service |
| Scaling | Scale the whole application | Scale individual services independently |
| Failure isolation | A bad deploy can take down everything | Failures are contained to one service |
| Team ownership | Shared ownership of one codebase | Each team owns and deploys its service |
| Debugging complexity | Single log stream, simple stack traces | Requires distributed tracing across services |
| Ops overhead | Low — one pipeline, one deploy | High — multiple pipelines, queues, databases |
| Best for | Early-stage, small teams, uncertain domains | Mature products with distinct scaling needs |
Service communication patterns
How services communicate is one of the most consequential design decisions in a microservices architecture. The pattern you pick shapes your failure modes, latency, and operational complexity.
| Pattern | AWS service | When to use | Main downside |
|---|---|---|---|
| Synchronous HTTP | API Gateway, ALB | Caller needs an immediate result: user-facing reads, checkout flows | Service B being slow or down blocks Service A; chained calls amplify failures |
| Queue-based async processing | SQS | Work can be deferred and services should be decoupled: sending emails, processing uploads | Adds latency; tracing a job through the queue is harder |
| Fan-out events | SNS to SQS | Multiple services react independently to the same event: order placed, user registered | Publisher has no confirmation that any consumer succeeded |
| Streaming | Kinesis, MSK | High-volume ordered events: clickstreams, logs, IoT telemetry | More complex to operate; higher baseline cost than SQS |
Watch out for synchronous chain reactions If a user request requires Service A to call B, which calls C, which calls D, you have created a distributed monolith. Any service in the chain being slow makes the entire request slow. Any service being down makes the entire request fail. Look for opportunities to break synchronous chains with async messaging wherever an immediate response is not required.
A common pattern is synchronous HTTP for user-facing operations and SNS/SQS for everything that can happen in the background. See SNS vs SQS explained for a detailed comparison of the two queuing models.
Deployment options for microservices on AWS
AWS gives you four main options for running microservices. The right choice depends on your traffic pattern, team expertise, and how much operational overhead you can absorb.
| Option | Best for | Not ideal for | Ops overhead | Scaling model |
|---|---|---|---|---|
| Lambda | Event-driven or bursty services; short-running tasks | Long-running tasks; latency-sensitive services with cold start risk | Very low — no servers to manage | Automatic, per request |
| ECS / Fargate | Containerised services with steady or predictable traffic | Very spiky traffic where Lambda cost savings are larger | Medium — manage task definitions and service config | Configurable: target tracking or step scaling |
| EKS | Teams already running Kubernetes; portability requirements | Small teams or teams new to Kubernetes | High — Kubernetes has a steep learning curve | Horizontal Pod Autoscaler, Cluster Autoscaler |
| App Runner | Simple containerised web services; minimal config | Services needing fine-grained network or IAM control | Very low — push a container image and App Runner handles the rest | Automatic, based on request concurrency |
Where to start For a small team starting out, Lambda + API Gateway handles most services well and reduces operational overhead substantially. Move to ECS Fargate when you need long-running processes or fine-grained container control. See App Runner overview if you want a managed starting point for containerised services with minimal setup. For a structured side-by-side comparison, see choosing between Lambda, ECS, and EC2.
Data ownership and consistency
Think of a shared database like a shared whiteboard in an open office. Anyone can walk up and erase or rewrite something. If two people change it at the same time without telling each other, the result is unpredictable. And if you rearrange the columns, everyone’s notes break.
The database-per-service rule solves this by giving each service its own whiteboard that only it can write on. Other services that need the information ask for it through a well-defined interface, rather than walking up and reading it directly.
Never share a database between services If two services share a database, a schema migration in one breaks the other. Deployments become coupled again. A slow query in one service degrades the shared database for the other. You have recreated the monolith’s main disadvantage without any of the monolith’s simplicity.
In practice, data ownership means:
- Each service has its own RDS instance, DynamoDB table, or at minimum a separate schema
- No service queries another service’s database directly — all access goes through the owning service’s API
- When Service A needs data from Service B, it calls B’s API synchronously or subscribes to events that B publishes
Eventual consistency is unavoidable once you separate data across services. The inventory count on a product page may be slightly stale because it reflects the last event the Inventory Service published. This is acceptable for many use cases. Design for it explicitly rather than pretending all reads are perfectly consistent.
Idempotency and retries matter for async message processing. SQS delivers messages at least once, meaning a consumer can receive the same message twice. Design consumers so that processing the same message twice produces the same result: “insert order if it doesn’t already exist” rather than “insert order.” Configure dead-letter queues (DLQs) so messages that repeatedly fail are captured rather than silently dropped.
Stateless vs stateful services covers the design decisions around service state in more detail.
Security and observability
Microservices make both security and observability harder than they are in a monolith. You need to address both deliberately from the start.
IAM and least privilege. Each service should run under its own IAM role with permissions scoped to only what that service needs. The Order Service’s role should not have permission to write to the Product Service’s database or publish to unrelated SNS topics. See least privilege in AWS for the practical pattern. Service-to-service calls should use IAM-signed requests or short-lived tokens, not shared secrets.
Logs, metrics, and distributed tracing. In a monolith, a single log stream shows you what happened. In microservices, a single user request can touch five services, and you need to trace it across all of them to debug a failure.
Set up tracing before you go to production Configure AWS X-Ray for distributed tracing from the start — retrofitting it later is painful. Use correlation IDs that propagate across service calls so you can filter CloudWatch logs to a single request across all services. Observability matters more in microservices than in a monolith, not less.
Common mistakes
- Starting with microservices on a new project. You do not yet know where the natural service boundaries are. Premature decomposition creates the wrong boundaries, and refactoring across services later is much harder than refactoring inside a monolith. Build the monolith, learn the domain, extract services when a concrete problem justifies it.
- Sharing a database between services. The most common way microservices lose their independence. If two services share a database, a schema change in one can break the other. Deployments become coupled, and a slow query in one service degrades the shared database for the other.
- Long synchronous dependency chains. If a user request requires A to call B, which calls C, which calls D, any service being slow makes the whole request slow. Any service being down makes the whole request fail. Break synchronous chains with async messaging wherever the operation does not need an immediate result.
- Weak observability. Skipping distributed tracing and structured logging because setup takes time. Debugging a failure that crosses five services without tracing is extremely difficult. Set up AWS X-Ray and structured logs before you go to production, not after.
- Splitting by technical layer instead of business capability. Creating a “database service”, a “validation service”, and an “API service” produces distributed complexity with none of the independent deployability. Split by business domain — Order Service, Product Service, Notification Service — so each service can be owned, deployed, and scaled by one team.
Summary
- Microservices are not always better than a monolith. Build the monolith first and extract services when a concrete problem justifies the overhead.
- The core trade-off: microservices give you independent scaling, isolated failures, and team autonomy, in exchange for network complexity, distributed tracing requirements, and per-service databases.
- Service communication: synchronous HTTP for immediate responses, SQS for decoupled async work, SNS fan-out when multiple services react to the same event.
- Each service must own its own database. No cross-service queries — all data access goes through the owning service’s API.
- Lambda + API Gateway is the lowest-overhead starting point. ECS Fargate suits steady-traffic containerised services. EKS is for teams already running Kubernetes.
- Set up distributed tracing and structured logging from day one. Observability is harder in microservices, not easier.
Frequently asked questions
Should I start with microservices or a monolith on AWS?
Start with a monolith. Microservices add significant operational overhead: separate deployments, network calls between services, distributed tracing, independent databases. A well-structured monolith can handle significant load and is far easier to operate. Decompose into services only when a specific part of the system has different scaling requirements or when team coordination is genuinely blocked by coupling.
Does each microservice need its own database?
Yes, for true microservices. Each service should own its data and be the only service that writes to that data store. Other services that need that data should call the owning service via its API, not query the database directly. Shared databases recreate coupling and make independent deployments impossible in practice.
What AWS service should I use to run microservices?
It depends on your use case. Lambda works well for event-driven or bursty services with short execution times. ECS/Fargate suits services with steady traffic or longer-running tasks. EKS makes sense if your team already knows Kubernetes. App Runner is the simplest option for containerised web services. For most small teams starting out, Lambda + API Gateway is the lowest-overhead starting point.
How do microservices communicate on AWS?
Synchronously via HTTP/HTTPS using API Gateway or an Application Load Balancer, or asynchronously via SQS queues or SNS topics. Synchronous calls are simpler but create coupling: if Service B is down, Service A fails too. Asynchronous messaging decouples services but adds latency and makes tracing harder. Use synchronous for user-facing requests that need an immediate result, and async for background work.
Are microservices cheaper than a monolith on AWS?
Not necessarily. Microservices can reduce cost for components with uneven scaling needs, since you scale only the parts that need it. But they also introduce more infrastructure: more API Gateways, more queues, more databases, more observability tooling. For small teams and early-stage products, a monolith on a single EC2 instance or ECS task is almost always cheaper to run and operate.