10 KiB
Start by treating docs/router/specs.md as law. Nothing gets coded that contradicts it. The first sprint or two should be about wiring the skeleton and proving the core flows with the simplest possible transport, then layering in the real transports and migration paths.
I’d structure the work for your agents like this.
0. Read & freeze invariants
All agents:
-
Read
docs/router/specs.mdend to end. -
Extract and pin the non-negotiables:
- Method + Path identity.
- Strict semver for versions.
- Region from
GatewayNodeConfig.Region(no host/header magic). - No HTTP transport for microservice communications.
- Single connection carrying HELLO + HEARTBEAT + REQUEST/RESPONSE + CANCEL.
- Router treats body as opaque bytes/streams.
RequiringClaimsreplaces any form ofAllowedRoles.
Agree that these are invariants; any future idea that violates them needs an explicit spec change first.
1. Lay down the solution skeleton
“Skeleton” agent (or gateway core agent):
Create the basic project structure, no logic yet:
src/__Libraries/StellaOps.Router.Commonsrc/__Libraries/StellaOps.Router.Configsrc/__Libraries/StellaOps.Microservicesrc/StellaOps.Gateway.WebServicedocs/router/already hasspecs.md(add placeholders for the other docs).
Goal: everything builds, but most classes are empty or stubs.
2. Implement the shared core model (Common)
Common/core agent:
Implement only the data and interfaces, no behavior:
-
Enums:
TransportType,FrameType,InstanceHealthStatus.
-
Models:
ClaimRequirementEndpointDescriptorInstanceDescriptorConnectionStateRoutingContext,RoutingDecisionPayloadLimits
-
Interfaces:
IGlobalRoutingStateIRoutingPluginITransportServerITransportClient
-
Framestruct/class:FrameType,CorrelationId,Payload(byte[]).
Leave implementations of IGlobalRoutingState, IRoutingPlugin, transports, etc., for later steps.
Deliverable: a stable set of contracts that gateway + microservice SDK depend on.
3. Build a fake “in-memory” transport plugin
Transport agent:
Before UDP/TCP/Rabbit, build an in-process transport:
InMemoryTransportServerandInMemoryTransportClient.- They share a concurrent dictionary keyed by
ConnectionId. - Frames are passed via channels/queues in memory.
Purpose:
- Let you prove HELLO/HEARTBEAT/REQUEST/RESPONSE/CANCEL semantics and routing logic without dealing with sockets and Rabbit yet.
- Let you unit and integration test the router and SDK quickly.
This plugin will never ship to production; it’s only for dev tests and CI.
4. Microservice SDK: minimal handshake & dispatch (with InMemory)
Microservice agent:
Initial focus: “connect and say HELLO, then handle a simple request.”
-
Implement
StellaMicroserviceOptions. -
Implement
AddStellaMicroservice(...):- Bind options.
- Register endpoint handlers and SDK internal services.
-
Endpoint discovery:
- Implement runtime reflection for
[StellaEndpoint]+ handler types. - Build in-memory
EndpointDescriptorlist (simple: no YAML yet).
- Implement runtime reflection for
-
Connection:
-
Use
InMemoryTransportClientto “connect” to a fake router. -
On connect, send a HELLO frame with:
- Identity.
- Endpoint list and metadata (
SupportsStreamingfalse for now, simpleRequiringClaimsempty).
-
-
Request handling:
-
Implement
IRawStellaEndpointand adapter to it. -
Implement
RawRequestContext/RawResponse. -
Implement a dispatcher that:
- Receives
Requestframe. - Builds
RawRequestContext. - Invokes the correct handler.
- Sends
Responseframe.
- Receives
-
Do not handle streaming or cancellation yet; just basic request/response with small bodies.
5. Gateway: minimal routing using InMemory plugin
Gateway agent:
Goal: HTTP → in-memory transport → microservice → HTTP response.
-
Implement
GatewayNodeConfigand bind it from config. -
Implement
IGlobalRoutingStateas a simple in-memory implementation that:- Holds
ConnectionStateobjects. - Builds a map
(Method, Path)→ endpoint + connections.
- Holds
-
Implement a minimal
IRoutingPluginthat:- For now, just picks any connection that has the endpoint (no region/ping logic yet).
-
Implement minimal HTTP pipeline:
-
EndpointResolutionMiddleware:(Method, Path)→EndpointDescriptorfromIGlobalRoutingState.
-
Naive authorization middleware stub (only checks “needs authenticated user”; ignore real requiringClaims for now).
-
RoutingDecisionMiddleware:- Ask
IRoutingPluginfor aRoutingDecision.
- Ask
-
TransportDispatchMiddleware:- Build a
Requestframe. - Use
InMemoryTransportClientto send and awaitResponse. - Map response to HTTP.
- Build a
-
-
Implement HELLO handler on gateway side:
-
When InMemory “connection” from microservice appears and sends HELLO:
- Construct
ConnectionState. - Update
IGlobalRoutingStatewith endpoint → connection mapping.
- Construct
-
Once this works, you have end-to-end:
- Example microservice.
- Example gateway.
- In-memory transport.
- A couple of test endpoints returning simple JSON.
6. Add heartbeat, health, and basic routing rules
Common/core + gateway agent:
Now enforce liveness and basic routing:
-
Heartbeat:
- Microservice SDK sends HEARTBEAT frames on a timer.
- Gateway updates
LastHeartbeatUtcandStatus.
-
Health:
-
Add background job in gateway that:
- Marks instances Unhealthy if heartbeat stale.
-
-
Routing:
-
Enhance
IRoutingPluginto:- Filter out Unhealthy instances.
- Prefer gateway region (using
GatewayNodeConfig.Region). - Use simple
AveragePingMsstub from request/response timings.
-
Still using InMemory transport; just building the selection logic.
7. Add cancellation semantics (with InMemory)
Microservice + gateway agents:
Wire up cancellation logic before touching real transports:
-
Common:
- Extend
FrameTypewithCancel.
- Extend
-
Gateway:
-
In
TransportDispatchMiddleware:- Tie
HttpContext.RequestAbortedto aSendCancelAsynccall. - On timeout, send CANCEL.
- Tie
-
Ignore late
Response/stream data for canceled correlation IDs.
-
-
Microservice:
- Maintain
_inflightmap of correlation →CancellationTokenSource. - When
Cancelframe arrives, callcts.Cancel(). - Ensure handlers receive and honor
CancellationToken.
- Maintain
Prove via tests: if client disconnects, handler stops quickly.
8. Add streaming & payload limits (still InMemory)
Gateway + microservice agents:
-
Streaming:
-
Extend InMemory transport to support
RequestStreamData/ResponseStreamDataframes. -
On the gateway:
- For
SupportsStreamingendpoints, pipe HTTP body stream → frame stream. - For response, pipe frames → HTTP response stream.
- For
-
On microservice:
- Expose
RawRequestContext.Bodyas a stream reading frames as they arrive. - Allow
RawResponse.WriteBodyAsyncto stream out.
- Expose
-
-
Payload limits:
-
Implement
PayloadLimitsenforcement at gateway:- Early reject large
Content-Length. - Track counters in streaming; trigger cancellation when exceeding thresholds.
- Early reject large
-
Demonstrate with a fake “upload” endpoint that uses IRawStellaEndpoint and streaming.
9. Implement real transport plugins one by one
Transport agent:
Now replace InMemory with real transports:
Order:
-
TCP plugin (easiest baseline):
- Length-prefixed frame protocol.
- Connection per microservice instance (or multi-instance if needed later).
- Implement HELLO/HEARTBEAT/REQUEST/RESPONSE/STREAM/CANCEL as per frame model.
-
Certificate (TLS) plugin:
- Wrap TCP plugin with TLS.
- Add configuration for server & client certs.
-
UDP plugin:
- Single datagram = single frame; no streaming.
- Enforce
MaxRequestBytesPerCall. - Use for small, idempotent operations.
-
RabbitMQ plugin:
- Add exchanges/queues for HELLO/HEARTBEAT and REQUEST/RESPONSE.
- Use
CorrelationIdproperties for matching. - Guarantee at-most-once semantics where practical.
While each plugin is built, keep the core router and microservice SDK relying only on ITransportClient/ITransportServer abstractions.
10. Add Router.Config + Microservice YAML integration
Config agent:
-
Implement
__Libraries/StellaOps.Router.Config:- YAML →
RouterConfigbinding. - Services, endpoints, static instances, payload limits.
- Hot-reload via
IOptionsMonitor/ file watcher.
- YAML →
-
Implement microservice YAML:
- Endpoint-level overrides only (timeouts, requiringClaims, SupportsStreaming).
- Merge logic: code defaults → YAML override.
-
Integrate:
-
Gateway uses RouterConfig for:
- Defaults when no microservice registered yet.
- Payload limits.
-
Microservice uses YAML to refine endpoint metadata before sending HELLO.
-
11. Build a reference example + migration skeleton
DX / migration agent:
-
Build a
StellaOps.Billing.Microserviceexample:- A couple of simple endpoints (GET/POST).
- One streaming upload endpoint.
- YAML for requiringClaims and timeouts.
-
Build a
StellaOps.Gateway.WebServiceexample config around it. -
Document the full path:
- How to run both locally.
- How to add a new endpoint.
- How cancellation behaves (killing the client, watching logs).
- How payload limits work (try to upload too-large file).
-
Outline migration steps from an imaginary
StellaOps.Billing.WebServiceusing the patterns inMigration of Webservices to Microservices.md.
12. Process guidance for your agents
-
Do not jump to UDP/TCP immediately. Prove the protocol (HELLO/HEARTBEAT/REQUEST/RESPONSE/STREAM/CANCEL), routing, and limits on the InMemory plugin first.
-
Guard the invariants. If someone proposes “just call HTTP between services” or “let’s derive region from host,” they’re violating spec and must update
docs/router/specs.mdbefore coding. -
Keep Common stable. Changes to
StellaOps.Router.Commonmust be rare and reviewed; everything else depends on it. -
Document as you go. Every time a behavior settles (e.g. status mapping, frame layout), update the docs under
docs/router/so new agents always have a single source of truth.
If you want, next step I can convert this into a task board (epic → stories) per repo folder, so you can assign specific chunks to named agents.