拆四刀上線、每一刀都能單獨 review 且都在 flag 之下。切到第三刀之前,使用者完全感覺不到差異 — 第四刀才把流量切過去。
新建 orders、order_items、order_events 三張表到獨立 schema,定義 gRPC proto 與錯誤碼。先讓資料工程師簽 contract,後面才動。
把 OrdersService 起在 K8s,monolith 端加 OrderWriter facade 同時寫舊表與新服務、用 diff 比對結果但不採信新服務輸出。先觀察 72 小時。
把 GET /orders/:id 與 list endpoint 改讀新服務、舊表淪為 fallback。新服務寫入時用 outbox pattern 發 order.created 到 Kafka,下游帳務 / 物流訂閱。
flag 由內部帳號 → 5% → 25% → 100% 漸進放量,每階段卡 24h 看 error rate 與 p99。完成後砍掉 monolith 端 OrderWriter 與 shadow diff、補 on-call runbook。
實線是同步寫入路徑、虛線是非同步事件路徑。新服務寫完自己的表後才寫 outbox,由 relay 保證最少一次送達下游 — API 永遠不等 Kafka。
實線 = 同步請求路徑;橘色虛線 = 非同步事件擴散。同步路徑完全不依賴 Kafka 是否健康。
不是最終視覺、只是讓 reviewer 跟我先對齊 admin 後台會看到的「訂單詳情頁」跟「拆分狀態儀表板」應該長什麼樣。
discount_applied 在折扣碼是空字串時新舊輸出不同(null vs 0)最容易寫錯的兩段:outbox 表的 schema(必須跟業務寫入同一個交易內),以及 monolith 端那層雙寫 facade(要能比 diff 但不能讓新服務拖慢主流程)。
create table orders ( id uuid primary key default gen_random_uuid(), customer_id uuid not null, total_cents bigint not null check (total_cents >= 0), status text not null default 'pending', created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create table order_outbox ( id bigserial primary key, aggregate uuid not null, -- order id event_type text not null, payload jsonb not null, created_at timestamptz not null default now(), sent_at timestamptz -- relay 寫回 ); create index outbox_unsent on order_outbox (id) where sent_at is null;
class OrderWriter def create(params) legacy = Order.create!(params) # 主路徑、必成功 # 影子寫入:失敗只記 log、不擋使用者 Thread.new do begin shadow = OrdersClient.create( customer_id: legacy.customer_id, total_cents: legacy.total_cents, idempotency_key: legacy.id # 用舊 id 當去重 ) ShadowDiff.record(legacy, shadow) rescue => e Metrics.incr("orders.shadow.error", tags: [e.class.name]) end end legacy end end
order.created 事件、帳務多扣一次款。idempotency_key(用 legacy order id 或 client 的 request id),DB 層 unique index 兜底;relay 也用 outbox row id 做下游去重。events_pending 標籤,讓客服第一時間知道是延遲不是漏單。refunds 跟 payment_intents 一起動。傾向 v1 不動退款、保留 monolith 處理 — 但這意味著 3 個月內訂單在兩邊各有半套。line_items[].sku_variant,BI 那邊已經依賴新欄位寫了報表。要嘛全部 100% 前升級完、要嘛維持兩種 payload 半年。