feat: web, websocket 어댑터 추가 구현
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
# ADR-WS-003: STOMP is an Advanced adapter with a declared destination catalog
|
||||
|
||||
- Status: Accepted
|
||||
- Date: 2026-08-25
|
||||
- Scope: `adapter:inbound:websocket` — `advanced.stomp`, `advanced.stomp.rabbit`
|
||||
|
||||
## Context
|
||||
|
||||
Advanced Tasks 9–13 add STOMP 1.2 alongside the platform's own protocol, plus a RabbitMQ broker
|
||||
relay and cross-node user destinations.
|
||||
|
||||
This leaf already ships an older STOMP-over-SockJS channel (`stomp`, gated on
|
||||
`ca-skeleton.websocket.enabled`). Two `@EnableWebSocketMessageBroker` configurations in one context
|
||||
do not conflict loudly — both contribute a configurer, both call `configureMessageBroker`, and the
|
||||
broker that results is whichever ran last. Nothing errors and nothing logs.
|
||||
|
||||
STOMP also brings a destination model that is a free string from the client. Without a catalog, the
|
||||
set of reachable destinations is whatever the broker accepts, which for the simple broker is every
|
||||
string.
|
||||
|
||||
## Decision
|
||||
|
||||
**The Advanced adapter is its own module (`advanced-stomp`), separate from `advanced`.** It is the
|
||||
one Advanced capability that cannot be pure — STOMP here *is* the Spring Messaging types — and
|
||||
folding it into `advanced` would relax that module's purity for every capability in it.
|
||||
|
||||
**The relay is a further module (`advanced-stomp-rabbit`).** The adapter parses a protocol; the
|
||||
relay opens a TCP connection to somebody else's broker and makes every delivery depend on it.
|
||||
Different blast radius, different decision, different module.
|
||||
|
||||
**Destinations are declared, per operation.** `StompDestinationCatalog` maps `(operation,
|
||||
destination)` to a required permission. Undeclared is refused. `SUBSCRIBE` and `SEND` are separate
|
||||
declarations, because reading a feed and publishing into it are different rights.
|
||||
|
||||
**The authorization decision is a value, not an interceptor method.** `StompAuthorizationPolicy`
|
||||
returns a `StompAuthorizationDecision`; `StompSecurityInterceptor` only extracts and enforces. A rule
|
||||
reachable only through a `MessageChannel` gets tested for the cases somebody built a channel for.
|
||||
|
||||
**Only one STOMP runtime may run.** `StompBrokerExclusivity` fails the context when both channels
|
||||
are enabled, when both brokers are, or when the adapter is enabled with no broker behind it.
|
||||
|
||||
**A `RECEIPT` is never promoted to a commit.** `StompEvidence` has six stages and
|
||||
`StompAckPolicy.evidenceForReceipt()` is fixed at `PROTOCOL_RECEIPT`. The receipt is written by the
|
||||
protocol layer, which knows nothing about whether the work succeeded.
|
||||
|
||||
**The simple broker declares what it cannot do.** `SimpleBrokerProfile` cannot be constructed
|
||||
claiming cluster support or durable acks, and refuses activation outside local/test — in a
|
||||
multi-node deployment it does not error, it delivers to whichever fraction of users is on the
|
||||
publishing node.
|
||||
|
||||
**Unresolved user destinations are broadcast once and then dead-lettered.**
|
||||
`MultiNodeUserDestination` distinguishes a message that arrived *via* the broadcast from one that did
|
||||
not. Without that, every node rebroadcasts every unresolvable message on receipt.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Enabling Advanced STOMP requires disabling the legacy channel. There is no migration path that
|
||||
runs both; the exclusivity check makes that explicit at startup rather than at 3am.
|
||||
- A deployment must write its own catalog. There is deliberately no default: an empty one refuses
|
||||
every frame and reads as a broken adapter, and a non-empty one publishes destinations nobody chose.
|
||||
- The relay's cost is one broker connection per authenticated session plus one system connection.
|
||||
`brokerConnectionsFor` exists so this is computed before the first outage.
|
||||
- User-destination metrics are tagged with `UserDestinationAction`, never the destination — a user
|
||||
destination contains a user identifier by construction.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Extend the existing `stomp` package.** Rejected: it is Stable, and WS-ARCH-6 forbids a Stable
|
||||
module naming an Advanced one. Making the legacy channel profile-driven would have required that
|
||||
edge.
|
||||
- **One `advanced-stomp` module including the relay.** Rejected: the relay is a separate operational
|
||||
decision and deserves to be refusable on its own.
|
||||
- **Allow undeclared destinations with a wildcard permission.** Rejected: the wildcard becomes the
|
||||
default and the catalog becomes documentation.
|
||||
Reference in New Issue
Block a user