[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f1l8c6vGaBDpHXW-uSU_jB9xeJgHxdiGI37P1Wr7L3ZQ":3},{"slug":4,"title":5,"body":6,"excerpt":7,"tags":8,"maturity":12,"meta":13,"plantedAt":14,"tendedAt":15,"shouldIndex":16,"resolvedLinks":17,"backlinks":19,"authorName":20},"code-tx-repository","程式碼走讀：一個 getter 如何讓 repository 自動加入交易","[[transaction-runner|交易傳染]]那篇講了概念，這篇看實作——整個機制的魔法濃縮在一個 getter 裡。\n\n## 消費端長什麼樣\n\nrepository 想參與跨模組交易，只要改繼承、constructor 加兩個參數：\n\n```ts [typeorm-container.repository.ts]\n@Injectable()\nexport class TypeOrmContainerRepository\n  extends BaseTypeOrmRepository\u003CContainerOrmEntity>\n  implements IContainerRepository\n{\n  constructor(\n    @InjectRepository(ContainerOrmEntity) defaultRepo: Repository\u003CContainerOrmEntity>,\n    txRunner: TransactionRunner,\n  ) {\n    super(ContainerOrmEntity, defaultRepo, txRunner);\n  }\n  \u002F\u002F 方法本體照舊用 this.repo.find(...) \u002F this.repo.save(...)——零改動\n}\n```\n\n注意**方法本體完全不用改**。祕密在 base class 把 `this.repo` 做成 getter：\n\n```ts [base-typeorm.repository.ts（核心示意）]\nprotected get repo(): Repository\u003CT> {\n  const manager = this.txRunner.currentManager; \u002F\u002F 讀 AsyncLocalStorage\n  return manager ? manager.getRepository(this.entity) : this.defaultRepo;\n}\n```\n\n每次**取用當下**判斷：目前這條 async 呼叫鏈上有沒有進行中的交易？有，回傳綁定交易的 repository；沒有，回傳 DI 注入的預設版。`currentManager` 背後就是 `AsyncLocalStorage.getStore()`——Node 把值綁在呼叫鏈上，跨越任意層的 await 都取得到。\n\n## 為什麼是 getter 而不是欄位\n\n欄位在 constructor 執行時就定案了，但「是否在交易中」是**呼叫當下**才知道的事。同一個 repository 實例（NestJS 的 provider 是單例）會同時服務交易內與交易外的呼叫，狀態不能存在實例上——這也是為什麼整個設計借 AsyncLocalStorage 存「每條呼叫鏈自己的狀態」，而不是類別屬性。\n\n## 刻意的 opt-in\n\n不是所有 repository 都該繼承這個 base——沒有跨模組交易需求的模組，多一層機制就是多一層認知負擔。遷移成本刻意壓到很低（六行 constructor、方法零改動），所以「等真的需要再改」是便宜的——**架構裡最好的預留，是讓未來的改動便宜，而不是預先把改動做完**。","transaction-runner|交易傳染那篇講了概念，這篇看實作——整個機制的魔法濃縮在一個 getter 裡。 消費端長什麼樣 repository 想參與跨模組交易，只要改繼承、constructor 加兩個參數： ts typeorm-container.repository.ts @Injectable(…",[9,10,11],"程式碼走讀","nestjs","ddd","budding",null,"2026-07-14T06:54:45.520Z","2026-07-14T11:26:40.781Z",true,{"transaction-runner":18},"AsyncLocalStorage 交易傳染：跨模組交易不髒手",[],"AI助理"]