<?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>Shared Mutable State on Tarragon</title><link>https://tarrragon.github.io/blog/tags/shared-mutable-state/</link><description>Recent content in Shared Mutable State on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 07 Aug 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/shared-mutable-state/index.xml" rel="self" type="application/rss+xml"/><item><title>Shared Mutable State（共享可變狀態）</title><link>https://tarrragon.github.io/blog/ddd/knowledge-cards/shared-mutable-state/</link><pubDate>Fri, 07 Aug 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/ddd/knowledge-cards/shared-mutable-state/</guid><description>&lt;p>共享可變狀態是一個值放在多方都讀得到、任一方都改得動的位置，而它的當前值決定其他地方的行為。控制器持有的欄位、服務層的快取、被多個畫面訂閱的狀態物件都屬於這一類。它跟 &lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/snapshot/" data-link-title="Snapshot" data-link-desc="歷史記錄是否應該凍結當時狀態時使用。Snapshot 是某一時刻的狀態複本——歷史不隨現在的資料漂移。">snapshot&lt;/a> 相對：snapshot 凍結某一時刻的複本、不隨現在漂移；共享可變狀態則沒有版本，讀到的永遠是最後一次寫入的結果。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>共享可變狀態的代價不在讀寫本身，在於它&lt;strong>會長出跨函式的時序約束&lt;/strong>：「這個值必須活過那一次重設」「A 要在 B 清空之前讀完」。這類約束是 &lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/invariant/" data-link-title="Invariant" data-link-desc="領域模型的約束規則落在哪一層時使用。不變式是在物件整個生命週期都必須為真的業務規則——狀態只能沿流程轉換、被同一條規則綁住的欄位必須一起換。">invariant&lt;/a> 的一種，但落點的處境不同——單一物件的不變式可以做進建構子或型別簽名，跨函式的時序約束在型別簽名裡沒有位置、建構子也檢查不到，剩下的落點只有一條會紅的測試（強制層的取捨見 &lt;a href="https://tarrragon.github.io/blog/ddd/invariant-enforcement-layers/" data-link-title="不變式的強制層次" data-link-desc="業務約束落在文件層、型別層、執行層的差異與代價：違反時是靜默、編譯失敗還是當場拒絕。含不變式被撞時「需求違規 vs 約束錯形」的分辨、存在條件與輸入品質的分層邊界。">不變式的強制層次&lt;/a>）。&lt;/p>
&lt;p>把值收成呼叫時傳入的參數，可見範圍縮到那一次呼叫，約束就不存在了。所以遇到這類約束時的第一個問題是「這個值需不需要共享」，而不是「誰來守這個順序」。&lt;/p>
&lt;p>&lt;strong>可被觀察不等於可被寫入&lt;/strong>。一個值要讓畫面訂閱（&lt;a href="https://tarrragon.github.io/blog/ddd/knowledge-cards/state-stream/" data-link-title="State Stream（狀態流）" data-link-desc="畫面刷新靠補償、或考慮拿既有事件當刷新訊號時使用。狀態流是持續發布「資料當前值」的觀測載體——新值蓋過舊值、錯過中間值無代價，回答「現在是什麼」。">state stream&lt;/a> 的形態）是讀側需求，跟「誰改得動它」是兩個獨立的決定。兩者被同一個欄位承擔時，「因為畫面要看所以只好共享」會被誤當成「所以也只好開放多方寫入」。&lt;/p>
&lt;h2 id="可觀察訊號">可觀察訊號&lt;/h2>
&lt;p>註解出現「重設時不要清掉這個值」「先讀再清」這類順序叮嚀時，共享可變狀態已經長出時序約束，而強制還停在文件層。測試要先擺好某個欄位才跑得起來、而那個欄位不在被測函式的參數列裡，是同一個訊號的測試側形態。同一個欄位的寫入點散在多個控制器或服務時，「當前值是誰寫的」已經無法從單一位置回答。&lt;/p>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>先問這個值需不需要共享——能收成參數就消除整組約束，不需要任何一層守它。必須共享時把寫入點收斂到單一路徑（讀可以開放、寫要有唯一入口），並把「可觀察」與「可寫入」分開設計。約束消不掉時，交給一條會紅的行為測試，而不是交給一行叮嚀順序的註解——判準與當場可執行的驗證見 &lt;a href="https://tarrragon.github.io/blog/report/protective-comment-signals-missing-enforcement/" data-link-title="寫註解的動機是怕被改壞時，要處理的是那個約束、不是那行文字" data-link-desc="準備為一段程式寫註解、而動機是怕有人改壞它時使用。註解不參與執行、改壞的當下不產生訊號；防護需求要先問這個約束能不能被消除，不能消除才交給會發聲的機制，而判定靠當場破壞。">#253 寫註解的動機是怕被改壞時要處理的是那個約束&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>共享可變狀態是一個值放在多方都讀得到、任一方都改得動的位置，而它的當前值決定其他地方的行為。控制器持有的欄位、服務層的快取、被多個畫面訂閱的狀態物件都屬於這一類。它跟 <a href="/blog/ddd/knowledge-cards/snapshot/" data-link-title="Snapshot" data-link-desc="歷史記錄是否應該凍結當時狀態時使用。Snapshot 是某一時刻的狀態複本——歷史不隨現在的資料漂移。">snapshot</a> 相對：snapshot 凍結某一時刻的複本、不隨現在漂移；共享可變狀態則沒有版本，讀到的永遠是最後一次寫入的結果。</p>
<h2 id="概念位置">概念位置</h2>
<p>共享可變狀態的代價不在讀寫本身，在於它<strong>會長出跨函式的時序約束</strong>：「這個值必須活過那一次重設」「A 要在 B 清空之前讀完」。這類約束是 <a href="/blog/ddd/knowledge-cards/invariant/" data-link-title="Invariant" data-link-desc="領域模型的約束規則落在哪一層時使用。不變式是在物件整個生命週期都必須為真的業務規則——狀態只能沿流程轉換、被同一條規則綁住的欄位必須一起換。">invariant</a> 的一種，但落點的處境不同——單一物件的不變式可以做進建構子或型別簽名，跨函式的時序約束在型別簽名裡沒有位置、建構子也檢查不到，剩下的落點只有一條會紅的測試（強制層的取捨見 <a href="/blog/ddd/invariant-enforcement-layers/" data-link-title="不變式的強制層次" data-link-desc="業務約束落在文件層、型別層、執行層的差異與代價：違反時是靜默、編譯失敗還是當場拒絕。含不變式被撞時「需求違規 vs 約束錯形」的分辨、存在條件與輸入品質的分層邊界。">不變式的強制層次</a>）。</p>
<p>把值收成呼叫時傳入的參數，可見範圍縮到那一次呼叫，約束就不存在了。所以遇到這類約束時的第一個問題是「這個值需不需要共享」，而不是「誰來守這個順序」。</p>
<p><strong>可被觀察不等於可被寫入</strong>。一個值要讓畫面訂閱（<a href="/blog/ddd/knowledge-cards/state-stream/" data-link-title="State Stream（狀態流）" data-link-desc="畫面刷新靠補償、或考慮拿既有事件當刷新訊號時使用。狀態流是持續發布「資料當前值」的觀測載體——新值蓋過舊值、錯過中間值無代價，回答「現在是什麼」。">state stream</a> 的形態）是讀側需求，跟「誰改得動它」是兩個獨立的決定。兩者被同一個欄位承擔時，「因為畫面要看所以只好共享」會被誤當成「所以也只好開放多方寫入」。</p>
<h2 id="可觀察訊號">可觀察訊號</h2>
<p>註解出現「重設時不要清掉這個值」「先讀再清」這類順序叮嚀時，共享可變狀態已經長出時序約束，而強制還停在文件層。測試要先擺好某個欄位才跑得起來、而那個欄位不在被測函式的參數列裡，是同一個訊號的測試側形態。同一個欄位的寫入點散在多個控制器或服務時，「當前值是誰寫的」已經無法從單一位置回答。</p>
<h2 id="設計責任">設計責任</h2>
<p>先問這個值需不需要共享——能收成參數就消除整組約束，不需要任何一層守它。必須共享時把寫入點收斂到單一路徑（讀可以開放、寫要有唯一入口），並把「可觀察」與「可寫入」分開設計。約束消不掉時，交給一條會紅的行為測試，而不是交給一行叮嚀順序的註解——判準與當場可執行的驗證見 <a href="/blog/report/protective-comment-signals-missing-enforcement/" data-link-title="寫註解的動機是怕被改壞時，要處理的是那個約束、不是那行文字" data-link-desc="準備為一段程式寫註解、而動機是怕有人改壞它時使用。註解不參與執行、改壞的當下不產生訊號；防護需求要先問這個約束能不能被消除，不能消除才交給會發聲的機制，而判定靠當場破壞。">#253 寫註解的動機是怕被改壞時要處理的是那個約束</a>。</p>
]]></content:encoded></item></channel></rss>