Build something like this?

Root hub

eShop (AdventureWorks), mapped as three services

The eShop reference app (.NET, MIT), split into one repository per service the way a team would own it: Web Shop, Catalog Service and Ordering Service. ProvenMap analysed each repo and drew them as one system. Open any service to see its layers, or read the insights, such as where placing an order waits on another call. An unofficial map of eShop by the .NET team at Microsoft: github.com/dotnet/eShop.

3 layers9 insightsby Supportv3Published Oct 30, 2026

About this system

Online Store — System Landscape

An online store where shoppers browse a product catalog, add items to a basket, and place orders that are paid and fulfilled.

Systems

SystemKindRole
Web ShoprepoStorefront — the shopper-facing UI. Authenticates via Auth0, queries the Catalog, and submits orders to the Ordering Service.
Catalog ServicerepoManages products, prices, and stock levels.
Ordering ServicerepoHandles basket, checkout, and order lifecycle. Calls Stripe for payment and SendGrid for order confirmation emails.

External services

  • Stripe — payment processing, called by Ordering Service
  • SendGrid — transactional email, called by Ordering Service
  • Auth0 — shopper authentication, used by Web Shop

Actors

  • Shoppers — end users who browse and place orders via the Web Shop

The map

The map · Spine

Architecture

What's on this map
  • Auth0 · auth0
  • Catalog Service · microservice app
  • Ordering Service · microservice app
  • SendGrid · sendgrid
  • Shoppers · actor
  • Stripe · stripe
  • Web Shop · web app

The map · Layers

Layers3

What's on each layer

Catalog Service

Catalog Service — products, prices and stock served to the storefront (dotnet/eShop Catalog.API). C4 system context: container-grade with 1 drill-down (the Catalog API) surrounded by its externally-evidenced neighbours.

  • Catalog API · aspnet core
  • Catalog Database · postgresql
  • Embedding Provider · external integration
  • Event Bus · rabbitmq
  • Ordering Service · actor
  • Storefront · actor

Catalog API

Catalog.API — the ASP.NET Core minimal-API microservice that owns the product catalog: catalog item CRUD/search (including AI vector search), the EF Core/PostgreSQL persistence layer, and integration events published/consumed over RabbitMQ. Board grain: terminal (component-grade) — files were consolidated into domain-coherent container nodes rather than drilled further; no child boards were proposed.

  • Catalog AI (Embedding) Service · service
  • Catalog API Endpoints · api endpoint
  • Catalog API Host & Bootstrap · module
  • Catalog Domain Model · schema model
  • Text Embedding Provider · openai
  • Catalog Event Bus (RabbitMQ) · rabbitmq
  • Catalog Integration Event Handlers · handler component
  • Catalog Integration Event Contracts · event message
  • Catalog Integration Event Service · service
  • Integration Events · domain group
  • Catalog Persistence Layer · repository component
  • Catalog PostgreSQL Database · postgresql
  • Catalog Services Facade · service

Ordering Service

Ordering Service — basket checkout and the order lifecycle (payment, confirmation, shipping) from dotnet/eShop, split into an ASP.NET Core API host, a DDD domain model and an EF Core persistence layer. System context board: container-grade with 3 drill-downs (API, Domain, Infrastructure) and the externally evidenced systems they talk to.

  • Web App / API Client · actor
  • Catalog Service · microservice
  • eShop Shared Libraries · library package
  • RabbitMQ Event Bus · rabbitmq
  • Identity Provider · external integration
  • Ordering API · aspnet core
  • orderingdb (PostgreSQL) · postgresql
  • Ordering Domain · library package
  • Ordering Infrastructure · library package
  • Order Notifications · job worker
  • Order Processor (grace period) · job worker
  • Payment Processor · microservice

Ordering API

Ordering.API: the ASP.NET Core minimal-API host for the Ordering bounded context — order commands/queries via MediatR, domain and integration event handling, and identity/DB-seed infrastructure. Grain: terminal — the group plan for this unit's own files fits comfortably within the L1 band with no further drill-downs proposed.

  • API Endpoints · domain group
  • Api Extensions · module
  • App Bootstrap · module
  • Application Models · module
  • Command Pipeline · domain group
  • Commands · microservice
  • Command Validators · handler component
  • Ordering Context Seed · repository component
  • Domain Event Handlers · handler component
  • Identity Service · service component
  • Infrastructure Utilities · domain group
  • Integration Event Service Contract · service component
  • Integration Events · microservice
  • Integration & Identity · domain group
  • Order Queries · repository component
  • Orders Api · controller component
  • Pipeline Behaviors · handler component
  • Ordering Domain · boundary port
  • Ordering Infrastructure · boundary port

