1
archlab 1.0
2
schema "https://arch-lab.dev/schema/v1/diagram.schema.json"
3
title "Atlas Shop"
4
description "Global event-sourced order shop. Commands append immutable events to a regional event store, Kafka carries the stream, projections build eventually-consistent read models, and MirrorMaker 2 mirrors events between regions. The diagrams show one regional stamp (EU); the US region runs the identical topology."
5
owner "orders-platform"
6
tags #commerce #event-sourcing #kafka #multi-region
7
created 2026-07-28T00:00:00Z
8
updated 2026-07-28T00:00:00Z
9
tagcolor regional "#8b5cf6"
10
tagcolor region-us "#f59e0b"
11
tagcolor write-side "#e0524d"
12
tagcolor read-side "#10b981"
13
tagcolor async "#eab308"
14
generator "arch-lab" "0.1.0"
15
16
@component d-cmp-order-command "Order Command Service — Components" owner=order-command-service
17
desc "The write side: commands in, events out. No component updates state in place — the aggregate decides, the handler appends, the relay publishes. Boundary nodes come from the container view; the payment saga is omitted at this zoom."
18
view 1 0 0
19
command-handler:component "Command Handler" [Go / pgx] (576,256 208x96)
20
desc "Decodes and authenticates commands, folds the order's event history into state, invokes the aggregate, and appends the new events with optimistic concurrency — a racing writer makes the append fail, and the handler retries against fresh history. Returns the outcome synchronously."
21
ext-eventstore-cmp:database ^d-cnt-atlas/event-store (576,560 208x88)
22
ext-kafka-cmd:queue ^d-cnt-atlas/kafka (1064,856 208x88)
23
ext-storefront-cmp:external ^d-cnt-atlas/storefront (584,32 192x80)
24
order-aggregate:component "Order Aggregate" [Go] >d-code-order-aggregate (1064,256 208x96)
25
desc "Pure decision logic, rehydrated by folding the event stream. decide() enforces invariants — no paying a cancelled order — and emits new events. No I/O."
26
outbox-relay:component "Outbox Relay" [Go] (1064,556 208x96)
27
desc "Tails the committed journal and publishes each event to Kafka at-least-once, in stream order, key = order id. The journal is its own outbox: appending the event and queueing it are one transaction."
28
29
command-handler -> order-aggregate : "Rehydrates state, calls decide(state, command)" id=e-handler-aggregate
30
command-handler <-> ext-eventstore-cmp : "Appends at expected version — unique (stream_id, version) rejects racing writers" [SQL/TCP] ~e-commands-eventstore id=e-handler-eventstore
31
outbox-relay ..> ext-eventstore-cmp : "Tails the journal (poll + LISTEN/NOTIFY)" [SQL/TCP] ~e-commands-eventstore id=e-outbox-eventstore
32
outbox-relay ..> ext-kafka-cmd : "Publishes to orders.events.v1, key = order id, at-least-once" [Kafka protocol] #async ~e-commands-kafka id=e-outbox-kafka
33
ext-storefront-cmp -> command-handler : "PlaceOrder, CancelOrder" [HTTP/JSON] ~e-storefront-commands id=e-sf-handler
34
35
@container d-cnt-atlas "Atlas Shop — Containers (one regional stamp)" owner=atlas-shop
36
desc "The EU region of the active-active deployment; the US stamp is identical. An order is written only in the region that accepted it (its home region); every region serves reads for all orders via mirrored events. Commands and queries are synchronous (solid); events cross Kafka asynchronously (dashed)."
37
view 1 0 0
38
frame read-side "Read side" in=regional-stamp
39
frame regional-stamp "Regional stamp (us-east-1)"
40
frame write-side "Write side" in=regional-stamp
41
event-store:database "Event Store" @postgresql~ [PostgreSQL 16] #regional #write-side in=write-side (440,800 208x96)
42
desc "Append-only order_events journal, one stream per order, unique (stream_id, version). An order's stream lives only in its home region, so there are no cross-region write conflicts by construction."
43
ext-payment:external ^d-ctx-root/payment-gateway (32,496 176x80)
44
ext-shopper:person ^d-ctx-root/shopper (880,32 160x88)
45
kafka:queue "Event Backbone" @queue! [Apache Kafka 3.7] #regional in=regional-stamp (840,800 240x96)
46
desc "Regional Kafka cluster carrying orders.events.v1 (key = order id, so each order's events stay ordered) plus the mirrored us.orders.events.v1. Retention is long enough to rebuild any projection from scratch."
47
order-command-service:container "Order Command Service" @golang~ [Go 1.23] #regional #write-side >d-cmp-order-command in=write-side (440,488 208x96)
48
desc "The write side. Validates each command against the order's event history and appends new events — never updating state in place; the event stream is the system of record. Its payment saga consumes OrderPlaced, calls the gateway, and feeds the result back in as a command."
49
order-projection-service:container "Order Projection Service" @golang~ [Go 1.23] #read-side #regional in=read-side (1272,488 208x96)
50
desc "The read side. Consumes the local and mirrored event topics into denormalised views and serves order queries. Eventually consistent — milliseconds behind the write side, seconds for cross-region orders. Upserts are versioned and idempotent, so at-least-once delivery never double-applies."
51
read-store:database "Order Read Store" @mongodb~ [MongoDB 7] #read-side #regional in=read-side (1272,800 208x96)
52
desc "Denormalised order views: summary, history, tracking. Disposable — any view can be dropped and rebuilt by replaying the event topics."
53
storefront:container "Storefront" @nextjs~ [Next.js 15] #regional in=regional-stamp (864,240 192x96)
54
desc "Server-rendered shop UI; routes commands to the write side and queries to the read side. After checkout it renders the command response, not the projection — the shopper never observes read-model lag on their own order."
55
us-region:external "US Region (peer stamp)" [Identical deployment, us-east-1] #region-us (840,1096 240x96)
56
desc "The identical topology in us-east-1. Each region owns writes for orders placed there and mirrors its events to the other. Mirroring is async: a region loss can lose the last seconds of unreplicated events (RPO > 0)."
57
58
order-command-service <-> event-store : "Appends events; reads streams to rehydrate" [SQL/TCP] id=e-commands-eventstore
59
order-command-service ..> kafka : "Publishes committed events via outbox, key = order id" [Kafka protocol] #async id=e-commands-kafka
60
order-command-service -> ext-payment : "Authorises and captures payments" [HTTPS/JSON] ~e-atlas-payment id=e-commands-payment
61
kafka <..> us-region : "Mirrors event topics both ways — async, per-partition order preserved" [MirrorMaker 2] #async id=e-kafka-us
62
order-projection-service ..> kafka : "Consumes orders.events.v1 + us.orders.events.v1" [Kafka protocol] #async id=e-projections-kafka
63
order-projection-service <-> read-store : "Projects events into views; serves queries" [MongoDB wire protocol] id=e-projections-readstore
64
ext-shopper -> storefront : "GeoDNS serves each shopper from the nearest healthy region" [HTTPS] ~e-shopper-atlas id=e-shopper-storefront
65
storefront -> order-command-service : "Order commands: PlaceOrder, CancelOrder" [HTTP/JSON] id=e-storefront-commands
66
storefront -> order-projection-service : "Order queries: summary, history, tracking" [HTTP/JSON] id=e-storefront-queries
67
68
path command "The write path"
69
beat "A shopper's command reaches the region nearest them."
70
ext-shopper -> storefront -> order-command-service
71
beat "The command authorises the payment and appends its events."
72
order-command-service -> ext-payment
73
order-command-service -> event-store
74
beat "Committed events are published, and mirrored to the peer region."
75
order-command-service -> kafka -> us-region
76
77
path query "The read path"
78
beat "A query never touches the command side."
79
ext-shopper -> storefront -> order-projection-service
80
beat "The projection service consumes both regions' events."
81
order-projection-service -> kafka
82
beat "And serves its views from the read store."
83
order-projection-service -> read-store
84
85
@code d-code-order-aggregate "Order Aggregate — Code" owner=order-aggregate
86
desc "Event sourcing in one loop: state is a left fold of events, decide() proposes new events from the current state, and nothing mutates in place."
87
view 1 0 0
88
apply-fn:code "apply()" [Go func] (936,320 192x88)
89
desc "(OrderState, OrderEvent) → OrderState. The fold step: replaying history is events.reduce(apply, empty)."
90
decide-fn:code "decide()" [Go func] (200,320 192x88)
91
desc "(OrderState, Command) → []OrderEvent. Pure: enforces invariants and either emits events or rejects the command. No clock, no I/O."
92
order-event:code "OrderEvent" [Go sum type] (568,608 192x88)
93
desc "OrderPlaced, PaymentCaptured, PaymentFailed, OrderShipped, OrderCancelled — immutable facts named in the past tense, versioned for schema evolution."
94
order-state:code "OrderState" [Go struct] (568,32 192x88)
95
desc "In-memory snapshot of one order, produced only by apply(). Never persisted — rebuilt from the stream on every command."
96
97
apply-fn -> order-state : "Returns the next state" id=e-apply-state
98
decide-fn -> order-event : "Emits new events" id=e-decide-event
99
order-event -> apply-fn : "Folded one at a time" id=e-event-apply
100
order-state -> decide-fn : "Current state in" id=e-state-decide
101
102
@context d-ctx-root "Atlas Shop — System Context"
103
desc "Who shops here and the one third party the platform cannot work without. Shoppers everywhere use one shop; geography appears one level down."
104
view 1 0 0
105
atlas-shop:system "Atlas Shop" [Go / Next.js / Kafka] >d-cnt-atlas (400,352 320x128)
106
desc "Online order shop deployed as identical stamps in eu-central-1 and us-east-1. Orders are event-sourced: every state change is an immutable event, and all read views derive from the event stream."
107
payment-gateway:external "Payment Gateway" [REST API] (448,712 224x96)
108
desc "Third party that authorises and captures card payments."
109
shopper:person "Shopper" (472,48 176x96)
110
desc "Customer browsing, ordering, and tracking deliveries — served by whichever region is closest."
111
112
atlas-shop -> payment-gateway : "Authorises and captures payments" [HTTPS/JSON] id=e-atlas-payment
113
shopper -> atlas-shop : "Browses, orders and tracks deliveries" [HTTPS] id=e-shopper-atlas