Epic: what a microservices estate needs from this API
- Dominant language
- C#
- Stars
- 6
- Forks
- 7
- Avg merge
- 4h 21m
- Merged PRs (30d)
- 307
Description
Eleven issues across three milestones are the same question asked by one person: an architect deciding whether this API can sit inside their estate. Filed separately, they read as unrelated. This is the sentence that orders them.
**The positioning is not that barakoCMS is a microservice.** It is a modular monolith on one Postgres, deliberately, and that is the right shape for something self-hosted. The claim worth being able to make is that it is a well behaved participant: it publishes events, it shows up in traces, it can be scoped and rate limited per consumer, and its contract is versioned. That is how Stripe and Auth0 are positioned, and nobody asks whether they are microservices.
## What already holds up
Worth writing down, because the gap list below reads worse without it.
- **A contract version on every response**, including a 401. `X-Api-Contract-Version`, from `ApiContract`, and readable cross origin since #685. Most APIs have nothing like this.
- **Health and readiness that an orchestrator understands**, with a seed gate that holds readiness closed until the data seeder finishes rather than answering ready and then failing every request.
- **Machine to machine keys** that act as their owner and cannot exceed them.
- **Signed outbound webhooks**: HMAC over `{timestamp}.{body}`, a 300 second tolerance, and a delivery record per attempt with retention.
- **Idempotency keys**, and a published OpenAPI document.
## The gaps, in the order they get asked about
**1. Nothing shows up in a trace. #691**
`grep -r 'OpenTelemetry\|ActivitySource\|traceparent'` over the codebase returns nothing. A caller that sends a `traceparent` gets no span, and a request crossing three services cannot be followed. `CorrelationIdMiddleware` reaches the log and stops (#624).
First, and not because it is the most interesting. It is the cheapest (ASP.NET Core, `HttpClient`, Npgsql and Marten all emit `Activity` already, so most of the work is registration and an export decision) and it is the question asked in the first hour of an evaluation. Outbound propagation is the half people forget: `Connectors`, `RequestAction` and `WebhookAction` all call third parties, and a trace that stops at our boundary is half a trace.
**2. The event stream is single instance. #566, then #688**
`ContentChangeListener` is a Marten session listener, so it fans out on the instance that committed the write. Its own remarks say so. A subscriber on instance A never sees a publish through instance B, which means the stream is not usable in any deployment that runs more than one. #688 explores whether a broker is the answer and whether that is a module or core.
**3. Polling is all or nothing. #549, #560**
No `ETag`, no `If-None-Match`, and `DeliveryQuery.Parse` allowlists only `def.Fields`, so `updatedAt` cannot be filtered or sorted. A consumer that wants to stay current has to fetch everything, every time. The sitemap already caps at 50,000, so this is a ceiling with a number on it.
**4. One consumer can take the API down. #563**
No rate limiting on delivery, and an API key has no quota. Acceptable when the only consumer is a console you wrote. Not acceptable when a service you do not control retries in a loop.
**5. A key cannot be scoped to one job. #653**
The scope layer treats erase and rollback as ordinary writes. A service that needs to read content should not hold a key that can erase it.
**6. A retry does not replay. #612**
An idempotent replay answers 409 rather than returning the original response, and keys never expire. Retrying is the normal behaviour of every message bus and HTTP client in an estate, so this is the difference between safe and surprising.
**7. Nothing can be received. #690**
Outbound is considered. Inbound has no story at all, and one already exists in a corner: `BarakoCMS.Email.Resend` has a bespoke endpoint with its own signature verification because it had to.
**8. Reads only scale by growing the primary. #564**
Later than the rest, and honest to name.
## Order
Do **#691, #566, #549, #563**. That is the set that moves this from integrable if you are patient to integrable properly. Three are already in 4.1.0. Tracing and rate limiting are what an architect checks before committing; the event stream and conditional reads are what make it pleasant afterwards.
Then #653, #612 and #624.
Leave #688, #690 and #564 as explorations. A broker and inbound webhooks are the ambitious half and they are worth less than tracing, because a consumer who cannot debug across the boundary will not get as far as needing them.
## Done when
An architect can answer yes to all of: does it publish changes I can subscribe to, does it appear in my traces, can I scope a credential to one service, can I stop one consumer starving the others, and is the contract versioned. Four of those five are gaps today and one is already true.
Tracks: #691, #566, #688, #549, #560, #563, #653, #612, #624, #690, #564.
Contributor guide
Research direction
Start with the ordered tracks #691, #566, #549 and #563, then read the named entry points such as CorrelationIdMiddleware, ContentChangeListener and DeliveryQuery.Parse. Run the stated grep for OpenTelemetry, ActivitySource and traceparent to assess the tracing gap. Done means the five architect-facing questions in the issue can be answered yes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, postgresql
- Domain
- backend-api-design, databases, distributed-systems, observability
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100