Commands

MediatR CQRS commands and their handlers for the Ordering API — order creation, order status transitions and idempotent-request support. Grain: terminal (group-plan budgetVerdict fits at 18 files against the 5-20 L2 band, no drill-downs proposed).

  • Cancel Order Command · handler component
  • Create Order Command · handler component
  • Create Order Draft Command · handler component
  • Idempotency Support · domain group
  • Identified Command · handler component
  • Order Creation · domain group
  • Order Status Transitions · domain group
  • Api Extensions · boundary port
  • Application Models · boundary port
  • Set Awaiting Validation Order Status Command · handler component
  • Set Paid Order Status Command · handler component
  • Set Stock Confirmed Order Status Command · handler component
  • Set Stock Rejected Order Status Command · handler component
  • Ship Order Command · handler component

Integration Events

Ordering.API's integration-event surface: the events it consumes from other microservices via handlers, the events it publishes to announce order-status changes, and the service that drives the publish/log cycle through the event bus. Terminal grain: the group plan reads budgetVerdict "fits" (18 of 18 files, band 5-20) with no drill-down proposed, so this board stays at component grain.

  • Consumed Integration Events · domain group
  • Event Handlers · domain group
  • GracePeriodConfirmedIntegrationEvent · event message
  • GracePeriodConfirmedIntegrationEventHandler · handler component
  • OrderingIntegrationEventService · service component
  • OrderPaymentFailedIntegrationEvent · event message
  • OrderPaymentFailedIntegrationEventHandler · handler component
  • OrderPaymentSucceededIntegrationEvent · event message
  • OrderPaymentSucceededIntegrationEventHandler · handler component
  • OrderStartedIntegrationEvent · event message
  • OrderStatusChangedToAwaitingValidationIntegrationEvent · event message
  • OrderStatusChangedToCancelledIntegrationEvent · event message
  • OrderStatusChangedToPaidIntegrationEvent · event message
  • OrderStatusChangedToShippedIntegrationEvent · event message
  • OrderStatusChangedToStockConfirmedIntegrationEvent · event message
  • OrderStatusChangedToSubmittedIntegrationEvent · event message
  • OrderStockConfirmedIntegrationEvent · event message
  • OrderStockConfirmedIntegrationEventHandler · handler component
  • OrderStockRejectedIntegrationEvent · event message
  • OrderStockRejectedIntegrationEventHandler · handler component
  • Published Integration Events · domain group

Ordering Domain

Ordering.Domain — the DDD domain model of the Ordering service: the Order and Buyer aggregates, their repository contracts, domain events, shared SeedWork base types, and domain exceptions. Terminal grain: the group plan reads budgetVerdict 'fits' with no drill-downs proposed, so this board is authored at component grain (one node per DDD building block) rather than as containers over sub-boards.

  • Buyer Aggregate · aggregate
  • Buyer Repository Contract · repository component
  • Domain Events · event message
  • Order Aggregate · aggregate
  • OrderingDomainException · class interface
  • Order Repository Contract · repository component
  • SeedWork · module

Ordering Infrastructure

EF Core persistence layer for the Ordering service — the OrderingContext DbContext, its entity mappings, schema migration history, idempotent-request tracking, and aggregate repositories. Grain: terminal — the group plan's budget fits (21 predicted vs band 10-40) with no drill-downs proposed, so this board is authored at component grain.

  • Buyer Repository · repository component
  • Client Request · schema model
  • Database Migrations · schema model
  • Entity Configurations · schema model
  • Idempotency · domain group
  • Mediator Extension · handler component
  • OrderingContext · repository component
  • Order Repository · repository component
  • Persistence Context · domain group
  • Repositories · domain group
  • Request Manager · service component

Web Shop

