Skip to content

feat(sns): named-broker and broker-per-tenant support (GH-3305)#3317

Merged
jeremydmiller merged 1 commit into
mainfrom
feat/sns-named-and-tenant-3305
Jul 6, 2026
Merged

feat(sns): named-broker and broker-per-tenant support (GH-3305)#3317
jeremydmiller merged 1 commit into
mainfrom
feat/sns-named-and-tenant-3305

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #3305

Brings the AWS SNS transport (Wolverine.AmazonSns) up to multi-broker parity with SQS/Kafka/RabbitMQ/Azure Service Bus. SNS previously had neither feature. This PR adds both.

Named broker

  • Add public AmazonSnsTransport(string protocol) ctor (required by TransportCollection.GetOrCreate<T>(BrokerName)); the parameterless ctor delegates to it.
  • Fix the topic URI scheme: AmazonSnsTopic built its Uri from a hard-coded "sns" literal, so a named broker's topics carried scheme sns while Protocol was the name → findEndpointByUri mismatch. The scheme is now built from parent.Protocol (mirroring AmazonSqsQueue).
  • New API: AddNamedAmazonSnsBroker(BrokerName[, Action<AmazonSimpleNotificationServiceConfig>]), ToSnsTopicOnNamedBroker(BrokerName, string), UseAmazonSnsTransportLocallyAsNamedBroker(BrokerName, int port = 4566).

SNS↔SQS pairing caveat (design note)

TransportCollection keys transports by Protocol, so a named SNS broker and a same-named SQS broker cannot coexist under one BrokerName (they'd evict each other) — the named SNS owns that key. Since the SNS-side SQS is used only internally (the SNS client + SNS→SQS subscription/queue-policy provisioning), a named SNS broker pairs with a standalone, unregistered AmazonSqsTransport that ConnectAsync seeds from the SNS connection so it targets the same account/region. A user who also wants to listen on the subscribed queue does so through the default (or a differently-named) SQS broker. The default (unnamed) broker still pairs with the shared default SQS transport as before.

Broker-per-tenant (publish side)

SNS is publish-only (BuildListenerAsync throws NotSupportedException; consumption happens by subscribing an SQS queue to the topic). So broker-per-tenant here needs only a TenantedSendernever a CompoundListener.

  • New AmazonSnsTenant: a child AmazonSnsTransport with its own SnsConfig / CredentialSource / SNS client and its own paired SQS client for subscription provisioning. Compile(parent, runtime) seeds credentials + connection from the parent, applies the tenant's overrides, aligns the paired SQS client to the tenant's final SNS account/region, then builds both clients.
  • AmazonSnsTransport: Tenants cache, per-tenant client build in ConnectAsync, and IAsyncDisposable to dispose tenant clients.
  • AmazonSnsTopic.CreateSender: when tenants exist and the endpoint is tenant-aware, wrap the default in a TenantedSender and register a per-tenant InlineSnsSender bound (via BuildTenantSibling) to each tenant's SNS client. Both the tenant senders and the default fallback are fire-and-forget InlineSnsSender (never BatchedSender), per Message is stuck in outbox with multiple tenants and durable messaging #2361TenantedSender does not forward RegisterCallback, so a batched sender under it would silently drop every message.
  • New config API: AddTenant(string, Action<AmazonSimpleNotificationServiceConfig>) + credentials overload, and TenantIdBehavior(TenantedIdBehavior).

Scope: full per-tenant consumption composes with SQS #3316

This PR implements SNS broker-per-tenant on the send + subscription-provisioning side. Full per-tenant consumption (tenant SNS topic → tenant SQS queue → Wolverine listener) requires SQS tenant listeners, which live in the separate SQS broker-per-tenant PR #3316 (open, not in this base). Each SNS tenant already provisions its subscription/queue-policy through its own paired SQS client that lines up on the same account/region, so the two compose directly.

Tests

Unit (no broker/Docker) — AmazonSnsPerTenantConfigurationTests (8) + the named-broker Uri-scheme assertion: tenant registration/compile/credential inheritance, paired-SQS alignment, tenant-aware default, BuildTenantSibling (deep-copies subscriptions), TenantIdBehavior default/override, and that a named broker's topic Uri scheme is the broker name.

Integration (LocalStack, skip-guarded) — end_to_end_with_named_broker: publish to a named SNS topic → subscribed SQS queue on the default broker → handler. AmazonSnsPerTenantConnectionTests: a tenant message publishes to the tenant region and a default message to the shared region (routing diverges by Envelope.TenantId), plus TenantedSender + per-tenant client resolution.

LocalStack note

Like the SQS PR, tenant separation is simulated via signing region (AuthenticationRegion) on the single LocalStack endpoint, which partitions SNS/SQS per region — LocalStack cannot enforce separate accounts. The two routing tests each provision an observation in only one region and assert the positive: LocalStack community SNS delivers unreliably when a topic of the same name exists in both regions at once, so the pair (tenant→west, default→east) proves divergence without a flaky cross-region negative. The sender wiring is additionally proven broker-free in the unit tests.

Verification

  • dotnet build wolverine.slnx -c Release0 errors, 0 warnings.
  • New SNS tests: 13 passing (8 unit + 5 integration on LocalStack). Existing SNS suites (endpoint-uri, transport, send/receive, inline compliance) still green.

🤖 Generated with Claude Code

Bring the AWS SNS transport up to multi-broker parity with SQS/Kafka/etc.
SNS previously had neither feature.

Named broker:
- Add public AmazonSnsTransport(string protocol) ctor; parameterless delegates to it.
- Build the topic URI scheme from parent.Protocol (was hard-coded "sns"), so a
  named broker's topics carry the broker name as their Uri scheme and match
  findEndpointByUri.
- AddNamedAmazonSnsBroker(BrokerName[, Action<SnsConfig>]),
  ToSnsTopicOnNamedBroker, UseAmazonSnsTransportLocallyAsNamedBroker.
- Because TransportCollection keys transports by Protocol, a named SNS broker and
  a same-named SQS broker cannot coexist. The named SNS's paired SQS (used only
  for the SNS-side client + SNS->SQS subscription provisioning) is therefore a
  standalone, unregistered AmazonSqsTransport seeded from the SNS connection in
  ConnectAsync so it targets the same account/region.

