Skip to content
AWS aws architecture 6 min read

Microservices on AWS

Microservices break a large application into many small, independently deployable services that each own one job and talk to each other over the network. The promise is that small teams can ship faster and scale parts of the system separately. The catch is that you trade simple in-process function calls for a distributed system, and distributed systems are hard. This page shows the AWS building blocks for microservices, when each one fits, and the most important warning of all: most teams should not start here.

Should you even use microservices?

A monolith is a single deployable application where every part runs in one process and calls happen as ordinary function calls. Microservices replace those function calls with network calls between separate services.

That single change ripples outward. A function call never fails on its own; a network call can time out, retry, or partly succeed. In a monolith you read one database in a transaction; across services you get eventual consistency (data is correct eventually, but two services can briefly disagree). One deploy becomes ten deploys. One log file becomes ten log streams.

Start with a monolith. A well-structured “modular monolith” (clear modules inside one app) gives you most of the team benefits with none of the network pain. Split a module into its own service only when a real force demands it: one part needs to scale far more than the rest, or two teams keep colliding in the same codebase. Splitting too early is the most common and most expensive cloud mistake.

Compute: where your services run

Every microservice needs somewhere to run its code. AWS gives you three main choices.

OptionWhat it isWhen to use itWhen not to
LambdaRun a function with no servers; AWS scales it per requestSpiky or low/medium traffic, event-driven work, small teams who want zero server managementSteady high traffic (cost climbs), long jobs over 15 min, very latency-sensitive paths (cold starts)
ECS on FargateRun containers without managing servers; AWS runs the host for youMost container microservices; you want containers but not a cluster to babysitYou need fine Kubernetes control or run thousands of pods
EKSManaged Kubernetes (the open-source container orchestrator)You already use Kubernetes, need its ecosystem, or run a very large fleetSmall teams — Kubernetes is a lot of operational overhead

ECS stands for Elastic Container Service. EKS stands for Elastic Kubernetes Service. Fargate is the “serverless” mode where you don’t manage the underlying servers.

Cost note: A Lambda handling 2 million requests a month (200 ms, 512 MB) costs only a few US dollars. The same constant traffic on Fargate runs 24/7 and can cost far more — but a Fargate task serving millions of requests an hour is far cheaper than Lambda at that volume. Match the model to the traffic shape.

The front door: API Gateway or ALB

Clients should not call your services directly. Put one entry point in front of them.

  • API Gateway is a managed HTTP front door with built-in authentication, rate limiting (throttling), request validation, and a free TLS certificate. Best for public REST/HTTP APIs, especially in front of Lambda.
  • Application Load Balancer (ALB) spreads HTTP traffic across container targets and routes by URL path. Best in front of ECS/EKS services. It is cheaper per request at high volume.

A common pattern: ALB in front of ECS services, API Gateway in front of Lambda functions.

Service discovery with Cloud Map

When the “Orders” service needs to call the “Payments” service, how does it find its address? Container IP addresses change constantly as tasks start and stop, so you cannot hard-code them. AWS Cloud Map is a service registry: services register themselves, and others look them up by a friendly name like payments.internal. ECS can register tasks into Cloud Map automatically.

Create a private DNS namespace so services can resolve each other inside your VPC (Virtual Private Cloud — your private network in AWS):

Console steps

  1. Open the AWS Cloud Map console.
  2. Choose Create namespace.
  3. Name it internal, pick API calls and DNS queries in VPCs, and select your VPC.
  4. Choose Create namespace. ECS services can now register under names like payments.internal.

CLI equivalent

aws servicediscovery create-private-dns-namespace \
  --name internal \
  --vpc vpc-0a1b2c3d \
  --region us-east-1

Output:

{
    "OperationId": "abc12345-6789-0abc-def0-1234567890ab"
}

Talk asynchronously: SQS, SNS, EventBridge

Synchronous calls (service A waits for service B to reply) create tight coupling: if B is down, A breaks. Asynchronous messaging lets A drop a message and move on. This is the single most powerful way to make microservices resilient.

ServicePatternUse it for
SQS (Simple Queue Service)One queue, one consumer group pulls messagesBuffering work, retries, smoothing traffic spikes
SNS (Simple Notification Service)One message, fanned out to many subscribersBroadcasting an event to several services at once
EventBridgeEvent bus with content-based routing rulesDecoupled event-driven architectures; routing by event type

Send an event to an EventBridge bus from a service:

aws events put-events --entries '[
  {
    "Source": "orders.service",
    "DetailType": "OrderPlaced",
    "Detail": "{\"orderId\":\"o-12345\",\"total\":49.99}",
    "EventBusName": "default"
  }
]'

Output:

{
    "FailedEntryCount": 0,
    "Entries": [
        { "EventId": "11112222-3333-4444-5555-666677778888" }
    ]
}

Other services subscribe to OrderPlaced events without the Orders service knowing they exist. That is loose coupling in action.

See across services: X-Ray tracing

In a monolith, one stack trace tells the whole story. Across services, a single user request might touch five services, and a slow response could be any of them. AWS X-Ray is a distributed tracing tool: it stitches together the path of one request across every service into a single trace, so you can see exactly where time is spent and where errors start.

Turn it on for a Lambda function:

aws lambda update-function-configuration \
  --function-name orders-api \
  --tracing-config Mode=Active

Output:

{
    "FunctionName": "orders-api",
    "TracingConfig": { "Mode": "Active" }
}

For ECS, run the X-Ray daemon as a sidecar container and grant the task role the AWSXRayDaemonWriteAccess policy. Then view the service map in the X-Ray console (now part of CloudWatch) to see the whole call graph.

Observability is not optional. With microservices, “it works on my machine” is meaningless — failures live in the gaps between services. Set up centralized logging (CloudWatch Logs), metrics, and tracing on day one, not after your first 2 a.m. outage.

Best practices

  • Begin with a modular monolith; extract a service only when scaling needs or team boundaries truly force it.
  • Prefer async messaging (SQS/SNS/EventBridge) over synchronous chains so one slow service does not topple the rest.
  • Give every service its own database; sharing a database recreates the monolith’s coupling with worse failure modes.
  • Make every cross-service call defensive: timeouts, retries with backoff, and idempotency (safe to process the same message twice).
  • Put a single front door (API Gateway or ALB) ahead of services and never expose them directly to the internet.
  • Wire up tracing (X-Ray), centralized logs, and metrics before you ship — debugging blind across services is brutal.
  • Automate each service’s deploy independently so small teams can ship without coordinating a big-bang release.
Last updated June 15, 2026
Was this helpful?