One bid through the rings

Pinch or Ctrl+scroll to zoom; the text is selectable. Source · TheYard

One bid through TheYard's rings: the endpoint asks Application, Application asks Domain, Domain decides, Application saves through the IBidStore port, an adapter writes the store, and the endpoint answers One bid through the rings Left to right is outside to inside. Solid arrows are calls the code makes in order; the dashed arrow is the one call that crosses outward, at runtime, through the port. Outside: the host and the adapters api/TheYard.Api and the two adapter projects Application: the use cases and the port api/TheYard.Application Domain: the rules api/TheYard.Domain BidEndpoints.HandleBid Endpoints/BidEndpoints.cs POST /api/vehicles/{id}/bids, signed in. Is this session on this store? If not, 401. Does the vehicle exist (Auction.Find)? If not, 404. Reads the server clock once: Clocks.Now(). Hands the vehicle, the amount and that clock in. Writes the answer: 400 with the reason, or 200 with the bid and the vehicle as it now stands. EfBidStore or CosmosBidStore EfSources.cs and CosmosSources.cs Each implements IBidStore against one store. EF Core: one row per buyer per vehicle, in Azure SQL Database or SQLite. Cosmos DB SDK: one document in the buyer's partition. Composition/StoreRegistration.cs picks which. Auction.PlaceBidAsync Auction.cs The one class endpoints ask about the auction. Passes the bid to the service that keeps bids. BidService.PlaceBidAsync BidService.Bidding.cs Takes the gate: one bid at a time. Folds everybody's bids into the vehicle, so the rules see the price as it stands. Asks Domain, then on yes writes the store first and memory second. IBidStore, the port Ports.cs Declared here, in the inner ring: the shape of what Application needs, and nothing about SQL or Cosmos DB. LoadAsync, SaveAsync(userId, vehicleId, state), ClearAsync(userId). BidRules.ResolveBid BidRules.cs Sold to anybody already? Refused. Live and at or above buy-now? Won, at the buy-now price. Not started, or ended? Refused. Below the minimum next bid? Refused. Otherwise accepted, as a BidOutcome. No store, no web, no clock of its own. Held by OnionTests: Domain_never_reaches_the_disk_ the_network_or_the_clock Endpoints_reach_the_auction_only_ through_the_Application_ring 1 2 3 4 5 6 runs it implements 7 1 The endpoint asks Application, with the server clock as a value. 2 Auction hands the bid to BidService, which keeps everybody's bids. 3 BidService asks Domain whether the bid stands. 4 Domain answers with a BidOutcome and touches nothing else. 5 On yes, BidService saves through IBidStore, the port it owns. 6 The call reaches whichever adapter Composition wired in at startup. 7 The outcome travels back out and the endpoint writes the answer. Source: docs/images/bid-walk.svg in the repository, drawn by docs/images/bid-walk.mjs and redrawn when the bid's path changes.