Broker-per-tenant (publish side only; SNS is publish-only):
- New AmazonSnsTenant: child AmazonSnsTransport with its own SnsConfig /
  CredentialSource / SNS client and a paired SQS client for subscription
  provisioning; Compile seeds from the parent then applies tenant overrides.
- AmazonSnsTransport gains a Tenants cache, per-tenant client build in
  ConnectAsync, and IAsyncDisposable to dispose tenant clients.
- AmazonSnsTopic.CreateSender wraps the default in a TenantedSender and registers
  a per-tenant InlineSnsSender bound to each tenant's SNS client via
  BuildTenantSibling. Both default and tenant senders are fire-and-forget
  InlineSnsSender (never BatchedSender) per GH-2361. BuildListenerAsync still
  throws NotSupportedException.
- AddTenant(tenantId, Action<SnsConfig>) + credentials overload, and
  TenantIdBehavior(TenantedIdBehavior) on the configuration.

Full per-tenant CONSUMPTION composes with the SQS broker-per-tenant support
(PR #3316): each SNS tenant provisions its subscription/queue-policy through its
own paired SQS client that lines up on the same account/region.

Tests: unit wiring coverage (no broker) for tenant registration/compile/sender
resolution and the named-broker Uri scheme; LocalStack integration for named
publish -> subscribed SQS queue -> handler, and per-tenant publish routing by
region partition. Docs: named-broker + multi-tenancy sections in sns.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jeremydmiller
jeremydmiller marked this pull request as ready for review July 6, 2026 11:03
@jeremydmiller
jeremydmiller merged commit e600083 into main Jul 6, 2026
24 of 25 checks passed
This was referenced Jul 9, 2026
This was referenced Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AWS SNS: add named-broker and broker-per-tenant support

1 participant