Queue with a dead-letter queue¶
The first multi-resource pattern: two resources that always travel together, with the
redrive policy wired between them. It returns a small handle (QueueWithDlq) holding
the logical IDs, ready-made references, and the resources to put in the template.
@dataclass(frozen=True)
class QueueWithDlq:
"""The handle `queue_with_dlq` returns: the logical IDs, how to refer to them, and the resources."""
queue: str
dlq: str
resources: Resources
@property
def queue_arn(self) -> Expr[str]:
return get_att(self.queue, "Arn")
@property
def queue_url(self) -> Expr[str]:
return ref(self.queue)
@property
def dlq_url(self) -> Expr[str]:
return ref(self.dlq)
def queue_with_dlq(
name: str,
*,
visibility_timeout: int = 30,
max_receive_count: int = 3,
tags: list[Expr[Tag]] | None = None,
) -> QueueWithDlq:
"""A queue and its dead-letter queue, with logical IDs `{name}Queue` and `{name}DLQ`."""
queue_id, dlq_id = f"{name}Queue", f"{name}DLQ"
dlq: sqs.QueueProps = {}
main: sqs.QueueProps = {
"VisibilityTimeout": visibility_timeout,
"RedrivePolicy": {"deadLetterTargetArn": get_att(dlq_id, "Arn"), "maxReceiveCount": max_receive_count},
}
if tags:
dlq["Tags"] = main["Tags"] = tags
return QueueWithDlq(queue_id, dlq_id, {dlq_id: sqs.queue(**dlq), queue_id: sqs.queue(**main)})
orders = queue_with_dlq("Order", visibility_timeout=180, tags=tags(Stage="prod"))
orders.queue_arn # GetAtt OrderQueue.Arn
orders.queue_url # Ref OrderQueue
orders.resources renders as:
{
"OrderDLQ": { "Type": "AWS::SQS::Queue", "Properties": { "Tags": [ ... ] } },
"OrderQueue": { "Type": "AWS::SQS::Queue", "Properties": {
"VisibilityTimeout": 180,
"RedrivePolicy": {
"deadLetterTargetArn": { "Fn::GetAtt": ["OrderDLQ", "Arn"] },
"maxReceiveCount": 3
},
"Tags": [ ... ] } }
}
Things worth noticing¶
- The handle is the contract. The caller never rebuilds
"OrderQueue"by hand:orders.queue_arnandorders.queue_urlcome from the same IDs the pattern used for theGetAtt, so the two can never disagree, and a typo is an attribute error. - Typed property dicts, not
**kwargs.dlqandmainare annotatedsqs.QueueProps, so each key and value is checked, and the optionalTagskey is only added when there are tags. Unpacking them with**into the factory keeps the output free of empty lists. - The tags use one list for both queues, which is fine because the list is not mutated afterwards. If you ever do mutate shared data, copy it first.