<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Go 入門實戰指南 on Tarragon</title><link>https://tarrragon.github.io/blog/go/</link><description>Recent content in Go 入門實戰指南 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 22 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/go/index.xml" rel="self" type="application/rss+xml"/><item><title>Go 教材核心術語</title><link>https://tarrragon.github.io/blog/go/glossary/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/go/glossary/</guid><description>&lt;p>本頁整理 Go 入門篇與進階篇反覆使用的詞彙。核心目的是讓同一個概念在不同章節中保持同一種意思。&lt;/p>
&lt;p>Go 教材中的術語應先服務可讀性：詞彙要幫助工程師判斷責任邊界，而不是把簡單程式包裝成複雜架構。小程式可以只有 &lt;code>main.go&lt;/code>，服務變大後才逐步引入 event、repository、port、adapter、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/projection/" data-link-title="Projection" data-link-desc="說明從事件流或資料變更推算出查詢用讀取視圖的轉換機制">projection&lt;/a> 等詞彙。&lt;/p>
&lt;h2 id="輸入與行為">輸入與行為&lt;/h2>
&lt;h3 id="action">Action&lt;/h3>
&lt;p>&lt;code>action&lt;/code> 表示 client 對服務提出的意圖。它通常來自 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket&lt;/a> message、HTTP request 或 CLI input，還沒有完成驗證、授權或業務規則套用。&lt;/p>
&lt;p>例如 &lt;code>subscribe_topic&lt;/code> 可以是 WebSocket action，代表 client 想訂閱某個 &lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/topic/" data-link-title="Topic" data-link-desc="說明 topic 如何把事件依主題分流給不同訂閱者">topic&lt;/a>。它進入系統後，router 會先解析 payload，再交給 usecase 或 subscription manager。&lt;/p>
&lt;p>延伸閱讀：&lt;a href="https://tarrragon.github.io/blog/go/06-practical/new-websocket-action/" data-link-title="6.1 如何新增一個即時訊息 action" data-link-desc="修改 client message、路由與 handler">如何新增一個 WebSocket action&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go-advanced/02-networking-websocket/subscription-routing/" data-link-title="2.3 訂閱模型與訊息路由" data-link-desc="將 client action 對應到主題訂閱狀態">訂閱模型與訊息路由&lt;/a>。&lt;/p>
&lt;h3 id="command">Command&lt;/h3>
&lt;p>&lt;code>command&lt;/code> 表示 application layer 接受的行為輸入。它已經脫離 HTTP JSON、WebSocket frame 或 CLI flag 的外部格式，變成 usecase 可以理解的資料。&lt;/p>
&lt;p>例如 &lt;code>CreateNotificationCommand&lt;/code> 可以由 HTTP handler、WebSocket router 或背景 worker 建立。handler 負責把 request DTO 轉成 command，usecase 負責處理 command 的規則。&lt;/p>
&lt;p>延伸閱讀：&lt;a href="https://tarrragon.github.io/blog/go/07-refactoring/handler-boundary/" data-link-title="7.1 把 handler 邏輯拆成可測單元" data-link-desc="分離 HTTP 協定處理與核心邏輯">把 handler 邏輯拆成可測單元&lt;/a>。&lt;/p>
&lt;h3 id="usecase">Usecase&lt;/h3>
&lt;p>&lt;code>usecase&lt;/code> 表示一個 application 行為。它負責協調 validation、repository、event publisher、clock、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/transaction/" data-link-title="Transaction" data-link-desc="說明 transaction 如何讓一組資料變更一起成功或一起回復">transaction&lt;/a> 等能力，並保留「這件事如何完成」的規則。&lt;/p>
&lt;p>usecase 的重點是行為邊界，不是資料夾名稱。小型程式可以先用函式表達 usecase；當 handler、worker、WebSocket action 都需要共用同一套規則時，再把 usecase 抽出來。&lt;/p>
&lt;p>延伸閱讀：&lt;a href="https://tarrragon.github.io/blog/go/07-refactoring/handler-boundary/" data-link-title="7.1 把 handler 邏輯拆成可測單元" data-link-desc="分離 HTTP 協定處理與核心邏輯">把 handler 邏輯拆成可測單元&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go/07-refactoring/hexagonal-migration/" data-link-title="7.6 逐步遷移到 ports/adapters 架構" data-link-desc="用 ports 與 adapters 控制 Go 服務的依賴方向">逐步遷移到 ports/adapters 架構&lt;/a>。&lt;/p>
&lt;h2 id="事件系統">事件系統&lt;/h2>
&lt;h3 id="domain-event">Domain Event&lt;/h3>
&lt;p>&lt;code>domain event&lt;/code> 表示系統承認已經發生的內部事實。它和 action 不同：action 是請求，domain event 是經過系統語意整理後的事實。&lt;/p>
&lt;p>例如 &lt;code>notification.created&lt;/code> 可以表示通知已被建立。這個事件可以來自 HTTP request、WebSocket action、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue&lt;/a> message 或 background worker，但進入 processor 前應先被 normalize 成同一種內部模型。&lt;/p>
&lt;p>延伸閱讀：&lt;a href="https://tarrragon.github.io/blog/go/06-practical/new-event-type/" data-link-title="6.2 如何新增一種 domain event" data-link-desc="擴展事件常數、輸入驗證與處理流程">如何新增一種事件類型&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/go-advanced/04-architecture-boundaries/component-boundaries/" data-link-title="4.1 事件來源、處理流程與狀態邊界" data-link-desc="分辨事件來源、事件融合、處理流程、狀態真相與推送邊界">事件來源、處理流程與狀態邊界&lt;/a>。&lt;/p>
&lt;h3 id="domainevent">DomainEvent&lt;/h3>
&lt;p>&lt;code>DomainEvent&lt;/code> 是範例程式中用來承載 domain event 的 Go 型別。它通常包含事件類型、來源、主體、發生時間、接收時間與 payload。&lt;/p>
&lt;p>名稱使用 PascalCase 是因為它是 Go 型別；概念說明時使用 &lt;code>domain event&lt;/code>，程式碼型別使用 &lt;code>DomainEvent&lt;/code>。&lt;/p>
&lt;h3 id="event-envelope">Event Envelope&lt;/h3>
&lt;p>&lt;code>event envelope&lt;/code> 是事件外層的穩定欄位集合。它通常描述 event id、event type、source、subject、occurred time、received time、schema version、&lt;a href="https://tarrragon.github.io/blog/backend/knowledge-cards/correlation-id/" data-link-title="Correlation ID" data-link-desc="說明跨事件或跨服務的關聯識別碼如何支援排障">correlation id&lt;/a> 與 payload。&lt;/p></description><content:encoded><![CDATA[<p>本頁整理 Go 入門篇與進階篇反覆使用的詞彙。核心目的是讓同一個概念在不同章節中保持同一種意思。</p>
<p>Go 教材中的術語應先服務可讀性：詞彙要幫助工程師判斷責任邊界，而不是把簡單程式包裝成複雜架構。小程式可以只有 <code>main.go</code>，服務變大後才逐步引入 event、repository、port、adapter、<a href="/blog/backend/knowledge-cards/projection/" data-link-title="Projection" data-link-desc="說明從事件流或資料變更推算出查詢用讀取視圖的轉換機制">projection</a> 等詞彙。</p>
<h2 id="輸入與行為">輸入與行為</h2>
<h3 id="action">Action</h3>
<p><code>action</code> 表示 client 對服務提出的意圖。它通常來自 <a href="/blog/backend/knowledge-cards/websocket/" data-link-title="WebSocket" data-link-desc="說明 WebSocket 如何提供長連線雙向即時通訊">WebSocket</a> message、HTTP request 或 CLI input，還沒有完成驗證、授權或業務規則套用。</p>
<p>例如 <code>subscribe_topic</code> 可以是 WebSocket action，代表 client 想訂閱某個 <a href="/blog/backend/knowledge-cards/topic/" data-link-title="Topic" data-link-desc="說明 topic 如何把事件依主題分流給不同訂閱者">topic</a>。它進入系統後，router 會先解析 payload，再交給 usecase 或 subscription manager。</p>
<p>延伸閱讀：<a href="/blog/go/06-practical/new-websocket-action/" data-link-title="6.1 如何新增一個即時訊息 action" data-link-desc="修改 client message、路由與 handler">如何新增一個 WebSocket action</a>、<a href="/blog/go-advanced/02-networking-websocket/subscription-routing/" data-link-title="2.3 訂閱模型與訊息路由" data-link-desc="將 client action 對應到主題訂閱狀態">訂閱模型與訊息路由</a>。</p>
<h3 id="command">Command</h3>
<p><code>command</code> 表示 application layer 接受的行為輸入。它已經脫離 HTTP JSON、WebSocket frame 或 CLI flag 的外部格式，變成 usecase 可以理解的資料。</p>
<p>例如 <code>CreateNotificationCommand</code> 可以由 HTTP handler、WebSocket router 或背景 worker 建立。handler 負責把 request DTO 轉成 command，usecase 負責處理 command 的規則。</p>
<p>延伸閱讀：<a href="/blog/go/07-refactoring/handler-boundary/" data-link-title="7.1 把 handler 邏輯拆成可測單元" data-link-desc="分離 HTTP 協定處理與核心邏輯">把 handler 邏輯拆成可測單元</a>。</p>
<h3 id="usecase">Usecase</h3>
<p><code>usecase</code> 表示一個 application 行為。它負責協調 validation、repository、event publisher、clock、<a href="/blog/backend/knowledge-cards/transaction/" data-link-title="Transaction" data-link-desc="說明 transaction 如何讓一組資料變更一起成功或一起回復">transaction</a> 等能力，並保留「這件事如何完成」的規則。</p>
<p>usecase 的重點是行為邊界，不是資料夾名稱。小型程式可以先用函式表達 usecase；當 handler、worker、WebSocket action 都需要共用同一套規則時，再把 usecase 抽出來。</p>
<p>延伸閱讀：<a href="/blog/go/07-refactoring/handler-boundary/" data-link-title="7.1 把 handler 邏輯拆成可測單元" data-link-desc="分離 HTTP 協定處理與核心邏輯">把 handler 邏輯拆成可測單元</a>、<a href="/blog/go/07-refactoring/hexagonal-migration/" data-link-title="7.6 逐步遷移到 ports/adapters 架構" data-link-desc="用 ports 與 adapters 控制 Go 服務的依賴方向">逐步遷移到 ports/adapters 架構</a>。</p>
<h2 id="事件系統">事件系統</h2>
<h3 id="domain-event">Domain Event</h3>
<p><code>domain event</code> 表示系統承認已經發生的內部事實。它和 action 不同：action 是請求，domain event 是經過系統語意整理後的事實。</p>
<p>例如 <code>notification.created</code> 可以表示通知已被建立。這個事件可以來自 HTTP request、WebSocket action、<a href="/blog/backend/knowledge-cards/queue/" data-link-title="Queue" data-link-desc="說明 queue 如何保存等待處理的工作並形成容量邊界">queue</a> message 或 background worker，但進入 processor 前應先被 normalize 成同一種內部模型。</p>
<p>延伸閱讀：<a href="/blog/go/06-practical/new-event-type/" data-link-title="6.2 如何新增一種 domain event" data-link-desc="擴展事件常數、輸入驗證與處理流程">如何新增一種事件類型</a>、<a href="/blog/go-advanced/04-architecture-boundaries/component-boundaries/" data-link-title="4.1 事件來源、處理流程與狀態邊界" data-link-desc="分辨事件來源、事件融合、處理流程、狀態真相與推送邊界">事件來源、處理流程與狀態邊界</a>。</p>
<h3 id="domainevent">DomainEvent</h3>
<p><code>DomainEvent</code> 是範例程式中用來承載 domain event 的 Go 型別。它通常包含事件類型、來源、主體、發生時間、接收時間與 payload。</p>
<p>名稱使用 PascalCase 是因為它是 Go 型別；概念說明時使用 <code>domain event</code>，程式碼型別使用 <code>DomainEvent</code>。</p>
<h3 id="event-envelope">Event Envelope</h3>
<p><code>event envelope</code> 是事件外層的穩定欄位集合。它通常描述 event id、event type、source、subject、occurred time、received time、schema version、<a href="/blog/backend/knowledge-cards/correlation-id/" data-link-title="Correlation ID" data-link-desc="說明跨事件或跨服務的關聯識別碼如何支援排障">correlation id</a> 與 payload。</p>
<p>envelope 的價值是讓不同事件共享同一種路由、去重、記錄與觀測方式。payload 則保留每種事件自己的資料內容。</p>
<p>延伸閱讀：<a href="/blog/go/06-practical/new-event-type/" data-link-title="6.2 如何新增一種 domain event" data-link-desc="擴展事件常數、輸入驗證與處理流程">如何新增一種事件類型</a>、<a href="/blog/go-advanced/04-architecture-boundaries/event-fusion/" data-link-title="4.4 多來源 event 融合" data-link-desc="合併 HTTP、queue、timer 與外部事件來源">多來源 event 融合</a>。</p>
<h3 id="event-log">Event Log</h3>
<p><code>event log</code> 表示記錄 domain event 的事實紀錄。它用來追蹤系統承認過哪些事件，重點是事件語意、順序、去重與後續查詢。</p>
<p>[event <a href="/blog/backend/knowledge-cards/log/" data-link-title="Log" data-link-desc="說明 log 如何記錄單一事件的上下文並支援事故排查">log</a>](/go/backend/knowledge-cards/event-log) 和 structured log 的用途不同。structured log 服務操作診斷，event log 服務業務事實追蹤；兩者可以共享 <code>trace_id</code>、<code>event_id</code>、<code>subject_id</code> 等欄位，但不應互相取代。</p>
<p>延伸閱讀：<a href="/blog/go/06-practical/structured-recording/" data-link-title="6.5 如何新增結構化記錄欄位" data-link-desc="區分 operational log、domain event log 與狀態資料">如何新增結構化記錄欄位</a>、<a href="/blog/go-advanced/06-production-operations/log-fields/" data-link-title="6.3 結構化日誌欄位設計" data-link-desc="讓 log 可 grep、可聚合、可追蹤">結構化日誌欄位設計</a>。</p>
<h3 id="event-store">Event Store</h3>
<p><code>event store</code> 是具備持久化、排序、replay、schema 演進與 transaction 語意的事件儲存。它比 event log 承擔更高的資料一致性責任。</p>
<p>教材中的 event log 先用來建立事件記錄概念；當系統需要 replay、跨節點處理或以事件歷史作為狀態來源時，才需要討論 event store。</p>
<p>延伸閱讀：<a href="/blog/go-advanced/07-distributed-operations/outbox-idempotency/" data-link-title="7.2 Durable queue、outbox 與 idempotency" data-link-desc="設計跨 process 事件傳遞的可靠性與去重邊界">Durable queue、outbox 與 idempotency</a>。</p>
<h3 id="event-sourcing">Event Sourcing</h3>
<p><code>event sourcing</code> 表示以事件歷史作為狀態真相來源。系統透過事件序列重建狀態，事件歷史本身就是真相。</p>
<p>保留 event log 不等於採用 event sourcing。許多服務會記錄 domain event 作為審計或整合用途，但 <a href="/blog/backend/knowledge-cards/source-of-truth/" data-link-title="Source of Truth" data-link-desc="說明正式資料來源如何決定資料判斷、修復與一致性責任">source of truth</a> 仍然是資料庫中的 current state。</p>
<p>延伸閱讀：<a href="/blog/backend/knowledge-cards/source-of-truth/" data-link-title="Source of Truth" data-link-desc="說明正式資料來源如何決定資料判斷、修復與一致性責任">Source of Truth：狀態邊界</a>。</p>
<h3 id="dedup-key">Dedup Key</h3>
<p><code>dedup key</code> 表示用 domain 語意判斷兩筆事件是否是同一件事的 key。它通常由 subject kind、subject id、event type、外部序號或時間窗口組成。</p>
<p>dedup key 的重點是「同一件事」，不是「同一份 bytes」。raw payload hash 可以偵測完全相同的輸入，但無法處理不同來源描述同一個 domain fact 的情境。</p>
<p>延伸閱讀：<a href="/blog/go/07-refactoring/dedup-refactor/" data-link-title="7.3 事件去重邏輯的重構策略" data-link-desc="保留語義鍵並降低重複流程">事件去重邏輯的重構策略</a>、<a href="/blog/go-advanced/04-architecture-boundaries/dedup-key/" data-link-title="4.2 事件去重與語義鍵設計" data-link-desc="用 entity ID、event type、來源語意與時間窗口建立去重鍵">事件去重與語義鍵設計</a>。</p>
<h3 id="idempotency-key">Idempotency Key</h3>
<p><code>idempotency key</code> 表示外部呼叫或重試流程用來安全重複執行的 key。它常出現在 HTTP request、queue message 或 outbox publish 流程中。</p>
<p><a href="/blog/backend/knowledge-cards/idempotency/" data-link-title="Idempotency" data-link-desc="說明同一操作執行多次時如何保持結果一致">idempotency</a> key 和 dedup key 的責任不同。idempotency key 保護同一次操作的重試；dedup key 保護 domain 層面上同一件事的重複描述。</p>
<p>延伸閱讀：<a href="/blog/go-advanced/07-distributed-operations/outbox-idempotency/" data-link-title="7.2 Durable queue、outbox 與 idempotency" data-link-desc="設計跨 process 事件傳遞的可靠性與去重邊界">Durable queue、outbox 與 idempotency</a>。</p>
<h2 id="狀態與資料模型">狀態與資料模型</h2>
<h3 id="repository">Repository</h3>
<p><code>repository</code> 表示狀態或資料存取的邊界。它負責保存與讀取某一類資料，並讓外部呼叫者不需要知道資料目前存在 memory、<a href="/blog/backend/knowledge-cards/database/" data-link-title="Database" data-link-desc="說明 database 在後端系統中如何承擔正式狀態、查詢與一致性責任">database</a> 或遠端服務。</p>
<p>repository 的核心價值是權責集中。當狀態轉移、copy boundary、transaction 或查詢模型開始變複雜時，把資料能力集中在 repository 會比讓 handler 直接操作 map 更穩定。</p>
<p>延伸閱讀：<a href="/blog/go/06-practical/repository-port/" data-link-title="6.6 如何新增 repository port" data-link-desc="先建立儲存邊界，再決定 memory、SQLite 或外部資料庫實作">如何新增 repository port</a>、<a href="/blog/go/07-refactoring/state-boundary/" data-link-title="7.4 狀態管理的安全邊界" data-link-desc="用 lock、copy 與 API 限制保護共享狀態">狀態管理的安全邊界</a>。</p>
<h3 id="repository-port">Repository Port</h3>
<p><code>repository port</code> 表示 application layer 需要的資料能力介面。它由 usecase 的需求定義，而不是由資料庫表格或具體儲存技術定義。</p>
<p>例如 usecase 只需要 <code>Save</code> 和 <code>FindByID</code>，port 就只暴露這兩個方法。memory repository、SQL repository 或 test fake 都可以實作同一個 port。</p>
<p>延伸閱讀：<a href="/blog/go/06-practical/repository-port/" data-link-title="6.6 如何新增 repository port" data-link-desc="先建立儲存邊界，再決定 memory、SQLite 或外部資料庫實作">如何新增 repository port</a>、<a href="/blog/go-advanced/07-distributed-operations/database-transactions/" data-link-title="7.1 資料庫 transaction 與 schema migration" data-link-desc="把 repository 邊界延伸到資料庫交易、migration 與一致性語意">資料庫 transaction 與 schema migration</a>。</p>
<h3 id="state-owner">State Owner</h3>
<p><code>state owner</code> 表示擁有某份可變狀態寫入權的元件。它可以是 mutex 保護的 repository，也可以是單一 goroutine 持有狀態並透過 channel 接收 command。</p>
<p>state owner 的重點是只有一個地方能決定狀態如何改變。其他元件應送入 command 或 event，而不是直接修改內部 map、slice 或 pointer。</p>
<p>延伸閱讀：<a href="/blog/go-advanced/01-concurrency-patterns/shared-state/" data-link-title="1.4 共享狀態與複製邊界" data-link-desc="用 lock 與 copy 保護長期服務的狀態資料">共享狀態與複製邊界</a>、<a href="/blog/backend/knowledge-cards/source-of-truth/" data-link-title="Source of Truth" data-link-desc="說明正式資料來源如何決定資料判斷、修復與一致性責任">Source of Truth：狀態邊界</a>。</p>
<h3 id="source-of-truth">Source of Truth</h3>
<p><code>source of truth</code> 表示狀態轉移的寫入權責，是系統承認「誰能決定目前狀態」的邊界。</p>
<p>小型服務的 source of truth 可能是 memory repository；加入資料庫後，source of truth 仍然包含 application 的狀態規則、transaction 邊界與持久化資料。</p>
<p>延伸閱讀：<a href="/blog/backend/knowledge-cards/source-of-truth/" data-link-title="Source of Truth" data-link-desc="說明正式資料來源如何決定資料判斷、修復與一致性責任">Source of Truth：狀態邊界</a>。</p>
<h3 id="projection--read-model">Projection / Read Model</h3>
<p><code>projection</code> 或 <code>read model</code> 表示為讀取需求整理出的資料模型。它可以來自 domain state、event history 或其他來源，目標是讓查詢、列表、即時推送或 UI 顯示更直接。</p>
<p>projection 可以提升讀取效率與簡化 response 組裝，但它不應反過來成為狀態真相。狀態規則仍然應由 repository、state owner 或 usecase 控制。</p>
<p>延伸閱讀：<a href="/blog/go/06-practical/state-fields/" data-link-title="6.3 如何擴展狀態投影欄位" data-link-desc="更新狀態模型、repository 與 API 輸出">如何擴展狀態資料欄位</a>、<a href="/blog/backend/knowledge-cards/source-of-truth/" data-link-title="Source of Truth" data-link-desc="說明正式資料來源如何決定資料判斷、修復與一致性責任">Source of Truth：狀態邊界</a>。</p>
<h3 id="response-view">Response View</h3>
<p><code>response view</code> 表示對外輸出的資料形狀。它負責 JSON tag、<code>omitempty</code>、顯示文字、相容性欄位與 API contract。</p>
<p>response view 的核心責任是翻譯內部資料給外部使用者。顯示文字、前端 badge、API 版本相容欄位通常應放在 response view，而不是混進 domain state。</p>
<p>延伸閱讀：<a href="/blog/go/06-practical/state-fields/" data-link-title="6.3 如何擴展狀態投影欄位" data-link-desc="更新狀態模型、repository 與 API 輸出">如何擴展狀態資料欄位</a>。</p>
<h3 id="dto">DTO</h3>
<p><code>DTO</code> 表示資料傳輸形狀。它常用於 HTTP request、HTTP response、queue message、WebSocket payload 或外部 API client。</p>
<p>DTO 的責任是描述邊界格式。它可以有 JSON tag、相容性欄位與外部命名慣例，但不應直接取代 domain model、repository model 或 command。</p>
<p>延伸閱讀：<a href="/blog/go/07-refactoring/handler-boundary/" data-link-title="7.1 把 handler 邏輯拆成可測單元" data-link-desc="分離 HTTP 協定處理與核心邏輯">把 handler 邏輯拆成可測單元</a>、<a href="/blog/go/07-refactoring/domain-packages/" data-link-title="7.5 以 domain 重新整理 package" data-link-desc="讓 account、job、event、workflow 這類領域邊界在目錄中可見">以 domain 重新整理 package</a>。</p>
<h3 id="copy-boundary">Copy Boundary</h3>
<p><code>copy boundary</code> 表示回傳或接收 slice、map、pointer 時用複製保護狀態所有權的邊界。它防止呼叫端透過引用修改 repository 內部資料。</p>
<p>Go 的 slice、map 與 pointer 都可能共享底層資料，所以 repository 回傳資料時要判斷是否需要 shallow copy 或 deep copy。資料量大時，可以改用分頁、projection 或 snapshot cache 來控制成本。</p>
<p>延伸閱讀：<a href="/blog/go/02-types-data/pointers-copy/" data-link-title="2.5 指標與資料複製邊界" data-link-desc="理解指標、slice 與共享狀態的防護策略">指標與資料複製邊界</a>、<a href="/blog/go-advanced/01-concurrency-patterns/shared-state/" data-link-title="1.4 共享狀態與複製邊界" data-link-desc="用 lock 與 copy 保護長期服務的狀態資料">共享狀態與複製邊界</a>。</p>
<h2 id="架構邊界">架構邊界</h2>
<h3 id="port">Port</h3>
<p><code>port</code> 表示 application 依賴的能力介面。它描述「我需要什麼能力」，例如儲存通知、發布事件、讀取外部資料或取得現在時間。</p>
<p>Go 的 port 通常是小 interface。它應由使用方定義，讓 application 可以依賴抽象能力，而不是依賴具體資料庫、message <a href="/blog/backend/knowledge-cards/broker/" data-link-title="Broker" data-link-desc="說明 broker 在訊息傳遞系統中負責保存、路由與交付訊息">broker</a> 或 <a href="/blog/backend/knowledge-cards/http-client/" data-link-title="HTTP Client" data-link-desc="說明服務呼叫外部 HTTP 依賴時需要管理 timeout、連線與重試">HTTP client</a>。</p>
<p>延伸閱讀：<a href="/blog/go/07-refactoring/interface-boundary/" data-link-title="7.2 用 interface 隔離外部依賴" data-link-desc="建立小而穩定的測試替身">用 interface 隔離外部依賴</a>、<a href="/blog/go/07-refactoring/hexagonal-migration/" data-link-title="7.6 逐步遷移到 ports/adapters 架構" data-link-desc="用 ports 與 adapters 控制 Go 服務的依賴方向">逐步遷移到 ports/adapters 架構</a>。</p>
<h3 id="adapter">Adapter</h3>
<p><code>adapter</code> 表示把外部技術或協定接到 application port 的實作或轉換層。它可以是 HTTP handler、WebSocket router、SQL repository、queue <a href="/blog/backend/knowledge-cards/consumer/" data-link-title="Consumer" data-link-desc="說明 consumer 如何取得等待處理的工作並產生業務結果">consumer</a> 或 external API client。</p>
<p>adapter 的核心責任是翻譯邊界格式。application 不應知道 HTTP body、SQL row、queue message 或 WebSocket frame 的細節。</p>
<p>延伸閱讀：<a href="/blog/go/07-refactoring/hexagonal-migration/" data-link-title="7.6 逐步遷移到 ports/adapters 架構" data-link-desc="用 ports 與 adapters 控制 Go 服務的依賴方向">逐步遷移到 ports/adapters 架構</a>。</p>
<h3 id="inbound-adapter">Inbound Adapter</h3>
<p><code>inbound adapter</code> 表示把外部輸入轉成 application command 或 domain event 的 adapter。HTTP handler、WebSocket router、CLI command、queue consumer 都可以是 inbound adapter。</p>
<p>inbound adapter 通常負責 parsing、基本 validation、身份資訊提取與錯誤轉換。行為規則應交給 usecase 或 processor。</p>
<p>延伸閱讀：<a href="/blog/go/07-refactoring/handler-boundary/" data-link-title="7.1 把 handler 邏輯拆成可測單元" data-link-desc="分離 HTTP 協定處理與核心邏輯">把 handler 邏輯拆成可測單元</a>、<a href="/blog/go-advanced/02-networking-websocket/read-write-pump/" data-link-title="2.1 read pump / write pump 模式" data-link-desc="分離 WebSocket 讀取、寫入與心跳">read pump / write pump 模式</a>。</p>
<h3 id="outbound-adapter">Outbound Adapter</h3>
<p><code>outbound adapter</code> 表示實作 application port 並連接外部系統的 adapter。SQL repository、Redis cache、message publisher、email sender、external API client 都屬於這類。</p>
<p>outbound adapter 的重點是隔離技術細節。usecase 依賴 port；adapter 承擔 retry、serialization、connection、<a href="/blog/backend/knowledge-cards/timeout/" data-link-title="Timeout" data-link-desc="說明等待外部操作的時間上限如何保護資源與使用者體驗">timeout</a> 與外部錯誤轉換。</p>
<p>延伸閱讀：<a href="/blog/go/06-practical/repository-port/" data-link-title="6.6 如何新增 repository port" data-link-desc="先建立儲存邊界，再決定 memory、SQLite 或外部資料庫實作">如何新增 repository port</a>、<a href="/blog/go-advanced/07-distributed-operations/database-transactions/" data-link-title="7.1 資料庫 transaction 與 schema migration" data-link-desc="把 repository 邊界延伸到資料庫交易、migration 與一致性語意">資料庫 transaction 與 schema migration</a>。</p>
<h3 id="normalizer">Normalizer</h3>
<p><code>normalizer</code> 表示把 raw input 轉成內部模型的元件。它常出現在事件系統中，負責把 HTTP callback、queue message 或外部 API response 轉成 <code>DomainEvent</code>。</p>
<p>normalizer 的責任是建立內部一致性。不同來源可以有不同 raw format，但進入 processor 前應變成一致的 domain event。</p>
<p>延伸閱讀：<a href="/blog/go-advanced/04-architecture-boundaries/component-boundaries/" data-link-title="4.1 事件來源、處理流程與狀態邊界" data-link-desc="分辨事件來源、事件融合、處理流程、狀態真相與推送邊界">事件來源、處理流程與狀態邊界</a>。</p>
<h3 id="processor">Processor</h3>
<p><code>processor</code> 表示處理 domain event 或 background job 的元件。它負責套用規則、去重、更新狀態、寫入 event log 或呼叫 publisher。</p>
<p>processor 應處理已經 normalize 的資料。它不應依賴 HTTP request body、WebSocket frame 或 queue message 的原始格式。</p>
<p>延伸閱讀：<a href="/blog/go/06-practical/new-background-worker/" data-link-title="6.4 如何新增背景工作流程" data-link-desc="接入 context、channel 與 shutdown">如何新增背景工作流程</a>、<a href="/blog/go-advanced/04-architecture-boundaries/component-boundaries/" data-link-title="4.1 事件來源、處理流程與狀態邊界" data-link-desc="分辨事件來源、事件融合、處理流程、狀態真相與推送邊界">事件來源、處理流程與狀態邊界</a>。</p>
<h2 id="工具與靜態分析">工具與靜態分析</h2>
<h3 id="ast-walker">AST Walker</h3>
<p><code>AST walker</code> 表示用 visitor pattern 對解析後的抽象語法樹做 DFS 走訪，並在每個節點上套用規則或收集資訊的處理方式。在 Go 中，<code>ast.Walk</code> 是 <code>go/ast</code> 與 <code>goldmark/ast</code> 都採用的 API 慣例：傳入 root node 跟 walker 函式，framework 負責遞迴下降、把 entering / exiting 時機告訴你。</p>
<p>walker 的責任是<strong>把樹的走訪邏輯外部化</strong>，讓規則本身只關心「這個節點我要做什麼」。多條規則可以共用同一次走訪；複雜的 context（例如父節點是什麼、目前在哪個 heading 層級）由 walker 狀態機累積，不污染 rule 程式碼。Block vs inline 節點的判讀是 walker 用法最常見的陷阱 — 對 inline 節點呼叫 <code>Lines()</code> 會 panic，因此需要走上去找最近的 block 節點再取位置。</p>
<p>延伸閱讀：<a href="/blog/go/09-tooling-and-analysis/goldmark-ast-basics/" data-link-title="9.2 第三方 parser 整合：goldmark AST 入門" data-link-desc="用 goldmark 把 markdown 解析成 AST，掌握 ast.Walk visitor 模式、block 與 inline 節點的判讀、byte offset 如何定位到行號">goldmark AST 入門</a>、<a href="/blog/posts/%E4%BB%80%E9%BA%BC%E6%98%AF-ast-%E5%BE%9E%E5%AD%97%E4%B8%B2%E5%88%B0%E8%AA%9E%E6%B3%95%E6%A8%B9%E7%9A%84%E8%A6%96%E8%A7%92%E8%BD%89%E6%8F%9B/" data-link-title="什麼是 AST — 從字串到語法樹的視角轉換" data-link-desc="AST 與 regex 的差異判準：規則需要知道文字處在什麼結構中時 regex 就不夠。附 regex 誤判的具體 case。">什麼是 AST</a>。</p>
<h3 id="idempotent-文字改寫">Idempotent 文字改寫</h3>
<p><code>idempotent 文字改寫</code> 表示對同一輸入跑一次或多次、結果相同的轉換契約。應用在 <code>gofmt</code>、<code>prettier</code>、<code>ruff fix</code> 這類 formatter 與 fixer 上，契約讓工具能安全地接到 pre-commit hook（重複跑不會累積漂移）、能用 <code>--check</code> 跟 <code>--fix</code> 共用同一套邏輯（差別只在要不要寫檔）、能分段除錯。</p>
<p>實作上的核心技巧是<strong>每條規則自己冪等</strong>：判斷「違規才修」而非「無論如何都套用」。多規則串成流水線時，每條規則的輸出要是下一條的合法輸入；行數變動時要重建 LineContext 索引。測試可以用「跑兩次結果相等」當斷言，直接驗證契約。</p>
<p>延伸閱讀：<a href="/blog/go/09-tooling-and-analysis/ast-idempotent-rewriting/" data-link-title="9.3 AST 驅動的 idempotent 文字改寫" data-link-desc="用 AST 定位位置、用 line-based 或 byte-level 改寫；設計多條 rule 的執行順序；--check 跟 --fix 如何共用邏輯">AST 驅動的 idempotent 文字改寫</a>、<a href="/blog/go/09-tooling-and-analysis/pre-commit-and-ci/" data-link-title="9.6 Pre-commit hook 與 CI 整合" data-link-desc="工具寫完只是起點；接到 pre-commit hook 跟 CI 才真正守住品質。Re-staging、dry-run vs apply、不能繞過的邊界">Pre-commit hook 與 CI 整合</a>。</p>
<h3 id="跨檔案-link-graph">跨檔案 Link Graph</h3>
<p><code>跨檔案 link graph</code> 表示把整個 repo 的檔案視為節點、檔案間連結視為邊的資料結構，用一次 parse 之後的 in-memory map 支援反向查詢（這個目標被誰引用？這個連結的目標存在嗎？）。避免 N² 的「每次查詢都重 parse 全部檔案」成本。</p>
<p>典型應用包含 orphan 偵測（節點沒有 inbound edge）、broken link 偵測（邊指到不存在的節點）、dependency cycle 偵測（graph 有環）、reverse index 建構（哪些檔案引用了 X）。Graph 一次建好後，所有後續 query 都是 map lookup 或 slice scan，microsecond 級。</p>
<p>延伸閱讀：<a href="/blog/go/09-tooling-and-analysis/cross-file-graph-analysis/" data-link-title="9.4 跨檔案圖分析：從 lint 走到 static analysis" data-link-desc="Single-file 規則用 AST 搞定；跨檔 orphan 偵測、broken link、backlink 完整性需要把整個 repo 建成圖再走訪。用 mdtools cards 為例">跨檔案圖分析</a>。</p>
<h3 id="tripwire-決策">Tripwire 決策</h3>
<p><code>tripwire 決策</code> 表示用事前約定的可量測條件，在命中時觸發「重新評估是否升級」的決策方法。目的是避開「太早升級」（過度工程化）跟「太晚升級」（信譽破產）兩種失敗。</p>
<p>tripwire 的重點是<strong>把評估時機從模糊直覺變成明確觸發</strong>。設計時要選合適的訊號（誤判率、維護成本、使用者投訴頻率），並定期檢驗條件是否仍然合理。適用於技術選型（regex vs AST、Python vs Go）、架構升級（單體 → 微服務）、工具邊界（lint vs full type-check）等決策。</p>
<p>延伸閱讀：<a href="/blog/go/09-tooling-and-analysis/tool-decision-tripwire/" data-link-title="9.5 工具決策：regex 到 AST、Python 到 Go 的 tripwire" data-link-desc="什麼訊號代表工具該升級到下一個層次；用 WRAP 框架做語言與實作層的技術決策；延遲決策的成本">工具決策的 tripwire</a>。</p>
<h3 id="pre-commit-hook-定位">Pre-commit Hook 定位</h3>
<p><code>pre-commit hook</code> 表示 git commit 流程中、commit 實際建立前自動觸發的檢查點。定位上它是<strong>快速守門員</strong>，負責秒級可完成的 lint / fmt / 基本驗證，把錯誤攔在本機、讓作者早期回饋。</p>
<p>跟 CI 的分工是：hook 守本機（快、只看 staged 檔案），CI 守共享 branch（慢、乾淨環境、完整 test）。兩者互補、共用同一套工具與規則。落地時要處理 re-staging（hook 自動修檔後 <code>git add</code> 回 staged）、exit code 語意（0 = pass、1 = violation block、2 = tool failure）、<code>--no-verify</code> 繞過的邊界（原則上禁止，緊急情境有條件例外）。</p>
<p>延伸閱讀：<a href="/blog/go/09-tooling-and-analysis/pre-commit-and-ci/" data-link-title="9.6 Pre-commit hook 與 CI 整合" data-link-desc="工具寫完只是起點；接到 pre-commit hook 跟 CI 才真正守住品質。Re-staging、dry-run vs apply、不能繞過的邊界">Pre-commit hook 與 CI 整合</a>。</p>
<h2 id="使用方式">使用方式</h2>
<p>閱讀章節時若遇到同一個詞在不同情境出現，先回到本頁確認它的核心責任。入門篇會用簡化範例建立語感；進階篇會把同一批詞彙放進並發、WebSocket、資料庫、觀測與部署壓力中重新檢查；工具類章節會把型別、interface、package 等概念落到 CLI、parser、graph analysis 等非服務場景。</p>
]]></content:encoded></item></channel></rss>