The DVA-C02 exam reaches for "architectural pattern" language in almost every Domain 1 scenario, and the correct answer almost always depends on whether the workload is interactive (low-latency request/response) or background (durable, eventually processed). The first job in any new design is to classify the workload along that axis, then map it to one of five recurring shapes: monolithic, microservices, event-driven, fanout, and orchestrated workflow. Each pattern resolves a specific trade-off between deployment unit size, blast radius, and the ease of evolving one capability without breaking the others. The exam rarely asks you to define the patterns from a textbook; instead, it embeds them in scenarios where you must pick the service combination that matches the pattern named in the stem.
A monolithic application packages every capability into a single deployable artifact. On AWS the canonical monolith runs on an Auto Scaling group of EC2 instances behind an Application Load Balancer, or on a single Elastic Beanstalk environment, with a relational database such as Amazon RDS for PostgreSQL handling persistence. Monoliths are easy to reason about, but a deploy of a small bug fix forces a redeploy of the entire codebase, and a memory leak in one feature consumes capacity the rest of the application needs. The exam often presents the monolith as the starting point in a "modernization" scenario where the right answer is to extract one capability into a Lambda function or to split traffic with API Gateway routes.
A microservices architecture splits each capability into an independently deployable unit that owns its own data store. The team that owns the orders service can release on a different cadence than the team that owns payments, and a failure in reviews does not bring down checkout. The trade-off is operational complexity: distributed tracing, retries, idempotency, schema evolution, and network reliability become first-class concerns. On AWS, microservices commonly run as Amazon ECS services on AWS Fargate, as Lambda functions behind API Gateway, or as containerized workloads on Amazon EKS. Each microservice typically owns a dedicated DynamoDB table or RDS schema; sharing a single database across services is an anti-pattern because it reintroduces the deployment coupling the split was supposed to remove.
Event-driven architecture inverts the call direction. Instead of service A calling service B, service A emits an event that service B subscribes to. On AWS this is implemented with Amazon EventBridge (for routing structured events to many consumers), Amazon SNS (for fanout to multiple subscribers), Amazon SQS (for durable point-to-point delivery), and Amazon Kinesis Data Streams (for ordered, replayable streams). Event-driven systems are loosely coupled by construction: a new consumer can subscribe to an existing event without the producer learning of its existence. The cost is reasoning about eventual consistency, message ordering, and duplicate delivery, all of which the exam tests through specific service behaviors.
Choreography and orchestration are two ways to compose several event-driven steps into a business process. In choreography, each service reacts to events emitted by others, and the workflow exists only as a graph of subscriptions; there is no central coordinator. In orchestration, a central component (on AWS, typically AWS Step Functions) holds the workflow definition, calls each step in turn, and tracks progress. Choreography is cheaper and more decoupled but harder to monitor and reason about; orchestration costs per state transition but gives a single place to see workflow status, handle errors, and replay failed executions. The exam frames the choice with phrases like "the team needs to see exactly which step failed" (orchestration) versus "the team wants services to evolve independently with no central definition" (choreography).
Fanout is a specialization of event-driven where one event produces work for many downstream consumers. The canonical AWS fanout pattern is SNS to SQS: a single SNS topic publishes a message, and each SQS queue subscribed to the topic receives a copy that its consumer can process at its own pace. EventBridge supports the same shape with multiple rules targeting different destinations from one event bus. Fanout is the right pattern when an event has many independent reactions (notify the customer, update analytics, write an audit record, enqueue a fulfillment job) and you do not want the producer to know about any of them.
Unit testing with mocked AWS service responses and integration testing
Unit tests for AWS code mock the SDK at the boundary so that the test exercises business logic without making real network calls. For Python, the standard tool is moto, which patches boto3 clients in-memory and supports a large slice of the AWS API surface. For Node.js, AWS publishes aws-sdk-client-mock, which intercepts SDK v3 commands. The rule of thumb the exam respects: unit tests never call AWS, integration tests do, and end-to-end tests run against a deployed environment that mirrors production configuration.
import boto3
from moto import mock_aws
@mock_aws
def test_order_handler_writes_to_dynamodb():
table_name = "acme-retail-orders"
ddb = boto3.client("dynamodb", region_name="us-east-1")
ddb.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "order_id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "order_id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
from order_service import handle_new_order
handle_new_order({"order_id": "ord-001", "amount": 42.00})
item = ddb.get_item(TableName=table_name, Key={"order_id": {"S": "ord-001"}})
assert item["Item"]["amount"]["N"] == "42"
Integration tests, by contrast, deploy real resources into a sandbox account using the AWS SAM CLI or the AWS CDK, exercise them through real API calls, and tear them down. AWS CodeBuild can run both layers as separate phases of a pipeline; the unit phase fails fast on logic regressions, and the integration phase catches issues that only appear under real IAM, network, and service-quota conditions.
version: 0.2
phases:
install:
runtime-versions:
python: 3.12
commands:
- pip install -r requirements-dev.txt
pre_build:
commands:
- pytest tests/unit --junitxml=reports/unit.xml
build:
commands:
- sam deploy --stack-name acme-retail-it --no-confirm-changeset
- pytest tests/integration --junitxml=reports/integration.xml
post_build:
commands:
- sam delete --stack-name acme-retail-it --no-prompts
reports:
pytest_reports:
files:
- reports/*.xml
file-format: JUNITXML
Relational and non-relational databases
Architectural patterns dictate where state lives. A monolith with strong relational requirements (multi-row transactions, foreign keys, ad-hoc SQL) maps to Amazon RDS or Amazon Aurora. A microservice that owns a single entity type with key-based access maps to Amazon DynamoDB. The exam rarely asks you to pick between PostgreSQL and DynamoDB in the abstract; it embeds the choice inside a scenario by describing the access pattern. Multi-table joins and ACID transactions across rows imply relational. Single-digit-millisecond key lookups at horizontal scale imply DynamoDB. Time-series, hierarchical, or graph access patterns push you to Amazon Timestream, Amazon DocumentDB, or Amazon Neptune respectively, and the exam treats these as distractors for relational versus key-value scenarios.
💡 Exam Trap: A scenario that mentions "complex ad-hoc reporting across orders, customers, and inventory" is steering you toward an OLAP store such as Amazon Redshift, not toward DynamoDB. DynamoDB cannot perform server-side joins, and forcing it into reporting workloads is an anti-pattern.
💡 Exam Trap: Choosing Aurora Serverless v2 instead of provisioned Aurora is the correct answer when the scenario describes variable or intermittent traffic with idle periods. Provisioned Aurora is the correct answer when the scenario describes sustained, predictable throughput.
Amazon Simple Storage Service (Amazon S3) tiers and lifecycle management
Architectural patterns also dictate object storage. A monolith may keep media files on EBS volumes attached to EC2; a microservices design almost always pushes binary content to Amazon S3 because S3 detaches storage capacity from compute. S3 storage classes form a cost-versus-latency ladder: S3 Standard for hot data, S3 Standard-IA and S3 One Zone-IA for warm data accessed roughly monthly, S3 Intelligent-Tiering when access patterns are unpredictable, and S3 Glacier Instant Retrieval, S3 Glacier Flexible Retrieval, and S3 Glacier Deep Archive for cold and archival data. Lifecycle policies move objects automatically between classes based on age and apply expiration to remove them after a retention period.
{
"Rules": [
{
"ID": "acme-retail-archive",
"Status": "Enabled",
"Filter": { "Prefix": "orders/" },
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER_IR" },
{ "Days": 365, "StorageClass": "DEEP_ARCHIVE" }
],
"Expiration": { "Days": 2555 },
"NoncurrentVersionExpiration": { "NoncurrentDays": 30 }
}
]
}
📊 Limit: S3 Glacier Flexible Retrieval offers Expedited (1 to 5 minutes), Standard (3 to 5 hours), and Bulk (5 to 12 hours) retrieval options. S3 Glacier Deep Archive offers only Standard (within 12 hours) and Bulk (within 48 hours).
⚠️ Anti-Pattern: Writing tiny objects (less than 128 KB) directly into S3 Standard-IA or Glacier classes wastes money: the minimum billable object size for these classes is 128 KB and the minimum storage duration for IA classes is 30 days. Lifecycle objects into IA after they have aged past the threshold, do not create them there.
🎯 Scenario: The platform team for
acme-retailruns a monolithic checkout service and is hit by a regional outage that takes the whole stack offline. The post-mortem proposes extracting thepaymentsflow into its own deployable unit, fronted by API Gateway and powered by Lambda, with order events written to an SNS topic that fans out to fulfillment, analytics, and email-notification queues. The new shape is event-driven microservices with fanout: payments can scale independently, the email outage no longer blocks fulfillment, and the monolith continues to serve catalog reads.
Decision Anchor. Choose event-driven microservices with SNS-to-SQS fanout when consumers must scale independently and survive each other's failures. Choose orchestrated workflows with Step Functions when the team needs visible step-by-step progress and centralized error handling. Choose the monolith when the application has fewer than three developers and the cost of operating distributed infrastructure outweighs the deployment-coupling benefit.
