DVA-C03 Application Development: Decoupling, APIs, SDKs, Events, Streams and AI
Developing code for applications hosted on AWS means writing services that talk to each other and to AWS through managed building blocks (queues, topics, event buses, APIs, streams, SDKs and AI services) so that each part can fail, scale and change without breaking the rest. This lesson covers DVA-C03 Task 1.1: telling tightly coupled designs from loosely coupled ones and decoupling them with Amazon SQS, Amazon SNS and Amazon EventBridge; choosing synchronous or asynchronous processing; building resilience with retries, backoff, idempotency and graceful degradation; validating and transforming requests in Amazon API Gateway; using SDK paginators, waiters and error handling; handling streaming data with Kinesis Data Streams and Amazon Data Firehose; decoding the data formats that AWS events deliver; using Kiro for spec-driven, reviewed AI-assisted development; and calling Amazon Bedrock from application code. Lambda configuration (Task 1.2) and data-store design (Task 1.3) have their own lessons.
On this page10 sections
- Tightly coupled vs loosely coupled systems
- Synchronous vs asynchronous patterns
- Fault tolerance: retries with backoff, idempotency and graceful degradation
- Building and extending APIs with Amazon API Gateway
- Calling AWS services with the AWS SDKs: pagination, waiters and errors
- Event-driven patterns: EventBridge, S3 Event Notifications and SNS
- Managing streaming data: Kinesis Data Streams and Amazon Data Firehose
- Serializing and deserializing data at AWS service boundaries
- AI-assisted development with Kiro: specs, steering, hooks and reviews
- Integrating managed AWS AI services: calling Amazon Bedrock from code
- Recognise tight coupling and decouple components with SQS queues, SNS-to-SQS fan-out and EventBridge, choosing between them by delivery model
- Decide which parts of a request must be synchronous and move the rest to queues, events or asynchronous invocation
- Build fault-tolerant code with SDK retry modes, backoff with jitter, idempotency keys and conditional writes, graceful degradation and circuit breakers
- Validate and transform API requests in API Gateway with models, request validators, mapping templates and AWS service integrations, and know what HTTP APIs leave out
- Use AWS SDK paginators, waiters and specific exception handling correctly
- Route and filter events with EventBridge, S3 Event Notifications and SNS, handle streaming data with Kinesis Data Streams and Firehose, and decode each service's payload format correctly
- Use Kiro specs, steering and hooks for AI-assisted development, and call Amazon Bedrock with Converse, streaming and batch inference
Tightly coupled vs loosely coupled systems
A system is tightly coupled when one component calls another directly and waits for it, so a slow or failed dependency slows or fails the caller; it is loosely coupled when components communicate through an intermediary (a queue, topic or event bus) and can fail, scale and change independently. The classic warning signs of tight coupling are a synchronous chain of HTTP calls, timeouts that cascade back to the user, lost work when a downstream service is down, and a producer whose code must change every time a new consumer is added.
AWS gives you three managed building blocks for decoupling, and each solves a different problem:
| Service | Delivery model | Use it when |
|---|---|---|
| Amazon SQS queue | Pull. Each message is processed by one of the competing consumers | Work must be buffered so the producer keeps going while the consumer is slow or down; a fleet of workers shares the jobs |
| Amazon SNS topic | Push. Every subscriber receives a copy of each message | One message must reach several independent consumers (fan-out) |
| Amazon EventBridge event bus | Push. Rules match events by content and route them to targets | Content-based routing to many targets, SaaS/AWS service events, archive and replay |
Queues: temporal decoupling and competing consumers
Putting an SQS queue between a web tier and a back-end service lets the web tier accept the request, enqueue it and return, even if the back end is slow or unavailable. Workers poll at their own pace. When a worker receives a message, SQS hides it from other workers for the visibility timeout; the worker deletes it only after it has finished. If the worker crashes, the message is never deleted, reappears when the timeout expires and another worker retries it. Deleting a message as soon as it is received turns a crash into lost work.
SNS-to-SQS fan-out
When several services must each process every message at their own pace, publish once to an SNS topic and subscribe one SQS queue per consumer. SNS removes the producer's knowledge of its consumers (add a subscriber without touching producer code), and each queue buffers its consumer independently, so one slow or failing consumer cannot affect the others. A single shared queue does not work for this: consumers on one queue compete and split the messages between them.
Synchronous vs asynchronous patterns
Use a synchronous request/response call only for the part of a request whose result the caller needs right now; move everything that can happen later to an asynchronous path (a queue, an event or an asynchronous invocation) so its latency and failures stay off the critical path.
Amazon API Gateway is synchronous: the client waits for the integration to answer, and the integration timeout is 29 seconds by default (Regional and private REST APIs can request a higher quota, but a minutes-long job still does not belong in a request). Work that takes longer, or a caller that only needs an acknowledgement, calls for one of these patterns:
- Queue and acknowledge. Put the request on an SQS queue (directly through an API Gateway AWS service integration, or from a small function), return 200 or 202 at once, and let a separate consumer process it and retry failures. This is how you meet a webhook sender's short response deadline.
- Asynchronous Lambda invocation from API Gateway. In a Lambda non-proxy (custom) integration, add an
X-Amz-Invocation-Typeintegration request header with the static value'Event'. API Gateway then invokes the function asynchronously and returns immediately, with no change to function code. (Lambda's own async retry and destination behaviour is covered in the Lambda lesson.) - Split the critical path. When part of the result is needed immediately (for example, whether a payment succeeded) and the rest can lag (receipt email, loyalty points, analytics), do the critical step synchronously, then publish an event (EventBridge or SNS) that the slower steps consume. The dividing line is the caller's next action: whatever that action depends on stays on the synchronous path, and everything else moves off it.
A synchronous caller waits for every step on its path, so the lasting fix for a caller that times out on slow work is to change which steps it waits for, not how fast or how long any one step is allowed to run.
Fault tolerance: retries with backoff, idempotency and graceful degradation
A resilient application retries transient failures with exponential backoff and jitter, makes every consumer idempotent because AWS messaging is at-least-once, and degrades gracefully when an optional dependency fails instead of failing the whole request.
Backoff, jitter and SDK retry modes
Exponential backoff increases the wait after each failed attempt; jitter adds a random amount to each wait. Backoff alone keeps many clients in lockstep (they all failed at the same moment and follow the same schedule), so their retries arrive in synchronized waves that are throttled again. Jitter spreads them out. The AWS SDKs retry throttling and transient errors for you, configured by retry mode and maximum attempts (for example retry_mode and max_attempts in a botocore Config, or the AWS_RETRY_MODE / AWS_MAX_ATTEMPTS settings):
| Retry mode | Behaviour |
|---|---|
| legacy | Older, SDK-specific retry logic kept for backward compatibility |
| standard | Consistent retry rules across SDKs: exponential backoff with jitter, a default maximum of 3 attempts |
| adaptive | Standard retries plus a client-side token-bucket rate limiter that slows the rate of new requests when the service throttles |
More attempts with standard mode only retries harder; adaptive mode is the setting that makes a high-volume client back off its overall request rate, with no custom code.
At-least-once delivery and idempotent consumers
SQS standard queues, SNS, EventBridge and S3 event notifications can all deliver the same message or event more than once, and a visibility timeout shorter than processing time makes an SQS message reappear while it is still being worked on. Setting the visibility timeout above the real processing time prevents the in-flight reappearance, but no consumer setting can stop the service itself from delivering a duplicate. The reliable fix is an idempotency key: a business identifier (order ID, payment ID) or the EventBridge event's id, which stays the same across redeliveries (its time field is not a key). Record the key with a conditional write such as DynamoDB PutItem with attribute_not_exists(pk) before doing the side effect; the first delivery succeeds and repeats fail the condition and are skipped. A separate read followed by a write leaves a race window between two concurrent deliveries.
SQS FIFO queues deduplicate sends within a fixed 5-minute deduplication interval (not configurable), using either an explicit MessageDeduplicationId or content-based deduplication, which is a SHA-256 hash of the message body. If the body contains anything that changes on each retry (a timestamp), every retry hashes differently and gets through; set MessageDeduplicationId to a stable value such as the order ID. FIFO deduplication does not stop a consumer from seeing a message again after its visibility timeout expires. Ordering is a separate setting: messages that share a MessageGroupId are delivered in order, and different groups are processed in parallel.
Graceful degradation and circuit breakers
Graceful degradation keeps the core feature working with reduced functionality when an optional dependency fails. It needs two pieces: a short timeout on the dependency call, so the request does not wait, and a fallback (a cached or default response, such as a flight-status page that shows the schedule with a default seat map when the seat-map service is slow). The aim is that the core content is always served quickly: anything that makes the request wait longer on the failing dependency, or fail when the core content was available, works against it.
When a dependency is down for a long time, even fast fallbacks waste every request's timeout and retries first, which ties up concurrency. A circuit breaker counts failures and, past a threshold, opens: calls are skipped and the fallback returns immediately for a cool-down period, after which a trial call checks whether the dependency has recovered.
Building and extending APIs with Amazon API Gateway
API Gateway REST APIs can validate requests and transform requests and responses without any backend code; which features you get depends on the API type and on whether the integration is a proxy or non-proxy integration.
Request validation
Define a model (a JSON Schema) for the request body and attach a request validator to the method (validate body, parameters, or both). API Gateway rejects an invalid request with HTTP 400 before the backend runs, so the function never pays to reject bad input. Validation is a schema check on the payload itself; it is a separate concern from authorization (who the caller is) and from transformation (reshaping the payload).
Proxy vs non-proxy integrations and mapping templates
A proxy integration (Lambda proxy or HTTP proxy) passes the request through as is and returns the backend's response as is. A non-proxy (custom) integration lets you attach mapping templates, written in Velocity Template Language (VTL), to the integration request and the integration response. Use them to rename fields, reshape a body, add or remove fields (for example, stripping internal fields from a legacy backend's response) and set integration headers, all without extra compute. Mapping templates exist only for non-proxy integrations.
AWS service integrations
API Gateway can call an AWS service API directly, such as SQS SendMessage, with no Lambda function in between. Two things are required that a Lambda proxy integration would otherwise hide: an IAM role that API Gateway assumes as the integration's credentials, allowing the action (here sqs:SendMessage on the queue), and an integration request mapping template that builds the service request from the client's body (for SQS, the body goes into the MessageBody parameter, for example as form-encoded Action=SendMessage&MessageBody=... with Content-Type: application/x-www-form-urlencoded). A public AWS endpoint needs no VPC link.
REST APIs vs HTTP APIs
| Feature | REST API | HTTP API |
|---|---|---|
| Request validation with models | Yes | No |
| VTL mapping templates | Yes (non-proxy integrations) | No (parameter mapping only: headers, query strings, paths) |
| API keys and usage plans, response caching, AWS WAF | Yes | No |
| Native JWT authorizer | No (use Cognito or Lambda authorizers) | Yes |
| Cost and latency | Higher | Lower |
If an API depends on schema validation or VTL transformations, moving it to an HTTP API means rewriting that logic in code; parameter mapping and JWT authorizers do not validate a body.
Calling AWS services with the AWS SDKs: pagination, waiters and errors
Application code calls AWS through the SDKs, and three SDK features prevent the most common bugs: paginators for list operations that return results in pages, waiters for resources that take time to become ready, and typed error handling so that expected failures are handled and unexpected ones still fail.
Paginators
List and scan operations return one page at a time. S3 ListObjectsV2, for example, returns at most 1,000 keys per call (MaxKeys cannot raise that) and sets IsTruncated with a NextContinuationToken when more remain. Code that makes one call silently sees only the first page. A paginator issues the follow-up requests for you:
s3 = boto3.client("s3")
pages = s3.get_paginator("list_objects_v2").paginate(
Bucket="media-archive", Prefix="2026/videos/")
count = sum(len(p.get("Contents", [])) for p in pages)
Waiters
Many control-plane calls return before the resource is usable: an EC2 instance starts pending, a CloudFormation stack starts CREATE_IN_PROGRESS, and DynamoDB CreateTable returns while the table is CREATING, and writes fail with ResourceNotFoundException until it is ACTIVE. A waiter polls the describe call on an interval until the resource reaches the desired state or the waiter times out, for example ec2.get_waiter("instance_running").wait(InstanceIds=[iid]) or ddb.get_waiter("table_exists").wait(TableName="Sessions"). It replaces fixed sleeps (which guess) and hand-written polling loops. Waiters and retries do different jobs: the SDK's retry logic handles throttling and transient faults, while readiness is something you wait for.
Handling service exceptions
The SDKs raise a service error that carries an error code. In boto3 it is a botocore.exceptions.ClientError whose response["Error"]["Code"] names the error, and the common ones are also exposed as classes such as client.exceptions.ConditionalCheckFailedException. Catch only the error you expect and can handle, and re-raise everything else so the caller (or Lambda's retry and failure handling) still sees real failures:
from botocore.exceptions import ClientError
try:
table.put_item(Item=item,
ConditionExpression="attribute_not_exists(voucherCode)")
except ClientError as e:
if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
log.info("voucher %s already redeemed", item["voucherCode"])
else:
raise
Each operation has its own error codes, so match the code to the call: a single conditional write raises ConditionalCheckFailedException, while a TransactWriteItems call whose condition fails raises TransactionCanceledException with a cancellation reason per item. A broad handler that treats every error as the expected one turns throttling, permission and network failures into silent successes.
Event-driven patterns: EventBridge, S3 Event Notifications and SNS
In an event-driven design, producers emit events about what happened and the routing layer decides who reacts; on AWS, EventBridge routes by event content, S3 Event Notifications announce object changes, and SNS fans messages out to subscribers with optional per-subscription filters.
EventBridge rules, targets and filtering
A rule on an event bus has an event pattern and up to five targets. Filter in the pattern, not in target code: patterns match exact values, prefixes, lists of values and numeric ranges, so only matching events are ever delivered.
{
"detail-type": ["ShipmentDelayed"],
"detail": {
"carrier": ["ACME"],
"delayHours": [{ "numeric": [">=", 48] }]
}
}
An input transformer on a target reshapes the payload sent to that target; it does not decide which events are delivered. For an SQS FIFO target, the rule supplies a message group ID.
Delivery failures: retry policy and dead-letter queues
When a target cannot be reached, EventBridge retries with backoff according to the target's retry policy: a maximum event age of up to 24 hours and a maximum number of retry attempts. When those are exhausted the event is dropped, unless the target has a dead-letter queue (an SQS queue). The DLQ holds exactly the events that failed for that target, so they can be reprocessed without touching other targets.
Archive and replay
An archive on a bus stores the events that match its pattern for a retention period you choose. StartReplay re-sends the events from a time window back to the bus. The replay destination can be limited to specific rule ARNs (FilterArns), so a fixed consumer can reprocess history while other rules' targets never see the events again. Use replay for events that were delivered but processed wrongly; use a DLQ for events that could not be delivered.
S3 Event Notifications
A bucket can send events such as s3:ObjectCreated:* to four destinations: an SNS standard topic, an SQS standard queue, a Lambda function or Amazon EventBridge. FIFO queues and FIFO topics are not supported as direct destinations; to reach an SQS FIFO queue, enable EventBridge on the bucket and add a rule that targets the queue with a message group ID. Each notification configuration can filter by key prefix and suffix, which is also how you stop a recursive loop where a function writes its output into the same bucket that invokes it (for example, trigger only on uploads/ keys ending in .png and write the output under resized/). Filtering in the notification configuration means unwanted objects never invoke the function at all.
SNS subscription filter policies, raw delivery and FIFO topics
Each SNS subscription can carry a filter policy, so a subscriber receives only matching messages while other subscribers still receive everything. The policy's scope is MessageAttributes by default; set FilterPolicyScope to MessageBody when the routing data is only in a JSON body, and nest the policy keys to mirror the payload, for example {"booking": {"tier": ["gold"]}} for a body such as {"booking": {"tier": "gold", ...}}. Filtering at the subscription means unmatched messages never reach that subscriber's endpoint at all.
By default SNS wraps each message delivered to SQS in a JSON envelope (Type, MessageId, TopicArn, Message, Timestamp and more), with the original payload as a string in Message. Turn on raw message delivery on the subscription to make the SQS body the original message.
SNS FIFO topics keep order within a message group and deduplicate publishes within 5 minutes (by deduplication ID or content). Fan-out with ordering and deduplication is an SNS FIFO topic with one SQS FIFO queue per consumer, using the business key whose order matters (an account ID, say) as the message group ID, so each account's messages stay in order while different accounts are processed in parallel. FIFO queues subscribe only to FIFO topics, and ordering and deduplication are FIFO features that standard queues and topics do not provide.
Managing streaming data: Kinesis Data Streams and Amazon Data Firehose
Use Amazon Kinesis Data Streams when your own consumers must read an ordered, replayable stream in real time, and Amazon Data Firehose when records only need to be delivered, buffered into files, to a destination such as S3 without consumer code.
Shards and partition keys
A Kinesis data stream is made of shards. Each shard accepts up to 1 MB per second or 1,000 records per second of writes, and records are routed to a shard by hashing their partition key. Records with the same key go to the same shard, and ordering exists only within a shard, so a key such as the device ID keeps each device's records in order. A low-cardinality key (a status field with only a handful of values, say) can use only as many shards as it has values: those hot shards throw ProvisionedThroughputExceededException while the rest sit idle, and adding shards does not help until the key spreads. Choose a key with many distinct values that still groups the records that must stay ordered.
Reading: shared throughput, enhanced fan-out and Lambda
- Shared (standard) consumers poll with
GetRecordsand share each shard's read throughput (2 MB per second) and call rate among every application reading it, so adding applications causesReadProvisionedThroughputExceedederrors and growing lag. - Enhanced fan-out registers a consumer that gets its own dedicated read throughput per shard, with records pushed over HTTP/2 through
SubscribeToShardat low latency. Use it when several applications read the same stream. - Lambda event source mapping. The parallelization factor (1 to 10) lets Lambda process several batches from one shard concurrently while still keeping records with the same partition key in order, which raises throughput without adding shards.
Amazon Data Firehose
Producers write records to a Firehose stream, and Firehose buffers them and delivers objects to S3 (and other destinations) with no consumer to write or run. Buffering uses two hints, buffer size and buffer interval, and delivery happens when the first is reached. At low volume the size is never reached, so the interval decides how often objects appear; lowering it (the S3 destination accepts down to 0 seconds) delivers sooner as more, smaller objects. Firehose can also invoke a Lambda function to transform records in flight; the response format is covered in the next section.
| Need | Choose |
|---|---|
| Custom real-time consumers, ordering per key, replay of recent data | Kinesis Data Streams |
| Land records in S3 in batched files with no consumer code | Amazon Data Firehose |
| Each of several apps needs its own read throughput | Kinesis enhanced fan-out consumers |
Serializing and deserializing data at AWS service boundaries
Every AWS integration hands your code data in a specific wire format, and most event-handling bugs come from parsing that format as if it were the producer's original payload. Know these formats:
| Source | What your code receives | What to do |
|---|---|---|
| Kinesis record in a Lambda event | record["kinesis"]["data"] is a base64-encoded string (the data blob is arbitrary bytes) | Base64-decode, decode the bytes (for example UTF-8), then parse the JSON |
| DynamoDB Streams record (and low-level DynamoDB API responses) | Items in attribute-value format: {"userId": {"S": "u-42"}, "score": {"N": "87.5"}} | Convert with boto3's TypeDeserializer (or unmarshall in the JavaScript SDK) |
| S3 event notification | The object key is URL-encoded (a space becomes +) | Decode it (for example urllib.parse.unquote_plus) before calling GetObject, or keys with spaces fail with NoSuchKey |
| SNS message delivered to SQS | A JSON envelope with your payload as a string in Message | Turn on raw message delivery, or parse body["Message"] |
| Firehose transformation function | Records with recordId and base64 data | Return each record with the same recordId, a result (Ok, Dropped or ProcessingFailed) and base64-encoded data |
DynamoDB numbers and JSON
In Python, TypeDeserializer turns DynamoDB numbers into decimal.Decimal to avoid losing precision, and the standard json module cannot serialize Decimal. Convert them to int or float when the receiver expects JSON numbers; json.dumps(..., default=str) avoids the error but sends numbers as strings.
from boto3.dynamodb.types import TypeDeserializer
from decimal import Decimal
td = TypeDeserializer()
def plain(v):
if isinstance(v, Decimal):
return int(v) if v % 1 == 0 else float(v)
if isinstance(v, dict):
return {k: plain(x) for k, x in v.items()}
if isinstance(v, list):
return [plain(x) for x in v]
return v
item = {k: td.deserialize(v) for k, v in new_image.items()}
payload = json.dumps(plain(item))
A Firehose transformation that returns plain JSON or omits result sends every record to the processing-failed error output, even when the transformation itself worked.
AI-assisted development with Kiro: specs, steering, hooks and reviews
AI-assisted development tools generate, review and improve code from natural-language instructions; on AWS the current tool is Kiro, an agentic IDE and CLI built around specs (planned, reviewable code generation), steering (persistent project context) and hooks (automation on events). Kiro is the AI development tool named in the DVA-C03 in-scope list, and AWS directs users of the older Amazon Q Developer IDE plugins to it.
Specs: spec-driven code generation
A spec turns a feature idea into three documents that the team reviews and edits, phase by phase, before code is written: requirements.md (user stories with acceptance criteria), design.md (architecture and approach) and tasks.md (trackable implementation tasks, run one at a time). A bugfix spec replaces the requirements with bugfix.md, which records the current behaviour, the expected behaviour and the behaviour that must stay unchanged, and then plans the fix with regression checks, which suits defects where quick fixes have broken other flows. A quick spec generates all the documents in one pass without stopping for review at each phase. A vibe session is a free-form chat for exploratory or ad hoc work, with no structured plan.
Steering files: conventions the agent always follows
Steering files are Markdown files in .kiro/steering/ (workspace) or ~/.kiro/steering/ (global) that give the agent lasting knowledge of your conventions, such as a logging library, error-handling style or naming rules, so developers stop repeating them in chat. Front matter sets the inclusion mode:
| Inclusion mode | Loaded when |
|---|---|
always (default) | Every interaction |
fileMatch | Only when working with files matching fileMatchPattern, for example services/**/*.py |
manual | Only when a developer references it in chat with #name |
auto | When the request matches the file's description |
Agent hooks: automated reviews, tests and scans
A hook pairs a trigger with an action (an agent prompt or a shell command). Triggers include file events (File Save, File Create, File Delete), Prompt Submit, Agent Stop (after the agent finishes its turn), Pre and Post Task Execution for spec tasks, tool-use events and manual triggers. Pick the trigger by when the work you want checked exists and how much of it the hook should see: file events fire per file (File Create only for new files, File Save for every edit to any file); Agent Stop fires once per turn, after all of that turn's changes are made; Pre Task Execution fires before a spec task starts and Post Task Execution after it ends. For example, updating a changelog with everything the agent touched in a turn fits Agent Stop, and running a formatter on each saved file fits File Save. Hooks act; steering only informs what the agent writes, so it never checks anything after the fact.
Supervised vs Autopilot
In Autopilot (the default) the agent works end to end and you review or revert the changes afterwards. In Supervised mode Kiro pauses after each turn that contains file edits and shows them as hunks you accept, reject or discuss before it continues.
Reviewing AI output like any other change
Beyond agent tasks, AI assistants help in two everyday ways named in the exam guide. Intelligent code completion offers the next lines inline as you type, drawing on the open file and project context; you accept a suggestion only after reading it, exactly as you would review a colleague's line. Refactoring suggestions come from asking the assistant to restructure existing code (extract a function, rename across files, replace a hand-written loop with an SDK paginator); the change arrives as a diff you inspect, run the tests against and accept or reject. Automated code reviews and security scanning (often wired up as hooks) round out the set, but AI output still goes through the team's controls. Controls at a developer's desk (steering, local hooks, Supervised mode) depend on that developer. Controls that cannot be bypassed sit on the shared path: branch protection that requires an approved pull request, and a pipeline stage (for example AWS CodeBuild in AWS CodePipeline) that runs static analysis and dependency scanning and fails the build on findings. Using AI tools for tests and CI/CD is covered in the Domain 3 lessons.
Integrating managed AWS AI services: calling Amazon Bedrock from code
Applications use foundation models on Amazon Bedrock through the bedrock-runtime API: Converse and ConverseStream for a model-agnostic messages interface, InvokeModel and InvokeModelWithResponseStream for a model-specific request body, and batch inference for large offline jobs.
| Operation | Request shape | IAM action required | Use when |
|---|---|---|---|
Converse | Same messages structure for every supported model | bedrock:InvokeModel | You want to switch models by changing only the model ID |
ConverseStream | Same as Converse; tokens stream back as they are generated | bedrock:InvokeModelWithResponseStream | Users should see text appear without waiting for the full answer |
InvokeModel | Body in the model provider's own format | bedrock:InvokeModel | You need a provider-specific feature or format |
InvokeModelWithResponseStream | Provider format, streamed | bedrock:InvokeModelWithResponseStream | Streaming with a provider-specific body |
The streaming action is separate: a role written for Converse fails with AccessDeniedException when the code switches to ConverseStream until bedrock:InvokeModelWithResponseStream is allowed on the model's ARN.
brt = boto3.client("bedrock-runtime")
resp = brt.converse(
modelId=MODEL_ID,
messages=[{"role": "user",
"content": [{"text": prompt}]}],
)
text = resp["output"]["message"]["content"][0]["text"]
Throttling and offline workloads
On-demand Bedrock calls are subject to per-account quotas, so handle ThrottlingException with SDK retries (backoff with jitter, adaptive mode for high-volume clients). For large jobs that do not need immediate answers, such as summarising a month of archived call transcripts, use batch inference: write the inputs as JSONL records to S3 and submit a job with CreateModelInvocationJob, naming the model, the S3 input and output locations and a service role that can access them. Bedrock processes the job asynchronously and writes results to S3, at a lower price than on-demand calls for supported models. Provisioned Throughput is a different tool: it reserves model capacity for steady, high-volume real-time traffic.
Agents
Agents hosted on Amazon Bedrock AgentCore are called from application code through the bedrock-agentcore client (for example InvokeAgentRuntime for an agent deployed to AgentCore Runtime), authorized with IAM like any other AWS API.
Amazon Quick, also on the in-scope AI list, is AWS's agentic workspace for business users (chat agents, research, dashboards and automations) rather than an API you build into application code; for this task, application code reaches foundation models through Bedrock.
Choosing a foundation model, prompt engineering and retrieval-augmented generation architecture are out of scope for this exam. Securing AI integrations (private connectivity, guardrails, PII handling, agent tool authorization) is Task 2.3.
Tip. Task 1.1 questions are developer scenarios: an application misbehaves (timeouts, lost or duplicated work, throttling, parse errors, AccessDeniedException) or a team states a requirement with a constraint such as no code changes, no Lambda or least operational overhead, and you choose the configuration or code change that meets it. Expect near-twin options from the same service family: SQS vs SNS vs EventBridge, one shared queue vs a queue per consumer, standard vs adaptive retry mode, a longer visibility timeout vs an idempotency key, proxy vs non-proxy integrations, REST vs HTTP APIs, a DLQ vs archive and replay, an input transformer vs an event pattern, more shards vs a better partition key or enhanced fan-out, Converse vs InvokeModel, and Kiro steering vs hooks vs specs with their inclusion modes and triggers. Over a quarter of items are multiple response and often require combining two facts, such as a partition key plus a parallelization factor, an IAM role plus a mapping template, or a filter scope plus a nested policy. Some stems include short Python SDK code or JSON event payloads, so read what the code actually does with the data format it receives.
- SQS buffers work for competing consumers; SNS fans one message out to every subscriber; one SQS queue per consumer behind an SNS topic gives independent, buffered fan-out.
- Keep only what the caller needs right now synchronous; API Gateway's integration timeout is 29 seconds by default, so long work goes to a queue, an event or an async Lambda invocation (X-Amz-Invocation-Type: 'Event' in a non-proxy integration).
- Backoff needs jitter to break synchronized retries; adaptive retry mode adds client-side rate limiting on top of standard retries.
- AWS messaging is at-least-once: make consumers idempotent with a stable key (business ID or EventBridge event id) recorded by a conditional write.
- REST APIs validate bodies with JSON Schema models and transform with VTL mapping templates in non-proxy integrations; HTTP APIs support neither.
- Use paginators for list calls, waiters for resource readiness, and catch only the specific error code you can handle.
- EventBridge: filter in the event pattern (numeric ranges included), DLQ for failed deliveries, archive and replay (optionally to specific rules) for reprocessing.
- S3 notifications go to SNS standard, SQS standard, Lambda or EventBridge; keys arrive URL-encoded; use EventBridge to reach FIFO queues and prefix/suffix filters to avoid loops.
- Kinesis ordering is per shard via the partition key; high-cardinality keys avoid hot shards; enhanced fan-out gives each app its own read throughput; Firehose buffers and delivers without consumer code.
- Kiro specs plan before code, steering holds conventions, hooks automate checks; Bedrock Converse is model-agnostic, ConverseStream needs bedrock:InvokeModelWithResponseStream, and batch inference suits offline jobs.
Frequently asked questions
When should I use SQS instead of SNS to decouple services?
Use an Amazon SQS queue when each message should be processed by exactly one consumer and work must be buffered while the consumer is slow or down, for example a pool of workers sharing jobs. Use an Amazon SNS topic when every subscriber must receive each message. To give several consumers their own copy and their own pace, combine them: publish to SNS and subscribe one SQS queue per consumer.
Why do my retries keep getting throttled even with exponential backoff?
Exponential backoff without jitter keeps clients synchronized: they all fail at the same moment and retry on the same schedule, so the retries arrive in waves that are throttled again. Adding random jitter to each delay spreads them out. If a single high-volume client is still throttled, the AWS SDK's adaptive retry mode adds client-side rate limiting that slows the rate of new requests.
How do I stop a Lambda function or worker from processing the same message twice?
You cannot stop duplicate delivery entirely, because SQS standard queues, SNS, EventBridge and S3 notifications deliver at least once. Make the processing idempotent instead: take a stable key such as the order ID or the EventBridge event id, record it with a DynamoDB conditional write (attribute_not_exists) before the side effect, and skip the work when the condition fails.
Can an Amazon API Gateway HTTP API validate request bodies?
No. Request validation with JSON Schema models and VTL mapping templates are REST API features. HTTP APIs offer parameter mapping and native JWT authorizers but no body validation, so an API that relies on API Gateway to reject invalid bodies with HTTP 400 before the backend runs should stay on a REST API or add validation code.
Why does my Lambda function get NoSuchKey for some S3 objects that exist?
Object keys in S3 event notifications are URL-encoded, so a key such as summer menu.pdf arrives as summer+menu.pdf. Passing it straight to GetObject asks for a key that does not exist. Decode the key first, for example with urllib.parse.unquote_plus in Python.
What is the difference between Kinesis Data Streams and Amazon Data Firehose?
Kinesis Data Streams is a stream that your own consumers read in real time, with ordering per partition key and the ability to re-read recent data. Amazon Data Firehose is a delivery service: it buffers incoming records by size and time and writes them to destinations such as Amazon S3 with no consumer code, optionally transforming them with a Lambda function on the way.
What is spec-driven development in Kiro?
Spec-driven development is Kiro's way of planning AI-generated code before writing it. A spec produces requirements.md (user stories and acceptance criteria), design.md and tasks.md, which the team reviews phase by phase; a bugfix spec records current, expected and unchanged behaviour in bugfix.md instead. The agent then implements the tasks one at a time, while steering files supply project conventions and hooks run checks automatically.
Should I call Amazon Bedrock with Converse or InvokeModel?
Use the Converse API (or ConverseStream for streamed output) when you want one request and response format across models, so switching models only changes the model ID. Use InvokeModel when you need a model provider's own request format. Converse is authorized by bedrock:InvokeModel and ConverseStream by bedrock:InvokeModelWithResponseStream. For large offline jobs, use batch inference instead of per-item calls.
Source
This lesson covers the "Development with AWS Services" domain of the official DVA-C03 exam guide. Vendors revise their guides — check the source for the current version.
- AWS Certified Developer – Associate (DVA-C03) exam guide — Amazon Web Services
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.