[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-namastack-turns-outbox-pain-into-reliable-events-zh":3,"article-related-namastack-turns-outbox-pain-into-reliable-events-zh":30,"series-tools-fe9fecba-d6ae-4293-af38-e68e6c2c111b":74},{"id":4,"slug":5,"title":6,"content":7,"summary":8,"source":9,"source_url":10,"author":11,"image_url":12,"cover_image":12,"category":13,"language":14,"translated_content":11,"related_article_id":15,"keywords":16,"key_takeaways":22,"views":26,"created_at":27,"published_at":28,"topic_cluster_id":29},"fe9fecba-d6ae-4293-af38-e68e6c2c111b","namastack-turns-outbox-pain-into-reliable-events-zh","Namastack 把 outbox 變穩定事件流","\u003Cp data-speakable=\"summary\">Namastack Outbox 是一套把資料庫寫入和事件發送綁在一起的 Spring Boot 可靠性模式。\u003C\u002Fp>\u003Cp>我用事件驅動系統一陣子了，最煩的不是架構圖畫不漂亮，而是現實會在最細的縫裡搞你：資料庫明明 commit 了，事件卻沒送出去；或者 broker 收到了，服務卻在中途死掉。你在 demo 裡看起來像在做\u003Ca href=\"\u002Ftag\u002F分散式系統\">分散式系統\u003C\u002Fa>，到了 production 才發現自己其實在賭運氣。我以前也想過靠 after-commit、重試、補 log 這些小修小補混過去，結果都是把麻煩往後推而已。\u003C\u002Fp>\u003Cp>這次我會拆的是 \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fnamastack\u002Fnamastack-outbox\">Namastack Outbox\u003C\u002Fa> 這個 repo 背後的思路。它不是在賣花俏名詞，我看它比較像是在把「可靠」這件事做得沒那麼惱人。官方把自己定位成 \u003Ca href=\"https:\u002F\u002Fwww.namastack.io\u002Foutbox\">Spring Boot 的 transactional outbox engine\u003C\u002Fa>，重點放在一致性、重試、排序、觀測性，這些才是團隊真正會撞牆的地方。\u003C\u002Fp>\u003Ch2>先別裝作 publish 很簡單\u003C\u002Fh2>\u003Cblockquote>“A transactional outbox engine that helps teams keep database changes and published events consistent without adding unnecessary operational complexity.”\u003C\u002Fblockquote>\u003Cp>翻譯一下就是：不要讓你的 app 自己說謊。資料庫說訂單已建立，事件流就不該因為 broker 抖一下而默默不同步。outbox pattern 存在的理由很單純，就是把「業務狀態變更」和「事件意圖」放進同一個交易裡，先保住一致性，再談送出。\u003C\u002Fp>\n\u003Cfigure class=\"my-6\">\u003Cimg src=\"https:\u002F\u002Fxxdpdyhzhpamafnrdkyq.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fcovers\u002Finline-1781949794069-sfg2.png\" alt=\"Namastack 把 outbox 變穩定事件流\" class=\"rounded-xl w-full\" loading=\"lazy\" \u002F>\u003C\u002Ffigure>\n\u003Cp>我以前看過太多團隊用「commit 後再 publish」硬撐，結果一到部署、重啟、網路抖動，事件就開始失蹤。這種 bug 最討厭的地方不是壞，而是它會假裝只有偶爾壞。你一開始會以為是 broker 問題，後來以為是程式 race condition，再後來才承認是設計本身就把失敗藏起來了。\u003C\u002Fp>\u003Cp>實操寫法很簡單：把事件當成持久化流程的一部分，不要把它當成 request path 裡的一個「順手做一下」。業務交易先寫資料，再寫 outbox record，然後交給獨立的 delivery worker 慢慢送。這不是酷炫做法，但它會讓你的系統少一個最常見的坑。\u003C\u002Fp>\u003Cp>Namastack 這個方向我喜歡的地方，是它沒有把問題講得很玄。它就是盯住資料和訊息之間那條最容易裂開的線，然後老老實實處理那條線上的髒活。\u003C\u002Fp>\u003Ch2>outbox 之所以有用，是因為它很無聊\u003C\u002Fh2>\u003Cp>我看這類工具，最先找的不是功能清單，而是它有沒有把 boring stuff 做完整。因為可靠性工具最怕的就是「看起來很聰明，實際上很難用」。Namastack 的 README 直接把重點寫出來：transactional outbox、retries、ordering、failure handling、metrics、tracing。這幾個字擺在一起，我就知道它不是想跟你聊願景，而是想跟你處理事故。\u003C\u002Fp>\u003Cp>白話一點說，這套東西的目標不是讓事件變得更浪漫，而是讓它每次都做同一件事。送不出去就等，服務掛了就讓別人接手，順序有要求就別亂跑。這些都不性感，但這些才是你在凌晨兩點會感謝自己的地方。\u003C\u002Fp>\u003Cp>我之前在一個 payment service 上踩過這種坑。Postgres 寫成功了，Kafka publish 偶爾掉一筆。表面上看起來只是少數事件沒到，實際上是整個系統的信任基礎在漏水。後來我們補 retry、補 idempotency、補一堆 log，最後才承認：如果一開始就用 outbox，大半的補丁都不用打。\u003C\u002Fp>\u003Cp>實操寫法：把「送事件」從業務邏輯裡拆出去。你的 request handler 不要直接依賴 broker 成功與否；它只負責把狀態和事件意圖寫進資料庫。真正的送出交給背景 worker，而且 worker 的行為要可重試、可追蹤、可恢復。\u003C\u002Fp>\u003Cul>\u003Cli>業務資料和事件意圖一起 commit。\u003C\u002Fli>\u003Cli>事件從 durable store 讀出來再送。\u003C\u002Fli>\u003Cli>重試策略要明寫，不要藏在 helper method 裡。\u003C\u002Fli>\u003Cli>假設任何一步都可能失敗。\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>Spring Boot 的手感，決定它會不會被採用\u003C\u002Fh2>\u003Cp>Namastack 強調 Spring Boot ergonomics、auto-configuration、familiar APIs，這些字看起來很平常，但我反而覺得這是重點。很多可靠性方案死掉，不是因為概念錯，而是因為接起來太煩，團隊用一次就不想再碰。工具如果需要一堆特殊 bootstrap、奇怪 annotation、額外 lifecycle，review 就先死一半。\u003C\u002Fp>\n\u003Cfigure class=\"my-6\">\u003Cimg src=\"https:\u002F\u002Fxxdpdyhzhpamafnrdkyq.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fcovers\u002Finline-1781949789915-rg3t.png\" alt=\"Namastack 把 outbox 變穩定事件流\" class=\"rounded-xl w-full\" loading=\"lazy\" \u002F>\u003C\u002Ffigure>\n\u003Cp>我看它支援 Java、Kotlin，還提到 Spring Modulith event externalization。這代表它想貼著 Spring 團隊原本的工作方式走，而不是逼你重寫平台。這種選擇我很買單，因為基礎設施工具真正的價值，不是讓你覺得自己很會架構，而是讓你少跟 library 吵架。\u003C\u002Fp>\u003Cp>翻譯一下就是：如果一個工具要你先學會它的世界觀，才能開始解決問題，那它多半太重了。可靠性工具應該像地板，不該像舞台。你踩上去就好，不用先跟它握手。\u003C\u002Fp>\u003Cp>實操寫法：評估這類工具時，不只看它能不能跑，要看團隊半年後還願不願意碰。你可以用一個很土的標準測試：新同事看 docs 和一個 example，能不能自己接起來。如果不能，表示這套東西的心智負擔太高。\u003C\u002Fp>\u003Cp>我通常還會問一題：這個整合有沒有辦法被一句話講清楚？如果講不清楚，通常也很難維護。\u003C\u002Fp>\u003Ch2>觀測性不是加分項，是基本功\u003C\u002Fh2>\u003Cp>README 裡提到 \u003Ca href=\"https:\u002F\u002Fmicrometer.io\u002F\">Micrometer metrics\u003C\u002Fa>、tracing、\u003Ca href=\"https:\u002F\u002Fdocs.spring.io\u002Fspring-boot\u002Freference\u002Factuator\u002Findex.html\">Actuator\u003C\u002Fa> support，這段我看得很認真。因為可靠性工具如果不能告訴你自己在幹嘛，那它就只是另一個黑盒子，外表寫著「穩」，裡面其實一團霧。\u003C\u002Fp>\u003Cp>白話一點說，你要能知道 pending 多少、送成功多少、重試幾次、卡在哪裡、哪個 consumer 壞掉。不是事後翻 log 猜，是當下就看得出來。觀測性做不好，outbox 只會\u003Ca href=\"\u002Fnews\u002Fclaude-design-assets-to-design-system-zh\">變成\u003C\u002Fa>一個比較有秩序的黑洞。\u003C\u002Fp>\u003Cp>我以前 debug 過一套 outbox-like 流程，功能上沒錯，但 ops 完全不知道它現在是忙、是塞、還是死。那種系統最折磨人，因為它不是不工作，而是你不知道它到底工作到哪裡。這也是為什麼 metrics 和 tracing 不是錦上添花，而是把工具\u003Ca href=\"\u002Fnews\u002Fdesignmd-agent-ready-ui-specs-zh\">變成可\u003C\u002Fa>支援系統的最低門檻。\u003C\u002Fp>\u003Cp>實操寫法：把 delivery pipeline 裡的關鍵狀態拉出來，至少要有 pending count、delivery latency、retry count、terminal failure count。Spring Boot 的話，直接接到 Actuator 和 Micrometer，不要自己發明一套沒人維護的 dashboard 命名法。\u003C\u002Fp>\u003Cul>\u003Cli>看 outbox table 還剩多少 pending rows。\u003C\u002Fli>\u003Cli>看從 write 到 publish 花多久。\u003C\u002Fli>\u003Cli>看 retry 是在救火還是在燒錢。\u003C\u002Fli>\u003Cli>看 tracing 能不能把交易和送出串起來。\u003C\u002Fli>\u003C\u002Ful>\u003Cp>如果你一分鐘內答不出「現在卡在哪」，那你沒有 observability，你只有希望。\u003C\u002Fp>\u003Ch2>重試和排序，才是 outbox 的真考題\u003C\u002Fh2>\u003Cp>Namastack 明講 retries、ordering、failure handling。這三個字才是 outbox pattern 真正開始麻煩的地方。大家都會說自己有用 outbox，但真正難的是：延遲怎麼處理、重送怎麼做、重複怎麼辦、順序到底要不要保、要保到什麼程度。\u003C\u002Fp>\u003Cp>翻譯一下就是，你需要 policy，不要靠感覺。是無限重試還是指數退避？N 次後丟 dead-letter？順序是全域、每個 aggregate、每個 tenant，還是根本不保證？這些都不是哲學題，是事故題。你不先定義，系統就會替你定義，而且通常定義得很難看。\u003C\u002Fp>\u003Cp>我看過團隊把 retry 寫成「失敗就再試一次」，聽起來很勤勞，實際上是在把 broker 壓更爛。也看過另一種，重試太保守，結果 outbox 裡堆了一整天的未送資料，最後是客服先發現。這就是為什麼 delivery contract 一定要寫清楚，不然你以為你在做可靠性，實際上你在製造另一種隨機性。\u003C\u002Fp>\u003Cp>實操寫法：先把 domain 的 ordering 需求講明白。是同一個訂單要保序，還是同一個客戶要保序，還是只要最終一致就好？講完再決定 worker 怎麼分批、怎麼重試、怎麼標記 terminal failure。不要反過來。\u003C\u002Fp>\u003Cp>Namastack 把這些放進產品敘事裡，我覺得是對的。因為真正出事的地方，從來不是「有沒有送」，而是「送成什麼樣」。\u003C\u002Fp>\u003Ch2>它透露出來的，不只是工具，而是團隊習慣\u003C\u002Fh2>\u003Cp>README 提到 relational databases、MongoDB、Kafka、RabbitMQ、\u003Ca href=\"\u002Ftag\u002Faws\">AWS\u003C\u002Fa> SNS、Spring Modulith，還有 production operations。這種組合不太像 demo 專案，比較像真的碰過一堆髒環境之後整理出來的東西。因為只有真的在維運的人，才會把相容性、整合面、文件、觀測性一起放進同一個討論裡。\u003C\u002Fp>\u003Cp>白話一點說，這種 repo 的價值不只是核心\u003Ca href=\"\u002Fnews\u002Fclaude-vs-gpt-vs-gemini-cheng-shi-ma-ji-zhun-dui-jue-zh\">程式碼\u003C\u002Fa>，而是它背後那種「我知道你會痛哪裡」的態度。你如果做過基礎設施，就知道最怕的是那種只會秀核心演算法，卻完全不管長期維護的人。那種東西很容易在第一個月看起來很美，第三個月就開始鬧脾氣。\u003C\u002Fp>\u003Cp>我也注意到它有很直接地提到 sponsorship。這件事我反而覺得誠實。維護、文件、整合、相容性、觀測性，這些都不是免費的。很多開源專案死掉不是因為 code 爛，而是因為沒人願意長期把這些 boring work 做完。\u003C\u002Fp>\u003Cp>實操寫法：你在選這類基礎工具時，別只看首頁漂不漂亮。先看 docs、issue、example、整合範圍、維護節奏。工具如果說不清楚自己怎麼活下來，通常也說不清楚怎麼幫你上 production。\u003C\u002Fp>\u003Cp>我會把這類專案當成「團隊習慣的外化」。它如果把事情講得清楚，通常代表作者真的在乎長期使用，而不是只想讓你今天覺得很酷。\u003C\u002Fp>\u003Ch2>可抄的模板\u003C\u002Fh2>\u003Cpre>\u003Ccode># Spring Boot transactional outbox playbook\n\n## Goal\nMake database writes and emitted events consistent, observable, and recoverable.\n\n## Core rule\nIf the business change matters, the event intent must be persisted in the same transaction.\n\n## Delivery rule\nDo not publish directly from the request path. A separate worker reads pending outbox records and publishes them later.\n\n## Retry policy\n- Retry transient failures with backoff.\n- Define a maximum attempt count or a dead-letter path.\n- Log and metric every terminal failure.\n\n## Ordering policy\nState the ordering contract explicitly:\n- global ordering\n- per aggregate ordering\n- per tenant ordering\n- no ordering guarantee\n\n## Observability\nExpose these signals through Actuator \u002F Micrometer:\n- pending outbox rows\n- delivery latency\n- retry count\n- success count\n- failure count\n- oldest undelivered record age\n\n## Team contract\n- If the event matters, it is not optional.\n- If delivery fails, the system must say so.\n- If ordering matters, document it before shipping.\n- If the integration is hard to operate, fix the integration.\n\n## Implementation sketch\n1. Write domain state and outbox record in one DB transaction.\n2. Worker polls or subscribes to pending outbox records.\n3. Worker publishes to Kafka, RabbitMQ, SNS, or another broker.\n4. On success, mark the record delivered.\n5. On transient failure, retry with policy.\n6. On repeated failure, surface a terminal error path.\n7. Trace the path from transaction to publish.\n\n## Copy-ready note\nWe use the transactional outbox pattern so business state and event intent commit together. A separate delivery process publishes events reliably with retries, ordering rules, and observability built in. The request path never depends on a best-effort publish.\n\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>這段模板是我根據 Namastack Outbox 的 README 和 transactional outbox 模式整理出來的可貼版，不是原 repo 原封不動的 checklist。原始概念來自 \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fnamastack\u002Fnamastack-outbox\">https:\u002F\u002Fgithub.com\u002Fnamastack\u002Fnamastack-outbox\u003C\u002Fa> 和 \u003Ca href=\"https:\u002F\u002Fwww.namastack.io\u002Foutbox\">https:\u002F\u002Fwww.namastack.io\u002Foutbox\u003C\u002Fa>，我加的是適合團隊文件和落地時直接拿去用的寫法；另外參考了 \u003Ca href=\"https:\u002F\u002Fmicrometer.io\u002F\">Micrometer\u003C\u002Fa> 與 \u003Ca href=\"https:\u002F\u002Fdocs.spring.io\u002Fspring-boot\u002Freference\u002Factuator\u002Findex.html\">Spring Boot Actuator\u003C\u002Fa> 的觀測性慣例。","我把 Namastack Outbox 拆成一套可直接抄進 Spring Boot 的可靠事件模式。","github.com","https:\u002F\u002Fgithub.com\u002Fnamastack",null,"https:\u002F\u002Fxxdpdyhzhpamafnrdkyq.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fcovers\u002Finline-1781949794069-sfg2.png","tools","zh","12a7a9d9-3333-4e6b-9ab3-dc56f9ebf037",[17,18,19,20,21],"transactional outbox","Spring Boot","event-driven systems","observability","retries",[23,24,25],"outbox 的價值不是酷，是把失敗變得可控。","Spring Boot 整合手感和觀測性，決定工具能不能進 production。","先定義排序、重試、失敗政策，再談實作。",0,"2026-06-20T10:02:49.479466+00:00","2026-06-20T10:02:49.454+00:00","c3c88dd2-a940-438a-b359-0e5a24562273",{"tags":31,"relatedLang":33,"relatedPosts":37},[32],{"name":20,"slug":20},{"id":15,"slug":34,"title":35,"language":36},"namastack-turns-outbox-pain-into-reliable-events-en","Namastack turns outbox pain into reliable events","en",[38,44,50,56,62,68],{"id":39,"slug":40,"title":41,"cover_image":42,"image_url":42,"created_at":43,"category":13},"7beaabe3-5421-4e2b-a42a-d1a7b669be12","deploy-minimax-m3-with-vllm-openai-api-zh","用 vLLM 部署 MiniMax M3 並開啟 OpenAI API","https:\u002F\u002Fxxdpdyhzhpamafnrdkyq.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fcovers\u002Finline-1781954276176-k5fw.png","2026-06-20T11:17:30.019598+00:00",{"id":45,"slug":46,"title":47,"cover_image":48,"image_url":48,"created_at":49,"category":13},"b8a08645-4041-4124-a44d-c5b3336bbd65","claude-design-assets-to-design-system-zh","Claude Design 把素材變成系統","https:\u002F\u002Fxxdpdyhzhpamafnrdkyq.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fcovers\u002Finline-1781946199259-wjwr.png","2026-06-20T09:02:46.505161+00:00",{"id":51,"slug":52,"title":53,"cover_image":54,"image_url":54,"created_at":55,"category":13},"f2fadb5a-41e2-4095-9467-d120f9acee99","vs-code-turns-folder-into-workspace-zh","VS Code 把資料夾變工作區","https:\u002F\u002Fxxdpdyhzhpamafnrdkyq.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fcovers\u002Finline-1781938995715-bygi.png","2026-06-20T07:02:52.407399+00:00",{"id":57,"slug":58,"title":59,"cover_image":60,"image_url":60,"created_at":61,"category":13},"95d2b100-b1b0-43ae-af46-7f9c85d93d00","midjourney-medical-turns-scans-into-spa-zh","Midjourney Medical把掃描變成Spa","https:\u002F\u002Fxxdpdyhzhpamafnrdkyq.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fcovers\u002Finline-1781909284807-n67y.png","2026-06-19T22:47:40.551779+00:00",{"id":63,"slug":64,"title":65,"cover_image":66,"image_url":66,"created_at":67,"category":13},"563c146c-b078-4610-93fa-af399a02c89a","three-multimodal-models-work-in-claude-code-zh","Claude Code 現在能接三個多模態模型","https:\u002F\u002Fxxdpdyhzhpamafnrdkyq.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fcovers\u002Finline-1781892161193-9rla.png","2026-06-19T18:02:15.364369+00:00",{"id":69,"slug":70,"title":71,"cover_image":72,"image_url":72,"created_at":73,"category":13},"86316fab-2e99-4958-b79f-8c54ce44d5c4","ollama-turns-local-llms-into-copyable-setup-zh","Ollama 讓本地 LLM 變可抄配置","https:\u002F\u002Fxxdpdyhzhpamafnrdkyq.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Fcovers\u002Finline-1781885006324-jvvo.png","2026-06-19T16:02:56.601682+00:00",[75,80,85,90,95,100,105,110,115,120],{"id":76,"slug":77,"title":78,"created_at":79},"855cd52f-6fab-46cc-a7c1-42195e8a0de4","surepath-real-time-mcp-policy-controls-zh","SurePath 推出即時 MCP 政策控管","2026-03-26T07:57:40.77233+00:00",{"id":81,"slug":82,"title":83,"created_at":84},"9b19ab54-edef-4dbd-9ce4-a51e4bae4ebb","mcp-in-2026-the-ai-tool-layer-teams-use-zh","2026 年 MCP：團隊真的在用的 AI 工具層","2026-03-26T08:01:46.589694+00:00",{"id":86,"slug":87,"title":88,"created_at":89},"af9c46c3-7a28-410b-9f04-32b3de30a68c","prompting-in-2026-what-actually-works-zh","2026 提示工程，真正有用的是什麼","2026-03-26T08:08:12.453028+00:00",{"id":91,"slug":92,"title":93,"created_at":94},"05553086-6ed0-4758-81fd-6cab24b575e0","garry-tan-open-sources-claude-code-toolkit-zh","Garry Tan 開源 Claude Code 工具包","2026-03-26T08:26:20.068737+00:00",{"id":96,"slug":97,"title":98,"created_at":99},"042a73a2-18a2-433d-9e8f-9802b9559aac","github-ai-projects-to-watch-in-2026-zh","2026 必看 20 個 GitHub AI 專案","2026-03-26T08:28:09.619964+00:00",{"id":101,"slug":102,"title":103,"created_at":104},"a5f94120-ac0d-4483-9a8b-63590071ac6a","claude-code-vs-cursor-2026-zh","Claude Code 與 Cursor 深度對比：202…","2026-03-26T13:27:14.279193+00:00",{"id":106,"slug":107,"title":108,"created_at":109},"0975afa1-e0c7-4130-a20d-d890eaed995e","practical-github-guide-learning-ml-2026-zh","2026 機器學習入門 GitHub 實用指南","2026-03-27T01:16:49.712576+00:00",{"id":111,"slug":112,"title":113,"created_at":114},"bfdb467a-290f-4a80-b3a9-6f081afb6dff","aiml-2026-student-ai-ml-lab-repo-review-zh","AIML-2026：像課綱的學生實驗 Repo","2026-03-27T01:21:51.467798+00:00",{"id":116,"slug":117,"title":118,"created_at":119},"80cabc3e-09fc-4ff5-8f07-b8d68f5ae545","ai-trending-github-repos-and-research-feeds-zh","AI Trending：把 AI 資源收成一張表","2026-03-27T01:31:35.262183+00:00",{"id":121,"slug":122,"title":123,"created_at":124},"3ce6e6e2-bac5-463e-9f8d-45caabcc61f7","awesome-ai-for-science-research-tools-map-zh","AI 科研工具清單，開始像地圖了","2026-03-27T01:46:50.521945+00:00"]