Shopper-facing storefront of the Online store: a Blazor Server web app (copied from dotnet/eShop) that browses the catalog, fills a basket and checks out by calling the Catalog, Basket, Ordering and Identity services, listening for order-status events on RabbitMQ and offering an AI chat assistant. Container-grade L0 with 1 drill-down (the WebApp deployable); the shared component library and the externally-evidenced systems it talks to are drawn inline.

  • AI chat providers · domain group
  • Online store backend services · domain group
  • Basket API · microservice
  • Catalog API · microservice
  • eShop.ServiceDefaults · library package
  • EventBusRabbitMQ · library package
  • Identity API · microservice
  • Ollama chat model · model runtime
  • OpenAI chat model · azure openai
  • Ordering API · microservice
  • RabbitMQ event bus · rabbitmq
  • Shared eShop libraries · domain group
  • Shopper · actor
  • WebApp · blazor
  • WebApp Components · library package

WebApp

Internals of the WebApp deployable: a Blazor Server storefront wiring catalog, basket, ordering, order-status events and an AI shopping assistant, plus its browser-side JS enhancements. Terminal grain for its own 32 files — the plan reads `fits` at 33 predicted nodes with no drill-down verdicts — carrying one opaque drill-down to the `Components` board for the UI layer.

  • App Bootstrap · domain group
  • Application Host · module
  • Basket & Ordering · domain group
  • Basket Service · repository component
  • Basket State · service component
  • Card Tilt · ui component
  • Cart Quantity · ui component
  • Catalog Sort · ui component
  • Chat Dock · ui component
  • Chat Eyes · ui component
  • Chat FAB · ui component
  • Client-side UX Scripts · domain group
  • Components · module
  • Image Zoom · ui component
  • Log Out Service · service component
  • Ordering Service · service component
  • Order Status Event Handlers · handler component
  • Order Status Integration Events · event message
  • Order Status Notifications · domain group
  • Order Status Notification Service · service component
  • Page Transitions · ui component
  • WebApp Components · boundary port
  • Product Image URL Provider · adapter component
  • Service Registration · module

Components

Component internals of the Blazor Server storefront UI layer (`src/WebApp/Components`): the host document and router, the persistent layout chrome, the AI shopping assistant and the routable pages. Terminal grain — the plan reads `over-band` at 24 predicted nodes, but that folds to 22 once the colocated chat JS, the two endpoint-only account routes and the using-directives file are claimed by their hosts; one layer down would be individual class members.

  • Host Document · layout component
  • App Shell · domain group
  • Cart Menu · ui component
  • Bag Page · page component
  • Catalog Page · page component
  • Chat Pane · ui component
  • Chat Engine · engine component
  • Checkout Page · page component
  • Component Imports · module
  • Footer Bar · layout component
  • Header Bar · layout component
  • Item Page · page component
  • Layout · domain group
  • Main Layout · layout component
  • Message Processor · component
  • Orders Page · page component
  • Order Status Refresher · ui component
  • Pages · domain group
  • Basket State · boundary port
  • Log Out Service · boundary port
  • Ordering Service · boundary port
  • Order Status Notification Service · boundary port
  • Router · controller component
  • Shopping Assistant · domain group
  • Chatbot Launcher · ui component
  • Account Route Shims · page component
  • User Menu · ui component

What to notice

Inbound · Signals

Insights9 analyses

9Insights
2Critical
risk5observation4
  • +4 more
Read all 9

Identified Command swallows a failed CreateOrder and still reports success on retry

risk · critical · verified

Identified Command records the request id before running the wrapped command, then catches any inner failure and returns false. The OrderStarted event that clears the basket is already saved in the same transaction, which still commits. A retry with the same id counts as a duplicate, so Create Order returns true and no order exists. This wrapper sits in front of 8 command handlers.

Impact: A buyer's basket can be cleared with no order created, and a retry hides the failure by reporting success, while every other order command inherits the same silent-catch idempotency behavior.

Recommendation (medium effort): In Identified Command's catch block, log the swallowed exception and return a result that distinguishes failure from a genuine duplicate, so a failed inner command cannot masquerade as a successful retry.

  1. Identified Command
  2. Cancel Order Command
  3. Create Order Command
  4. Create Order Draft Command
  5. Set Awaiting Validation Order Status Command
  6. Set Paid Order Status Command

An unreachable embedding model blocks product price changes

risk · critical · verified

UpdateItem and CreateItem await CatalogAI.GetEmbeddingAsync before saving, with no try/catch, and CatalogAI calls the embedding generator directly. When the configured provider (Ollama or OpenAI, chosen at startup by the OllamaEnabled flag) is unreachable, the exception escapes the endpoint before anything is saved. In UpdateItem that happens before the price-changed event reaches the outbox, so a price update fails outright.

