بازگشت به کتابخانهکتابخانه10.4کارگاه Fan-out/Fan-in برای سه Notification Adapter
مهندسی نرم‌افزار ایجنت‌محورAGENTIC SOFTWARE ENGINEERING — از Grilling و Context Engineering تا TDD، Ralph، Subagent، امنیت و تحویل قابل‌اعتماد
v1.0.0
01واقعیت مهندسی ایجنت‌محور
02Grilling و زبان مشترک
03Context Engineering
04Spec-Driven Development و برنامهٔ اجرایی
05Vertical Slices و تحویل تدریجی
06TDD با ایجنت‌ها
07گردش‌کار روزانه، Debug و UI Prototyping
08ساخت هارنس — Rules، Skills، Hooks و MCP
09Ralph و اجرای طولانی‌مدت
10Subagent، Worktree و ارکستراسیون موازی
11Verification، Security و مقیاس تیمی
12پروژهٔ نهایی سناریومحور
LESSON 10.4فصل ۱۰ — Subagent، Worktree و ارکستراسیون موازی

کارگاه Fan-out/Fan-in برای سه Notification Adapter

  • ~۷۵ دقیقه
  • ۵ پرسش
  • متن را انتخاب کن تا هایلایت شود

در این کارگاه یک قرارداد پیام ثابت را به سه worker مستقل می‌سپاریم: email، SMS و in-app. هر worker در worktree و runtime namespace جدا کار می‌کند؛ سپس evidence جمع می‌شود، merge با ترتیب آگاهانه انجام می‌گیرد و full integration verification نشان می‌دهد branchهای سبز واقعاً با هم سازگارند.

## اهداف یادگیری

پس از این کارگاه می‌توانی contract test را پیش از fan-out منتشر کنی؛ سه worktree و runtime namespace بسازی؛ file ownership را enforce کنی؛ evidence هر worker را جمع کنی؛ fan-in را با merge order امن انجام دهی؛ conflict را بدون overwrite حل کنی؛ و verification کامل پس از merge را اجرا کنی.

## جایگاه در چرخه

این خروجی نهایی فصل است: task graph درس ۳۶ و topology درس ۳۷ را به integration قابل مشاهده تبدیل می‌کند. رفتار booking ثابت است: پس از reschedule موفق، notification با `tenantId`، `appointmentId`، channel و متن بی‌خطر تولید می‌شود. ارسال بیرونی در آزمایشگاه fake است؛ credential یا endpoint واقعی نداریم.

## قرارداد مشترک و ownership

ابتدا owner قرارداد این interface را منتشر می‌کند:

type NotificationMessageV1 = {
  tenantId: string;
  appointmentId: string;
  channel: "email" | "sms" | "in-app";
  body: string;
  idempotencyKey: string;
};

قانون‌ها: tenant و appointment از context معتبر می‌آیند؛ raw secret یا PII اضافی در log نیست؛ duplicate message با همان key یک delivery منطقی است؛ unknown additive fields نادیده گرفته می‌شوند. contract tests شکل، channel، idempotency و tenant isolation را می‌سنجند.

مالکیت به این صورت است:

contract: packages/contracts/src/notification.ts       contract-owner
email:    apps/api/src/notifications/email-adapter.ts email-worker
sms:      apps/api/src/notifications/sms-adapter.ts   sms-worker
in-app:   apps/web/src/notifications/in-app-adapter.ts inapp-worker
wiring:   apps/api/src/notifications/index.ts          integration-owner

workerها wiring، contract، lockfile یا migration را تغییر نمی‌دهند. هرکدام fake provider و test fixture خودش را دارد. integration owner تنها کسی است که fan-in، wiring و full suite را انجام می‌دهد.

## اجرای workshop

Fan-out

۱. integration owner baseline، contract v1 و test را در branch پایه منتشر می‌کند. ۲. سه worktree از همان commit ساخته می‌شود. ۳. هر worker task contract خود را تأیید و namespace جدا رزرو می‌کند: portهای 4321، 4322 و 4323؛ database یا fake store با run-id جدا؛ cache prefix جدا؛ process و generated path جدا. ۴. worker فقط adapter خود را پیاده و evidence می‌سازد.

فرمان‌ها در یک repository آموزشی و sandbox محلی:

git worktree add -b agent/notify-email ../wt-email origin/main
git worktree add -b agent/notify-sms ../wt-sms origin/main
git worktree add -b agent/notify-inapp ../wt-inapp origin/main
pnpm exec vitest run packages/contracts/src/notification.contract.test.ts
git -C ../wt-email diff --check

`origin/main` در این snippet به معنای baseline ثبت‌شده است؛ در محیط واقعی revision را pin و قبل از شروع fetch می‌کنیم. خروجی هر worker باید شامل commit SHA، diff stat، command، exit code، contract output، targeted tests، runtime namespace و ریسک باقی‌مانده باشد. این Evidence Pack فقط «تست سبز» نیست.

برای اجرای تکرارپذیر، commandها را در خود worktree اجرا و artifact را بیرون از source نگه می‌داریم. این نمونه فقط به repository محلی و provider ساختگی وابسته است؛ `NOTIFY_PROVIDER=fake` هیچ credential یا شبکه‌ای نمی‌خواهد:

