feat: grpc 기능 deep 구현
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
# ADR-GRPC-003: Transport, business and stream evidence are three axes, and none implies another
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2026-08-30
|
||||
- Scope: `:grpc:grpc-core-api`, `:grpc:grpc-policy`, `:grpc:grpc-testkit`
|
||||
|
||||
## Context
|
||||
|
||||
A failed RPC produces a status code, and a status code is not an answer to the question the caller
|
||||
actually has. `DEADLINE_EXCEEDED` on a mutation does not say whether the mutation happened;
|
||||
`UNAVAILABLE` after the request was sent does not say the server never saw it; response headers
|
||||
arriving does not say a transaction committed.
|
||||
|
||||
Every one of those is a place where a plausible inference produces a duplicate write or a lost one,
|
||||
and none of them is visible in a test that only exercises the happy path.
|
||||
|
||||
## Decision
|
||||
|
||||
Model what happened as three independent axes, and refuse the inferences between them.
|
||||
|
||||
`GrpcTransportEvidence` records what the client observed on the wire, and distinguishes `NOT_SENT` —
|
||||
the client watched its own send fail — from `UNOBSERVED`, which is every other case where nothing is
|
||||
known. `GrpcBusinessEvidence` records what the application confirmed, with `COMMIT_UNKNOWN` as a real
|
||||
state rather than a placeholder. `GrpcStreamEvidence` is a sealed hierarchy whose non-empty cases all
|
||||
carry a position, because "partial" without a last sequence can be neither resumed nor reconciled.
|
||||
|
||||
`GrpcExecutionEvidence` holds all three and rejects combinations nobody could have observed: a unary
|
||||
call with stream evidence, or a request the client watched fail to send that nonetheless carries
|
||||
business evidence. Promoting response headers to a confirmed commit is possible only by editing
|
||||
`withResponseHeadersSeen`, which is one method rather than a plausible line in an interceptor.
|
||||
|
||||
`GrpcCompletionOutcome.forMutation` derives what a caller may conclude, and defaults
|
||||
`DEADLINE_EXCEEDED` and post-send `UNAVAILABLE` on a mutation to `COMPLETION_UNKNOWN`.
|
||||
|
||||
The same types are used by the failure model and by the observation convention, so an incident has
|
||||
one account of a call rather than two.
|
||||
|
||||
## Consequences
|
||||
|
||||
**A whole class of retry bug becomes unrepresentable.** `GrpcRetryEligibility` reads all three axes
|
||||
plus the idempotency profile; a caller cannot reach "retry" from a status alone because the status
|
||||
alone is not an input.
|
||||
|
||||
**The fault lane has something to check.** `GrpcTransportEvidenceClassifier` turns a client's
|
||||
observations into evidence and refuses to infer `NOT_SENT` from an unobserved state — and the lane
|
||||
exercises it against a real connection dropped mid-call, not against a mock.
|
||||
|
||||
**Callers must handle a third outcome.** `COMPLETION_UNKNOWN` is not a failure and not a success, and
|
||||
a caller that treats it as either is wrong. `GrpcOperationStatusQuery` and `GrpcCompletionReconciler`
|
||||
exist so that resolving it is a supported path rather than an exercise for the caller.
|
||||
Reference in New Issue
Block a user