Impact: A price change (and any other catalog write) fails whenever the configured embedding provider is down, even though pricing has nothing to do with search.

Recommendation (small effort): Catch embedding failures in UpdateItem and CreateItem, log them and save without a vector, so an unreachable model cannot fail the write and its price-changed event.

  1. Text Embedding Provider
  2. Catalog AI (Embedding) Service
  3. Catalog API Host & Bootstrap
  4. Catalog API Host & Bootstrap

Failed catalog price-change events are marked failed and never retried

risk · high · verified

CatalogIntegrationEventService saves each event to the outbox in the same transaction as the catalog change, then publishes it; if the publish throws, PublishThroughEventBusAsync only calls MarkEventAsFailedAsync. Nothing in the catalog service reads failed or pending outbox rows back to resend them, so Ordering can wait on a stock answer, or miss a price change, that never arrives.

Impact: A RabbitMQ publish failure leaves whichever service is waiting on that OrderStockConfirmed or price-change event stalled indefinitely, since nothing in this repo retries a failed or pending outbox row.

Recommendation (medium effort): Add a background sweep that reads failed and in-progress IntegrationEventLog rows and republishes them, so MarkEventAsFailedAsync is not a dead end.

  1. Catalog Integration Event Contracts
  2. Catalog API Host & Bootstrap
  3. Catalog Integration Event Handlers
  4. Catalog Integration Event Service

Order Queries reads straight from the write side's OrderingContext, not a separate read model

observation · medium · verified

OrderQueries is not a separate read model. All three of its queries run LINQ against the write side's EF Core OrderingContext and map its entities to DTOs by hand. Both order totals use Units × UnitPrice, the same formula as Order.GetTotal(), and neither subtracts the stored Discount. OrdersApi uses it for every order lookup, so a change to the Order aggregate's EF mapping changes what the API returns.

Impact: Any EF Core mapping change to the Order aggregate changes the shape or values the API returns, and the total shown to callers never reflects the per-item Discount stored on the order, since neither query subtracts it.

Recommendation (medium effort): Have OrderQueries subtract each item's Discount when computing Total, and treat the write model's mapping as a contract OrderQueries depends on — review it whenever the Order aggregate's schema changes.

  1. Orders Api
  2. Order Queries
  3. App Bootstrap

RabbitMQ status handlers skip the idempotency check HTTP commands get

observation · medium · verified

Every RabbitMQ event handler in Ordering API sends its status command straight to MediatR and skips the IdentifiedCommand wrapper that HTTP requests get. SetPaidIdentifiedOrderStatusCommandHandler exists to ignore duplicates, but nothing sends it. The same is true for SetStockConfirmed, SetStockRejected and SetAwaitingValidation. A redelivered payment, stock or grace-period message runs a status change that was already applied.

Impact: A redelivered event from the payment, catalog or grace-period worker silently repeats an order status transition, with no idempotency check to stop it.

Recommendation (medium effort): Route each integration-event handler's status command through its existing Identified*OrderStatusCommandHandler (keyed by the event id) instead of calling mediator.Send on the bare command, the way OrdersApi already does for HTTP.

  1. Catalog Service
  2. RabbitMQ Event Bus
  3. Order Processor (grace period)
  4. Ordering API
  5. Payment Processor

Awaiting-validation stock check confirms orders for unknown product ids

observation · medium · verified

The awaiting-validation handler looks up each ordered product with CatalogItems.Find and skips any id that comes back null; it rejects only when a found item lacks stock. So an order made entirely of unknown product ids gets OrderStockConfirmedIntegrationEvent. Nothing is reserved either: the paid handler later calls CatalogItem.RemoveStock, which throws CatalogDomainException once stock is zero.

Impact: An order for a nonexistent or already-removed product clears validation as confirmed, and the failure only surfaces later as an unhandled exception when the paid handler tries to remove stock, instead of as a rejection at the point the catalog actually knows the product id is unrecognised.

Recommendation (small effort): In the awaiting-validation handler, treat a null CatalogItems.Find result as a rejected line instead of skipping it, so an order of unrecognised products is rejected before payment.

  1. Ordering Service
  2. Event Bus
  3. Catalog API
  4. Catalog API Host & Bootstrap
  5. Catalog Persistence Layer
  6. Catalog AI (Embedding) Service
  7. Text Embedding Provider