mkdir -p ../artifacts/run-01/{email,sms,inapp}
for wt in ../wt-email ../wt-sms ../wt-inapp; do
  name=$(basename "$wt")
  git -C "$wt" diff --check
  git -C "$wt" status --short > "../artifacts/run-01/$name/status.txt"
  NOTIFY_PROVIDER=fake pnpm --dir "$wt" exec vitest run \
    packages/contracts/src/notification.contract.test.ts \
    --reporter=verbose > "../artifacts/run-01/$name/contract.txt"
  git -C "$wt" rev-parse HEAD > "../artifacts/run-01/$name/commit.txt"
  git -C "$wt" diff --stat > "../artifacts/run-01/$name/diff-stat.txt"
done

در shell واقعی شکست command باید با `set -euo pipefail` یا exit code صریح ثبت شود. مسیر test adapter با ساختار واقعی repository تطبیق داده می‌شود؛ این snippet ادعای وجود path خاصی نمی‌کند. اگر runner یا option متفاوت است، command اصلاح‌شده و نسخهٔ آن در Evidence Pack ثبت می‌شود. هیچ worker مجاز نیست `.env` واقعی، endpoint بیرونی یا secret را بخواند.

Fan-in و merge order

اول contract baseline و تست merge می‌شود؛ سپس adapterهایی که فایل مشترک ندارند، یکی‌یکی بر اساس evidence و ریسک merge می‌شوند. ترتیب پیشنهادی: email، SMS، in-app، و در پایان wiring و integration owner. پس از هر merge، contract و targeted suite اجرا می‌شود. اگر wiring قبل از adapterها merge شود، ممکن است branch سبز به import موجودنبودن برسد؛ بنابراین wiring آخر است.

Conflict فقط با تحلیل owner حل می‌شود. اگر دو branch contract را تغییر داده باشند، merge را متوقف، proposal v2 و consumer matrix بساز؛ conflict marker را حذف نکن تا سبز شود. اگر دو branch generated file را تغییر داده‌اند، producer را یک‌بار روی baseline نهایی اجرا و artifact را بازتولید کن. هیچ secret، database dump یا فایل `.env` وارد merge نمی‌شود.

فن‌آوری fan-in را مرحله‌ای نگه می‌داریم. integration owner ابتدا contract baseline را روی شاخهٔ integration می‌سنجد، سپس هر adapter را جداگانه merge می‌کند و همان لحظه contract test و targeted test را اجرا می‌کند. پس از adapter سوم، wiring merge می‌شود؛ این نقطه نخستین جایی است که importها و dependency injection واقعی دیده می‌شوند. بعد از آن workerها branch قبلی را بازنویسی نمی‌کنند؛ اصلاح جدید یک commit صاحب‌دار در integration است.

نمونهٔ آموزشی merge مرحله‌ای:

git switch -c integration/notifications origin/main
git merge --no-ff agent/notify-email
NOTIFY_PROVIDER=fake pnpm exec vitest run packages/contracts/src/notification.contract.test.ts
git merge --no-ff agent/notify-sms
NOTIFY_PROVIDER=fake pnpm exec vitest run packages/contracts/src/notification.contract.test.ts
git merge --no-ff agent/notify-inapp
NOTIFY_PROVIDER=fake pnpm exec vitest run packages/contracts/src/notification.contract.test.ts
git merge --no-ff agent/notify-wiring

در صورت conflict، command بعدی اجرا نمی‌شود. وضعیت، فایل‌های conflicted و owner تصمیم در artifact ثبت می‌شوند؛ `git merge --abort` فقط در شاخهٔ آزمایشی و پس از ثبت وضعیت استفاده می‌شود، نه برای حذف شواهد worker. بعد از wiring، post-merge gate باید با fixture تازه و fake provider اجرا شود:

NOTIFY_PROVIDER=fake pnpm exec vitest run \
  packages/contracts/src/notification.contract.test.ts \
  apps/api/src/notifications/notification.integration.test.ts
pnpm exec eslint apps/api/src/notifications apps/web/src/notifications
git diff --check
! rg -n 'BEGIN (RSA|OPENSSH) PRIVATE KEY|https?://[^ ]+/(send|deploy)' \
  apps/api/src apps/web/src
pnpm audit --audit-level=high --offline

این static/security gate اثبات امنیت کامل نیست: فقط diff، الگوهای credential و endpoint، lint و آسیب‌پذیری‌های شناخته‌شدهٔ موجود در cache محلی را می‌سنجد. نبود cache برای audit باید `BLOCKED` ثبت شود، نه اینکه با `--force` یا حذف audit دور زده شود. در پایان، تست integration باید tenant A/B، retry با key یکسان و fake provider شکست‌خورده را هم بسنجد. اگر هر branch سبز ولی این gate قرمز باشد، fan-in شکست‌خورده است.

## سناریوی booking-platform

