Epic «زمان قرار را جابهجا کن، reminder بفرست و calendar را بهروز نگه دار» قابل review یا تخمین دقیق نیست. اگر آن را به ticketهای UI، API و DB تبدیل کنیم، اولین evidence واقعی دیر میرسد. در این کارگاه، همان درخواست را به یک Walking Skeleton و شش Slice مستقل میبریم؛ هر کدام behavior، فایلهای نمونه، test، dependency و rollback دارد.
## اهداف یادگیری
پس از این کارگاه میتوانی یک Epic آمیخته را Scope کنی، Story Map مختصر بسازی، S0 تا S6 را بنویسی، هر Slice را با behavior/files/tests/rollback قابل review کنی، decomposition افقی را رد کنی و تصمیمهای باز را بدون اختراع policy ثبت کنی.
## جای درس در چرخهٔ سرتاسری و authority
در booking-platform، بیمار میخواهد وقتِ confirmed خود را در همان tenant و برای همان doctor تغییر دهد. فصل ۴ Spec v1.0، ADR-021 و P1 تا P6 را authority میداند. این قوانین را تغییر نمیدهیم:
- `tenantId` و `actorId` تنها از credential استخراج میشوند.
- وقت در timezone کلینیک validate و در UTC ذخیره میشود؛ adjacent interval مجاز است.
- deadline دقیق ۲۴ ساعت بسته است؛ status پذیرفته برای آن ۴۰۹ است و mutation/audit تازه رخ نمیدهد.
- یک conflict همزمان برای slot واحد، دقیقاً یک success و یک `409 SLOT_CONFLICT` میدهد.
- Idempotency-Key دقیقاً در scope `tenantId + actorId + operation` نگهداری میشود؛ `appointmentId`، `startLocal` و `endLocal` در fingerprint هستند. key/fingerprint برابر، status/body durable نخست را replay و audit یکتا نگه میدارد.
- `RESCHEDULE_V1_ENABLED=false` route جدید را ۴۰۴ میکند؛ route قدیمی سازگار میماند.
Scope شامل owner patient و appointment confirmed است. refund، cancellation، doctor change، recurring appointment، ارسال reminder، provider خارجی و historical backfill non-goal هستند. اگر Product reminder را برای release لازم بداند، Spec جدید لازم است.
## گام ۱: Story Map رفتار
Backbone بیمار را به فعالیت مینویسیم، نه نام component:
یافتن appointment → دیدن زمان کنونی → پیشنهاد زمان تازه
→ تأیید تغییر → دیدن نتیجه → بررسی تاریخچهزیر «تأیید تغییر»، release line نخست فقط این outcome را دارد: بیمار مجاز بازهٔ آزاد معتبر را میفرستد و زمان UTC/نسخهٔ جدید را میبیند. deadline بسته، input نامعتبر، access tenant دیگر، conflict concurrent و retry behaviorهای جدا هستند. «طراحی calendar»، «ساخت repository» و «نوشتن همهٔ DTOها» story نیستند؛ کارهای فنیاند که در Slice مصرفکننده مینشینند.
اولویت فقط ارزش نمایشی نیست. نقشهٔ ثابت کارگاه چنین است: S0 contract+flag+route؛ S1 success+auth؛ S2 eligibility+concurrency؛ S3 idempotency+replay؛ S4 privacy+isolation؛ S5 observability+CI+performance؛ S6 rollout+rollback. S0 wiring را زود ثابت میکند؛ S2 protected write را پیش از exposure گسترده با barrier اثبات میکند؛ S3 key/retry را کنترل میکند؛ S5 evidence عملکرد و coexistence را ثبت میکند. constraint نامطمئن، S2 را بلافاصله پس از S1 اولویت میدهد.
## گام ۲: S0 — contract، flag و route
Order: 0. Dependency: Spec v1.0، ADR-021، P1 baseline.
Behavior: route جدید contract پذیرفته را expose میکند، با flag false پاسخ 404 میدهد،
route قدیمی سازگار میماند و body parse یا mutation نمیشود.
Files: packages/contracts/.../reschedule.ts؛ apps/api/.../feature-flags.ts و reschedule.route.ts؛
contract test.
Tests: evidence P2 برای flag-default 404، route قدیمی و no mutation؛ A-09 به S1 تعلق دارد.
Rollback: flag false و config reload؛ schema یا audit حذف نمیشود.S0 فقط contract، exposure و route را مالک است. S1 نخستین behavior واقعی UI→API→database→CI را میسازد. form mock، Skeleton نیست. اگر path به environment غیر ephemeral رسید، mutation را متوقف و URL/config را بررسی کنید.
## گام ۳: Sliceهای بعدی، با review مستقل
S1 — success و authorization
Order: 1. Depends on: S0 contract/route.
Behavior: patient مالکِ appointment confirmed، interval آزاد را از UI واقعی ارسال میکند؛
200 { id, startAt, endAt, version } و یک audit RESCHEDULED میبیند.
Files: reschedule-form/E2E؛ route/service auth context؛ repository write/audit؛ safe integration runner.
Tests: A-01 authorization ادامهٔ درخواست را ثابت میکند؛ A-09 id ثابت، startAt/endAt UTC،
version 41→42 و یک audit را میسنجد.
Rollback: flag false یا deploy compatible؛ schema جدید پاک نمیشود.S1 نخستین Walking Skeleton است: UI authenticated، HTTP واقعی و database persistent را برای یک outcome باریک به هم وصل میکند و از runner موجود proof میگیرد. S5 مالک CI matrix، artifact و performance است. اگر authentication از body خوانده شود یا response از `{ id, startAt, endAt, version }` دور شود، contract فصل ۴ شکسته است.
S2 — eligibility و concurrency
Order: 2. Depends on: S1 success/auth; priority high because protected-write risk.
Behavior: invalid local time/interval، پنجرهٔ بسته یا appointment cancelled بدون mutation پاسخ پذیرفته میگیرند؛
دو request مجاز و همزمان برای interval واحد، دقیقاً یک 200 و یک 409 SLOT_CONFLICT میگیرند.
Files: validator/service error map؛ protected-write migration/EXCLUDE constraint؛ transaction store؛
conflict map؛ fixture barrier و CI integration job.
Tests: A-04/A-05/A-06/A-07 eligibility، از جمله 409 RESCHEDULE_WINDOW_CLOSED؛
A-08 barrier 100 iteration؛ Oracle: no failure mutation/audit و one confirmed interval/one audit.
Rollback: flag false؛ schema/audit حفظ و repair فقط forward migration/compatible code.S2 «error handling» مبهم نیست. UI میداند کدام ورودی قابل اصلاح است و پنجرهٔ بسته چیست؛ API code پذیرفته میدهد؛ test database را پیش و پس از request میسنجد. sleep جای barrier نیست و E2E ترتیبی proof race نیست.
S3 — idempotency و replay
Order: 3. Depends on: S1 success/auth and S2 transaction boundary.
Behavior: key نامعتبر 400 INVALID_IDEMPOTENCY_KEY و claim همزمان 409 REQUEST_IN_PROGRESS؛
retry key/fingerprint برابر status/body را replay و audit یکتا نگه میدارد؛
fingerprint متفاوت 409 IDEMPOTENCY_KEY_REUSED میگیرد.
Files: idempotency migration/table/store؛ fingerprint؛ service claim/replay؛ telemetry و integration fixture.
Tests: key invalid، claim همزمان، A-10 replay و A-11 fingerprint متفاوت؛ Oracle: 400/409 canonical،
one mutation/audit، response durable و no mutation for reused key.
Rollback: flag false؛ recordهای idempotency retained طبق policy؛ raw key log نمیشود.scope کلید `tenantId + actorId + operation` است؛ `appointmentId`، `startLocal` و `endLocal` فقط fingerprint هستند. S3، نه S0/S2، migration/table، `INVALID_IDEMPOTENCY_KEY`، `REQUEST_IN_PROGRESS` و replay را مالک است؛ این ownership ticket «DB جدا» و review مبهم را میبندد.
S4 — privacy و isolation
Order: 4. Depends on: S1 authentication context and error serialization.
Behavior: actor دیگر در همان tenant 403 APPOINTMENT_NOT_OWNED میبیند؛ actor cross-tenant
404 APPOINTMENT_NOT_FOUND میبیند؛ هیچکدام name/time/key/PII را افشا نمیکنند.
Files: auth context adapter؛ service authorization/isolation؛ route error serialization؛
privacy log guard و integration fixtures.
Tests: A-02 same-tenant 403، A-03 cross-tenant 404 و A-12 log scan؛ Oracle: no mutation,
safe error body and telemetry without PII/raw key.
Rollback: flag false؛ policy access با flag bypass نمیشود.admin capability آینده Scope تازه است. ۴۰۳ برای non-owner همان tenant صحیح است، اما برای cross-tenant privacy را میشکند.
S5 — observability، CI و performance
Order: 5. Depends on: S0–S4 stable response semantics.
Behavior: CI contract/integration/E2E evidence، privacy-safe telemetry و performance release-matched
را ثبت میکند؛ route قدیمی compatibility و consumerها آزموده میشوند.
Files: compatibility matrix and consumer contract tests؛ latency fixture/bench؛ dashboard and CI artifact config.
Tests: NFR-01 benchmark 100 samples, concurrency 1, version 41→42, p95 ≤400ms in staging؛
consumer tests and CI matrix for old/new code plus additive schema.
Rollback: flag false؛ metric/fixture evidence نگهداری؛ schema drop یا rewrite داده انجام نمیشود.اعداد benchmark در این Slice معیار نمونهٔ فصل ۴ هستند، نه ادعای اینکه laptop developer یا test mock performance release است. اگر fixture version mismatch داشت، benchmark exit 1 و دادهٔ کنترلشده reset میشود؛ آستانه را برای pass شدن تغییر نمیدهیم. matrix بدون consumer test، evidence compatibility نیست.
S6 — readiness و retirement تصمیمشده
Order: 6. Depends on: evidence S0–S5 and consumer inventory.
Behavior: rollout cohort فقط پس از contract/race/replay evidence فعال است؛ flag یا adapter قدیمی تنها با proof consumer retirement حذف میشود.
Files: rollout runbook; flag metadata; compatibility scan; PR/CI evidence links; forward migration when needed.
Tests: flag default/on matrix، consumer contract suite، scope/privacy scan و git diff --check.
Rollback: disable cohort/flag; preserve schema and historical audit; no destructive reversal after writes.S6 شامل feature rollout است، نه «کارهای باقیمانده». اگر evidence مفقود است، status `BLOCKED` و integration ادامه نمییابد. Contract مرحلهٔ expand/migrate/contract تنها بعد از inventory consumer و گذشت window سازگاری انجام میشود.
## گام ۴: decomposition افقی را رد کن
پیش از واردکردن کار فنی به backlog، آن را با actor، outcome، test و rollback بسنج. اگر یک item فقط نام layer دارد، آن را به Slice مالک behavior برگردان.
مثال ضعیف: decomposition افقی ردشده
این پیشنهاد را در review رد کنید:
Sprint A: UI calendar، formها و همهٔ messageها
Sprint B: تمام API routeها، DTOها، error middleware و telemetry
Sprint C: تمام migrationها، indexها، tableها و repositoryها
Sprint D: integration، E2E، feature flag و rolloutمشکل نام فناوری نیست؛ فقدان outcome است. هیچ Sprint نمیتواند بگوید بیمار چه کاری انجام داد، contract چه بود، test چه Oracleی داشت یا rollback چه چیزی را حفظ میکند. Sprint B ممکن است tenant privacy را اشتباه کند و Sprint C migration ناسازگار بسازد؛ هر دو تا D پنهان میمانند. جایگزین، S0 تا S6 است که در هر یک UI/API/DB فقط به اندازهٔ behavior میآید.
مثال اصلاحشده: S0 تا S6
S0 تا S6 در بخشهای پیشین نمونهٔ اصلاحشدهاند: هر کدام outcome، فایل، test/Oracle، dependency و rollback مستقل دارند و UI/API/DB را فقط برای همان behavior میآورند.
## گام ۵: گیت review هر Slice
پیش از merge، reviewer این پرسشها را با artifact پاسخ میدهد:
1. behavior با actor، trigger و outcome چیست و کدام non-goal را حذف کردهایم؟ 2. files تغییرکرده چرا برای همین behavior لازماند، نه یک refactor انباشته؟ 3. contract status/body/compatibility با Spec v1.0 همخوان است؟ 4. test Oracle شامل mutation، audit، privacy یا concurrency مورد نیاز هست؟ 5. flag default، cohort و expiry/owner چیست و false چه میکند؟ 6. rollback code، configuration، schema و data را واقعبینانه توصیف میکند؟ 7. dependency و evidence پیشنیاز واقعاً pass شدهاند یا فقط انتظار میروند؟
policy باز را `DECIDE` یا `BLOCKED` و ownerدار ثبت کنید؛ اما S2 بدون A-08 یا S3 بدون replay durable evidence، Slice کامل نیست.
## گام ۶: آزمایشگاه، rollback و گیت پذیرش
**ورودی:** Epic اولیه، Story Map، Spec v1.0، ADR-021، ExecPlan P1–P6 و diagram `vertical-slices.svg`. **گامها:** ۱) Scope/non-goal را جدا بنویس. ۲) Backbone و release line S0 را رسم کن. ۳) S0 تا S6 را در قالب بالا پر کن. ۴) ترتیب هر Slice را با risk/dependency توجیه کن. ۵) برای S2 barrier و برای S3 replay Oracle بنویس. ۶) decomposition افقی را خط بزن و علت آن را ثبت کن. ۷) compatibility matrix و flag runbook S6 را اضافه کن.
این فرمانها فقط artifact آموزشی برای booking-platform فرضیاند؛ فقط با database محلیِ ephemeral که نام آن `booking_platform_test` است اجرا کنید، نه staging یا production:
case "${TEST_DATABASE_URL:-}" in *booking_platform_test*) ;; *) echo "use only booking_platform_test"; exit 1;; esac
RESCHEDULE_V1_ENABLED=true pnpm exec vitest run apps/api/src/modules/appointments/reschedule.int.test.ts
RESCHEDULE_V1_ENABLED=true pnpm exec vitest run apps/api/src/modules/appointments/reschedule.int.test.ts --testNamePattern="A-08" --repeat=100**خروجی مورد انتظار:** هفت item reviewable وجود دارد: S0 contract+flag+route، S1 success+auth، S2 eligibility+concurrency، S3 idempotency+replay، S4 privacy+isolation، S5 observability+CI+performance و S6 rollout+rollback؛ هر کدام behavior، files، tests، dependency و rollback دارد. S1 UI→API→DB→CI واقعی است و race/replay dependency روشن دارد. **Recovery:** اگر S1 بیش از یک outcome دارد، happy path را باریک کنید. protected-write migration مالک S2 و idempotency migration مالک S3 است، نه Sprint DB. Artifactهای conflict را به owner Spec/ADR بازگردانید.
## خطاهای رایج و نشانهها
- **ticket بیرفتار:** «repository را بساز» actor یا Oracle ندارد.
- **فایل بهجای قرارداد:** file list بزرگ است اما status/error و compatibility نامعلوماند.
- **S1 نمایشی:** mock، DB in-memory یا CI lint-only دارد.
- **race با sleep:** test گاهی سبز است اما transaction overlap ثابت نشده.
- **rollback پاککننده:** audit/schema نوشتهشده را با revert حذف میکند.
- **Scope creep:** reminder/provider را زیر telemetry یا UI پنهان میکند.
## چکلیست Verification
- [ ] S0 تا S6 به actor/outcome و authority فصل ۴ trace میشوند.
- [ ] هر Slice behavior، files، tests، dependency و rollback واقعبینانه دارد.
- [ ] route flag-default 404 و authorization/privacy cross-tenant ثابتاند.
- [ ] S2 barrier 100 iteration و یک ۲۰۰/یک ۴۰۹ را اثبات میکند.
- [ ] S3 replay، scope `tenantId + actorId + operation`، fingerprint و audit یکتا را میسنجد.
- [ ] migration additive، compatibility و retirement فقط با evidence consumer پیش میروند.
## تمرین مستقل
Epic «بیمار appointment را cancel میکند و fee/refund میگیرد» را به چهار Slice پیشنهاد کن، بدون اینکه policy fee را خودت تعیین کنی.
**Rubric پاسخ:** پاسخ خوب cancellation behavior را از refund policy جدا میکند؛ owner و `DECIDE` برای fee دارد؛ هر Slice outcome/files/tests/rollback دارد؛ mutation/audit و tenant را میسنجد؛ و refund provider یا migration destructive را در Slice اول نمیگنجاند.
## جمعبندی
کارگاه نشان داد Slice خوب یک ticket کوچک نیست: قرارداد رفتار، پروندههای لازم، Oracle test، dependency و rollback دارد. S0 contract/flag/route را میبندد؛ S1 اتصال واقعی را اثبات میکند؛ S2 تا S5 ریسکهای domain، privacy، race، replay و compatibility را جدا میکنند؛ S6 تنها با evidence rollout یا retirement را پیش میبرد. تقسیم افقی همین قابلیت review را از بین میبرد.
## منابع مستقیم
- [Martin Fowler — Continuous Integration](https://martinfowler.com/articles/continuousIntegration.html) — راهنمای جامعهمحور برای integration کوچک و feedback خودکار؛ نسخهٔ pipeline اجباری نیست.
- [Martin Fowler — Branching Patterns](https://martinfowler.com/articles/branching-patterns.html) — تحلیل trade-off branchهای کوتاه و بلند؛ policy repository را جایگزین نمیکند.
- [Martin Fowler — Feature Toggles](https://martinfowler.com/articles/feature-toggles.html) — دستهبندی و بدهی toggleها؛ authorization یا correctness را تضمین نمیکند.
- [GitHub Docs — GitHub flow](https://docs.github.com/en/get-started/using-github/github-flow) — مستند رسمی merge/PR flow؛ جای migration plan و contract test نیست.
به زبان ساده
کارگاه Slice خوب نشان میدهد هر بخش چه Outcome، فایل، تست، dependency و rollbackی دارد. این قالب جلوی آن را میگیرد که reminder، refund و calendar sync بیصدا زیر عنوان reschedule وارد شوند.
مثال واقعی
در artifact فرضی کارگاه، S0 قرارداد و flag، S1 مسیر موفق، S2 race، S3 replay، S4 privacy، S5 شواهد عملیاتی و S6 rollout/rollback را جدا میکند.