Order-status updates only reach shoppers on the WebApp process that handled the event

risk · medium · verified

Order Status Notification Service is a singleton (Extensions.cs:26) that keeps subscriptions in an in-memory dictionary keyed by buyer id. All six RabbitMQ order-status handlers, such as OrderStatusChangedToPaidIntegrationEventHandler, call NotifyOrderStatusChangedAsync on it, and it wakes only the circuits subscribed in that same process. The event handlers have no other way to reach a buyer's open circuits.

Impact: With more than one WebApp replica behind a load balancer, a shopper whose browser circuit is on a different instance than the one that processed the RabbitMQ event never gets the live status refresh — their Orders page only updates on a manual reload.

Recommendation (medium effort): Replace the in-process subscription dictionary with a mechanism that fans out across replicas — a SignalR backplane, Redis pub/sub, or routing the notification through the message bus so every instance can push to its own local circuits.

  1. Components
  2. Order Status Notification Service
  3. Order Status Event Handlers
  4. Service Registration
  5. Order Status Refresher

A rejected order still empties the shopper's bag

risk · medium · verified

BasketState.CheckoutAsync awaits OrderingService.CreateOrder and then unconditionally deletes the basket. CreateOrder returns the raw HttpClient.SendAsync result and never checks the status code, so a 400 or 500 from the Ordering API looks like success. Checkout.razor then navigates straight to the orders page with no failure branch. All 3 of Basket State's dependents go through it, so every checkout takes this path.

Impact: A shopper who submits an order the Ordering API rejects sees their cart emptied and lands on an order history page where the order they just placed does not appear, with no error and no way to recover the cart contents.

Recommendation (small effort): Have OrderingService.CreateOrder check the response status and throw or return a result on failure, and have BasketState.CheckoutAsync only clear the basket after a confirmed success; Checkout.razor should show an error and keep the cart when checkout fails.

  1. Components
  2. Basket State
  3. Application Host
  4. Service Registration
  5. Cart Menu

A retired catalog item breaks Cart Menu's basket load everywhere

observation · low · verified

Cart Menu loads the basket through Basket State, which looks up every basket line with catalogItems[item.ProductId] and throws when a product is missing from the catalog. Cart Menu's startup handler catches only an unauthorized error, so that lookup failure is never caught. Basket State caches the failing call, so the bag badge stays broken on every page until the basket itself changes.

Impact: One retired product leaves the bag badge failing on every page the shopper visits, not just the one where the stale item was added, until they change the basket.

Recommendation (medium effort): Have Basket State's basket load skip or flag a line whose product is no longer in the catalog instead of indexing straight into the lookup, and stop caching a failed load in _cachedBasket so a retry can succeed.

  1. Cart Menu
  2. Basket State
  3. Basket State
  4. Log Out Service
  5. Log Out Service

Inbound · Declared

Boundaries

5rules

All hold

Holds
5
Violated
0
Not yet evaluated
0
Status
  • Holds5

Rules

+1 more

In the code

Database 11 · Endpoints 25 · Pages 7 · Events 3 · AuthZ 2 · Clients 6

Inbound · From code

Database

11tables

No primary key
0
Partitioned
0
Deprecated
0
Tables per schema
  • ordering7
  • public4

Tables

+7 more

Inbound · From code

Endpoints

25endpoints

Public
18
Deprecated
0
Rate-limited
0
By method
  • GET17
  • PUT4
  • POST3
  • DELETE1

Endpoints

+21 more

Inbound · From code

Pages

7pages

1 write without a guard

Guarded
4
With forms
2
Unguarded writes
1

Needs a look

Captures
/item/{itemId:int}
/
/cart
/checkout
/user/login
/user/logout

+1 more

Inbound · From code

Events

3channels

3 without a dead-letter queue

Event types
23
Subscriptions
13
No dead-letter
3

Needs a look

Event types per channel
  • eshop_event_bus12
  • eshop_event_bus6
  • eshop_event_bus5

Inbound · From code

AuthZ

2roles

Permissions
12
Policies
2
Resource types
3
Permissions per role
  • Authenticated user (ASP.NET Core default policy)7
  • Authenticated user5

Roles

Inbound · From code

Clients

6clients

HTTP
5
gRPC
1
Calls
13
Calls by target
  • catalog-api8
  • basket-api3
  • ordering-api2
  • embedding0
  • textEmbeddingModel0

Clients

+2 more