پس از reschedule موفق، orchestration لایهٔ domain یک پیام منطقی می‌سازد و adapterها آن را مصرف می‌کنند. تست باید ثابت کند tenant B پیام tenant A را نمی‌بیند، retry با همان idempotency key duplicate delivery نمی‌سازد، و reschedule transaction در صورت خطای fake provider rollback اشتباه ندارد. این کارگاه provider واقعی، queue مشترک یا cloud credential لازم ندارد.

## آزمایشگاه گام‌به‌گام

**ورودی:** contract v1، سه task مستقل، base commit، diagram، و rubric evidence. **فرایند:** ۱) baseline را verify کن. ۲) سه worktree بساز. ۳) برای هرکدام namespace ثبت کن. ۴) contract test را پیش از implementation اجرا کن. ۵) adapter و test را بساز. ۶) Evidence Pack سه‌گانه تولید کن. ۷) merge order را اجرا کن. ۸) wiring را اضافه کن. ۹) full verification را در worktree integration با fixture تازه اجرا کن.

**خروجی مورد انتظار:** سه commit مستقل، سه contract/adapter evidence، merge log مرتب، یک integration diff، full test output، `git diff --check`، و گزارش tenant/idempotency/privacy. **Recovery:** worker fail شد، branch را merge نکن و آن را `BLOCKED` با log نگه دار. conflict contract یعنی `DECIDE` و version proposal. full suite بعد از merge شکست خورد، آخرین merge را revert محلی و علت cross-branch را isolate کن؛ branch worker را دست‌کاری نکن. resource نشت‌کرده را با run-id پیدا و revoke کن، نه با حذف کور.

## bad/good

بد: سه agent هم‌زمان `notifications/index.ts` را تغییر می‌دهند، provider واقعی را با token جعلی‌نما صدا می‌زنند، تست را skip می‌کنند و چون هر branch سبز است merge را موازی می‌کنند.

خوب: contract immutable، ownership disjoint، fake provider، evidence مستقل، merge ترتیبی و full integration تازه دارد. smoke test بعد از wiring هم مسیر `reschedule → message → adapter` را می‌سنجد.

## خطاهای رایج

  • شروع fan-out پیش از تثبیت contract.
  • shared port/database/cache و flaky test.
  • merge هم‌زمان بدون integration owner.
  • اطمینان به branch-level green.
  • اجرای full suite با fixture یا secret واقعی.
  • بازتولید نکردن generated artifact پس از merge.

## Verification

  • [ ] نمودار orchestration با alt/caption حاضر است.
  • [ ] سه worktree، branch، index و runtime namespace جدا هستند.
  • [ ] contract test قبل و بعد از workerها سبز است.
  • [ ] Evidence Pack command، SHA، output و limitation دارد.
  • [ ] merge order و owner ثبت شده است.
  • [ ] conflict rule و recovery آزمایش شده است.
  • [ ] full integration، tenant isolation، idempotency، diff و static checks

پس از fan-in اجرا شده‌اند.

## تمرین و Rubric

سه adapter را روی کاغذ و سپس در sandbox به fan-out/fan-in ببرید. پاسخ کامل سه worktree و namespace جدا، contract test، owner wiring، evidence pack، merge order، recovery conflict، full integration و fake credential دارد. قبولی branchها بدون full verification امتیاز کامل ندارد.

## جمع‌بندی

Fan-out فقط پس از contract و ownership امن است. Fan-in یک مرحلهٔ مهندسی مستقل با merge order، conflict policy و evidence است. معیار موفقیت، integration کامل و قابل‌تکرار روی fixture تازه است، نه تعداد worker یا سبز بودن سه branch.

## منابع مستقیم

  • [Git worktree documentation](https://git-scm.com/docs/git-worktree.html) — lifecycle worktree؛ runtime namespace باید جدا طراحی شود.
  • [Anthropic — Building effective agents](https://www.anthropic.com/engineering/building-effective-agents) — delegation و orchestration؛ تضمین merge نیست.
  • [Martin Fowler — Continuous Integration](https://martinfowler.com/articles/continuousIntegration.html) — integration و feedback؛ ترتیب این workshop قراردادی است.
  • [NIST SSDF](https://csrc.nist.gov/pubs/sp/800/218/final) — evidence و traceability توسعهٔ امن.

به زبان ساده

Fan-out قرارداد و ownership را به workerها توزیع می‌کند و Fan-in آن‌ها را با ترتیب merge و full integration جمع می‌کند. سبز بودن هر branch جدا، سازگاری wiring و رفتار مشترک را ثابت نمی‌کند.

مثال واقعی

در کارگاه فرضی دوره، email، SMS و in-app هرکدام worktree و fake provider خود را دارند؛ پس از merge ترتیبی، tenant isolation، idempotency و integration تازه دوباره آزموده می‌شوند.

دانش‌سنجی

آزمون درس

۵ Q
01
ترتیب امن merge کدام است؟
02
contract test چه چیزی را می‌سنجد؟
03
در conflict contract چه کار نادرست است؟
04
چرا fake provider استفاده می‌شود؟
05
پس از fan-in چه چیزی لازم است؟

سناریوی تشریحی

کارگاه سه adapter را با fan-out/fan-in طراحی و verification کن.