Skip to main content
Reality Oracle
OverviewCitizensReality FeedVerified ActorsIntel SubscribersMarket PredictionInvestment FundsHow It WorksGates
Products
Backtest ForensicsLiving MapDrift AlertsPath QueriesCustom AxesRisk Maps
Solutions
Compliance OfficersRisk Managers / CROPortfolio ManagersCTOs & Engineering
Enterprise
Enterprise OverviewReality Oracle · SuperappThe AppsArchitectureAudit ManifestTrust CenterPricing
Developers
DocumentationAPI ReferenceSDKsArchitectureAudit Manifest
Resources
BlogUse CasesCustomer StoriesPressChangelog
Company
AboutFoundersCareersContact
Trust
Trust Center
More
PricingJoin Waitlist
··10 min read

Slice-owned ports: when a table belongs to one use-case

The deino-scry pattern — a private table gets a trait + adapter in its own crate.

Slice-owned ports: when a table belongs to one use-case

Not every table is cross-cutting. Some belong to one slice and one slice only, and forcing them into the shared ledger couples slices that have no business knowing each other. The slice-owned port is the answer: a trait and its adapter, colocated in the slice own crate, behind the same port rule.

INV-6 says persistence is always through a port, never a concrete Pg* or PgPool. Cross-cutting repositories live in deino-ledger. But a use-case slice that owns a private table — one no other slice reads — would, if it put that table repository in deino-ledger, be forcing every other slice to depend on a repository it does not use. That is coupling dressed as sharing. The slice-owned port is the pattern that keeps the port rule without creating false coupling: the trait and its Pg* adapter live in the slice own crate, and the slice coordinator or worker holds an Arc<dyn Store>.

deino-scry is the reference

deino-scry is the sentiment slice. It owns a private table for sentiment series, and it defines SentimentReader — a trait — plus PgSentimentReader, the adapter that talks to Postgres, both in the deino-scry crate. The worker holds Arc<dyn SentimentReader>. The composition root wires the Pg adapter into that Arc; the test wires a mock. No other crate imports SentimentReader, because no other crate needs to read the sentiment table. The port rule holds — the slice never touches sqlx for behavior, only its adapter module does — and the coupling stays inside the slice.

A self-contained slice: the trait and the adapter live together, behind the same port. The rest of the workspace never sees the table.
A self-contained slice: the trait and the adapter live together, behind the same port. The rest of the workspace never sees the table.

The other two: deino-loom and deino-whisper

The same pattern appears in deino-loom — LoomStore is the port, LoomPersistence is the adapter, both in the deino-loom crate — and in deino-whisper — WhisperStore is the port, PgWhisperStore is the adapter, both in deino-whisper. Each slice owns a table that only it reads, defines its own port, and holds the Arc<dyn Store> at the coordinator or worker. The rule is identical across all three: depend on the trait, never the pool. The only thing that varies is where the trait lives.

A cross-cutting table goes in the shared ledger. A private table stays in its slice. Both are behind a port — the rule does not move, only the address does.

  • deino-scry: SentimentReader + PgSentimentReader — the reference pattern.
  • deino-loom: LoomStore + LoomPersistence — orchestration owns its persistence.
  • deino-whisper: WhisperStore + PgWhisperStore — the webhook delivery worker port.

Why not put everything in deino-ledger

The temptation is to put every repository in deino-ledger for uniformity. The cost is that every slice that imports deino-ledger for a cross-cutting repo — say, the actor repository — also transitively sees the sentiment repository, the whisper queue, and the loom persistence, tables it has no reason to know exist. That is not a performance problem; it is a readability and coupling problem. A slice that does not read the sentiment table should not be able to import its repository. The slice-owned port keeps the dependency graph honest: you can only depend on what you actually use.

What the port buys the slice

A slice-owned port is not a loophole in the port rule; it is the port rule applied to a table only one slice owns. The slice gets the same benefit as a cross-cutting repo: tests swap the adapter for a mock, the domain never touches the pool, and the manifest records the adapter that ran without coupling the domain to storage. The only difference is locality — the trait and adapter live next to the code that uses them, not in a shared crate. That locality is the point: a private table is a private concern, and the architecture lets it stay one. We are building the POC; the three slice-owned ports are built, and the pattern is the reference for any new slice that owns a private table.

The port rule does not say where the trait lives. It says the domain depends on the trait, never the pool. A slice-owned port is the rule applied to a table only one slice owns.

— slice-owned port note
§Keep reading