138 lines
5.3 KiB
Markdown
138 lines
5.3 KiB
Markdown
|
|
# Putting an existing API behind fluid-core
|
||
|
|
|
||
|
|
fluid-core attaches **out of process** (ADR-0002). Your API contributes no code,
|
||
|
|
imports no library, and may be written in any stack. Integration is over a wire
|
||
|
|
contract, so the fact that fluid-core is written in Go is not something you need
|
||
|
|
to care about.
|
||
|
|
|
||
|
|
## What you need
|
||
|
|
|
||
|
|
1. **A running service.** Anything that speaks HTTP. This becomes an *adapter*.
|
||
|
|
2. **A contract.** OpenAPI 3.1 today; the validator interface admits others.
|
||
|
|
3. **An interface evolution intent.** The governance document that bounds how
|
||
|
|
the interface may change.
|
||
|
|
|
||
|
|
Nothing else. In particular you do not need a Kubernetes cluster, a message
|
||
|
|
broker, or Postgres — SQLite is the default evidence store.
|
||
|
|
|
||
|
|
## The five minutes version
|
||
|
|
|
||
|
|
```bash
|
||
|
|
export FLUID_INTERFACE=my-api
|
||
|
|
export FLUID_STORE=$PWD/fluid.db
|
||
|
|
|
||
|
|
# 1. Record the intent that governs this interface.
|
||
|
|
fluid intent put --version IEI-1 --file InterfaceEvolutionIntent.md --activate
|
||
|
|
|
||
|
|
# 2. Publish a revision pointing at your existing service.
|
||
|
|
fluid revision publish --file r1.yaml --ephemeral-key \
|
||
|
|
--adaptation-classes presentation --approved-by "$USER" --traffic-share 1.0
|
||
|
|
|
||
|
|
# 3. Route traffic to it.
|
||
|
|
fluid policy put --file routing-policy.yaml
|
||
|
|
```
|
||
|
|
|
||
|
|
A revision descriptor is small:
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
revision:
|
||
|
|
schema_version: "0.1"
|
||
|
|
id: "R-1"
|
||
|
|
interface: "my-api"
|
||
|
|
state: "stable"
|
||
|
|
contract:
|
||
|
|
type: "openapi"
|
||
|
|
digest: "sha256:<digest of your OpenAPI document>"
|
||
|
|
runtime:
|
||
|
|
upstream: "http://my-existing-service:8080" # your service, unchanged
|
||
|
|
timeout_ms: 5000
|
||
|
|
intent:
|
||
|
|
version: "IEI-1"
|
||
|
|
policy:
|
||
|
|
compatibility: "additive"
|
||
|
|
security_check: "passed"
|
||
|
|
```
|
||
|
|
|
||
|
|
`runtime.upstream` is the whole integration. Everything else is metadata about
|
||
|
|
what that upstream is allowed to be.
|
||
|
|
|
||
|
|
## What you get immediately
|
||
|
|
|
||
|
|
- **Revision identity.** Every response names the revision that served it.
|
||
|
|
- **Telemetry.** Requests, errors, call sequences and adoption, redacted before
|
||
|
|
storage.
|
||
|
|
- **Contract enforcement.** Undeclared paths, methods and fields are refused
|
||
|
|
rather than proxied.
|
||
|
|
- **An audit trail.** `fluid audit trace R-1` reconstructs how a revision came
|
||
|
|
to exist and what happened to it.
|
||
|
|
|
||
|
|
That is FLUID-0 (Instrumented) conformance, and it needs no AI and no change to
|
||
|
|
your service.
|
||
|
|
|
||
|
|
## What you should decide deliberately
|
||
|
|
|
||
|
|
**The pseudonymization salt.** Consumer identities are pseudonymized with HMAC.
|
||
|
|
The salt must be stable for the life of the interface — rotating it makes the
|
||
|
|
same consumer look like a new one and breaks every longitudinal comparison.
|
||
|
|
Store it where you store secrets, not in the repository.
|
||
|
|
|
||
|
|
**The authority mode.** Your intent document declares one, `FLUID-0` through
|
||
|
|
`FLUID-6`. This is a statement about authority, not maturity: a high-assurance
|
||
|
|
interface may deliberately stay at FLUID-2 forever. The unfilled template is
|
||
|
|
refused rather than defaulted, precisely so nobody inherits a permissive mode by
|
||
|
|
accident.
|
||
|
|
|
||
|
|
**Your complexity budget.** `policy.DefaultLimits()` permits only presentation
|
||
|
|
and implementation adaptations, refuses breaking changes, and caps exposure at
|
||
|
|
25%. If your interface needs more, say so explicitly in the intent rather than
|
||
|
|
widening the default.
|
||
|
|
|
||
|
|
## Adding a second revision
|
||
|
|
|
||
|
|
This is where FLUID earns its keep. Publish a second revision pointing at a
|
||
|
|
second adapter — a different build, a different service, a different language —
|
||
|
|
and let the experiment controller split traffic between them:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
fluid revision publish --file r2.yaml --ephemeral-key ...
|
||
|
|
fluid experiment design --file experiment.yaml
|
||
|
|
fluid experiment start E-1 --generation 2 --default-revision R-1 --policy-out rp.json
|
||
|
|
fluid policy put --file rp.json
|
||
|
|
```
|
||
|
|
|
||
|
|
The controller does not touch traffic. It emits a routing policy for you to
|
||
|
|
install, which is what makes an experiment interruptible: stopping one replaces
|
||
|
|
a document rather than unwinding anything.
|
||
|
|
|
||
|
|
## Signing
|
||
|
|
|
||
|
|
`--ephemeral-key` is for development. It generates a throwaway key and warns
|
||
|
|
that revisions signed with it will not verify after a restart, which is exactly
|
||
|
|
what you want to hear before it happens in production.
|
||
|
|
|
||
|
|
In production, generate a key pair, keep the private half in your secret store,
|
||
|
|
and configure the gateway's registry with the public half. The router accepts
|
||
|
|
only descriptors that verify — an unsigned or tampered descriptor is refused,
|
||
|
|
including one that was signed and then edited.
|
||
|
|
|
||
|
|
## What fluid-core will not do for you
|
||
|
|
|
||
|
|
- **It will not modify your backend.** Where a candidate needs capability the
|
||
|
|
backend does not have, FLUID emits a structured requirement and stops. The
|
||
|
|
backend owner accepts, plans, or declares it out of scope.
|
||
|
|
- **It will not decide what "better" means.** There is no universal fitness
|
||
|
|
scalar. You declare primary metrics and guardrails; the evaluator compares
|
||
|
|
against what you declared and refuses to infer criteria from the data.
|
||
|
|
- **It will not promote on thin evidence.** A comparison below the sample floor
|
||
|
|
is inconclusive, not successful. You can override that with an acknowledged
|
||
|
|
flag; the override is recorded as one.
|
||
|
|
|
||
|
|
## Reference
|
||
|
|
|
||
|
|
- `examples/echo-interface/` — the two-revision fixture the conformance suite
|
||
|
|
drives, and the smallest complete example.
|
||
|
|
- `conformance/suite/` — every conformance requirement asserted as a test.
|
||
|
|
`TestFirstVerticalSlice` is the whole loop in one function.
|
||
|
|
- `spec/` — the normative documents. When this guide and the spec disagree, the
|
||
|
|
spec is right and this guide is a bug.
|