Why this vocabulary matters
The exam guide puts designing architectures out of scope for a developer, but Skills 1.1.1 to 1.1.4 ask you to describe them. In practice that means reading a three-line scenario and recognising the pattern. Think of this level as learning to read the map before you drive.
We will use one running example: an online shop that takes an order, charges a card, reserves stock and sends an email.
Monolithic versus microservices
A monolithic application is one deployable unit. Orders, payments, inventory and email live in one codebase, run in one process and usually share one database. It is simple to build and debug, but every change redeploys everything and you can only scale the whole thing.
Microservices split the shop into small services, each owning its own data and deployed on its own. Teams release independently and scale only the hot part. The cost is that you now have a distributed system: network calls fail, and data is spread around.
Trade-off: microservices are not automatically better. They swap code complexity for operational complexity.
Tightly versus loosely coupled
Components are tightly coupled when one cannot work without the other being up, reachable and fast right now. Here the order service calls payment directly:
# Tightly coupled: order code knows payment's address and waits for it
resp = http.post("https://payments.internal/charge", json=order) # payment down? order fails too
They are loosely coupled when something sits in between, such as a queue, a topic or an event bus, so each side only knows the intermediary:
# Loosely coupled: drop the work on an Amazon SQS queue and move on
aws sqs send-message \
--queue-url https://sqs.eu-west-1.amazonaws.com/111122223333/orders \
--message-body '{"orderId":"A-1001","total":42.50}'
# The consumer can be slow, restarting or scaled to zero. The message waits.
The queue also acts as a buffer: a traffic spike fills the queue instead of knocking over the consumer.
Synchronous versus asynchronous
Synchronous means the caller waits for the answer on the same connection. Asynchronous means the caller hands the work off, gets an acknowledgement and carries on.
AWS Lambda shows the difference in one flag:
# Synchronous: connection stays open until the function returns
aws lambda invoke --function-name charge-card \
--invocation-type RequestResponse out.json
# { "StatusCode": 200 } <- out.json holds the function's result
# Asynchronous: Lambda queues the event and answers immediately
aws lambda invoke --function-name send-receipt \
--invocation-type Event out.json
# { "StatusCode": 202 } <- "accepted", no result comes back
How to choose:
- Synchronous when the user needs the result to continue ("was my card accepted?"). Trade-off: the caller is only as fast and as available as the slowest thing it waits for.
- Asynchronous when the work can finish later (email, thumbnails, reports). Trade-off: no immediate result, eventual consistency, and you must plan for retries.
Gotcha: Amazon SQS standard queues deliver at least once, so a message can occasionally arrive twice. Write consumers to be idempotent: processing the same order ID a second time must not charge the card again.
Event-driven and fanout
In an event-driven architecture, a producer announces a fact ("OrderPlaced") and does not know or care who reacts. Routers such as Amazon SNS and Amazon EventBridge deliver the event to the interested consumers.
Fanout is one message copied to many subscribers in parallel. The classic AWS form is one Amazon SNS topic with several Amazon SQS queues subscribed:
# One topic, two queues subscribed (repeat per queue)
aws sns subscribe --topic-arn arn:aws:sns:eu-west-1:111122223333:order-placed \
--protocol sqs \
--notification-endpoint arn:aws:sqs:eu-west-1:111122223333:inventory
# Publish once...
aws sns publish --topic-arn arn:aws:sns:eu-west-1:111122223333:order-placed \
--message '{"orderId":"A-1001"}'
# ...and the inventory queue AND the email queue each get their own copy.
Adding a third consumer later means adding a subscription. The publisher's code does not change.
Choreography versus orchestration
Both coordinate a multi-step workflow across services. They differ in who is in charge.
- Choreography: nobody is. Each service reacts to events and emits its own, typically through Amazon EventBridge. Payment hears
OrderPlacedand emitsPaymentTaken; inventory hears that and emitsStockReserved. It is very loosely coupled, but the overall flow lives in nobody's code, so end-to-end timeouts, retries and tracing are harder. - Orchestration: a central coordinator calls each step in order and tracks progress. On AWS that is AWS Step Functions, where a state machine holds the flow:
{
"StartAt": "ChargeCard",
"States": {
"ChargeCard": { "Type": "Task", "Resource": "arn:aws:lambda:...:charge-card", "Next": "ReserveStock" },
"ReserveStock": { "Type": "Task", "Resource": "arn:aws:lambda:...:reserve-stock", "End": true }
}
}
You get one place to see the flow, retry a step or roll back. The price is that every service now depends on the orchestrator.
Analogy: choreography is dancers who each know their cue; orchestration is a conductor pointing at each section.
Stateful versus stateless
A stateful component remembers something about a client between requests, in its own memory or on its local disk. A stateless one keeps nothing locally, so every request carries or looks up what it needs.
# Stateful: the basket lives in THIS server's memory
baskets = {} # lost on restart, invisible to server #2
baskets[session_id] = items
# Stateless: any server can handle any request
items = session_store.get(session_id) # state lives outside the server
Stateless components scale horizontally because all copies are interchangeable. A load balancer can send request 1 to server A and request 2 to server B, and you can add or terminate servers freely without losing anyone's basket.
The state does not disappear. It moves to one of three places:
- A data store for durable data, for example Amazon DynamoDB.
- A cache for fast, short-lived session data, for example Amazon ElastiCache.
- A token carried by the client, for example a signed JSON Web Token holding the user's identity.
Pitfall: load balancer sticky sessions hide the problem rather than fix it. When the pinned server dies, its sessions die with it.
What the exam asks about this
Expect short scenarios with a keyword to decode:
- "One event, several independent consumers" → fanout with Amazon SNS and Amazon SQS.
- "Central coordination, visible workflow, retries per step" → orchestration with AWS Step Functions.
- "Services react to events, no central controller" → choreography with Amazon EventBridge.
- "Sessions lost when instances scale in" → make the tier stateless and externalise the state.
- "Downstream is slow or sometimes unavailable, must not lose requests" → decouple with a queue and process asynchronously.
Key takeaways
- A monolith is one deployable unit; microservices are small, independently deployed services that each own their data.
- Loose coupling puts a queue, topic or event bus between components so one can fail or slow down without taking the other with it.
- Synchronous means wait for the answer; asynchronous means hand off and move on, accepting eventual consistency and possible duplicates.
- Fanout is publish once, deliver to many. Choreography has no central controller; orchestration has one.
- Stateless components scale horizontally because state lives in a data store, a cache or a token, not on the server.