The inner ring owns the interface
Pinch or Ctrl+scroll to zoom; the text is selectable.
Source
·
TheYard
The inner ring owns the interface: Application declares IBidStore and uses it, EfBidStore and CosmosBidStore implement it from the outer ring, and the composition root decides which one each store gets
The inner ring owns the interface
Every green arrow crosses a ring and points inward, at IBidStore. Application compiles with neither adapter in it; only the composition root names both sides.
Application: owns the interface
api/TheYard.Application
BidService
BidService.cs, BidService.Bidding.cs
Holds everybody's bids and asks BidRules before any is kept.
Receives its store through the constructor and never learns
which store that is:
public BidService(IBidStore store)
IBidStore, the port
Ports.cs
The shape of what Application needs from somewhere to keep bids,
in Application's own words, with no SQL and no Cosmos DB in it.
Task<IReadOnlyList<StoredBid>> LoadAsync();
Task SaveAsync(string userId, string vehicleId, BidState state);
Task ClearAsync(string userId);
NullBidStore
Ports.cs
The port wired to nothing: bids live in memory. The unit tests use it,
and so does a store that has not come up yet.
Outside: implements the interface
api/TheYard.Infrastructure, .Infrastructure.Cosmos, .Api
EfBidStore
Infrastructure/EfSources.cs
EF Core over Azure SQL Database or SQLite: one row per buyer per
vehicle, replaced in place, three tries on a concurrency conflict.
public sealed class EfBidStore(...) : IBidStore
CosmosBidStore
Infrastructure.Cosmos/CosmosSources.cs
The Cosmos DB SDK: one document per buyer per vehicle, in the buyer's
partition, so every write stays inside one partition.
public sealed class CosmosBidStore(CosmosStore store) : IBidStore
The composition root
Api/Composition/StoreRegistration.cs
The one place that names an adapter. It builds one backend per
store at startup and hands each BidService its adapter:
new BidService(new EfBidStore(contexts))
new BidService(new CosmosBidStore(store))
new BidService(NullBidStore.Instance)
// until the store attaches
implements
implements
calls
hands in the adapter
Held by OnionTests: Application_depends_only_on_Domain_Data_and_the_BCL,
Application_never_reaches_the_disk_or_the_network, Services_are_registered_only_in_the_composition_root,
Endpoints_never_reach_a_store_or_the_service_container.
Source: docs/images/port-adapter.svg in the repository, drawn by docs/images/port-adapter.mjs and redrawn when the port or an adapter changes.