بعد از Spec و ExecPlan ممکن است تیم بگوید «اول UI را تمام میکنیم، بعد API و در آخر database را وصل میکنیم». این برنامه ظاهراً منظم است، اما تا انتهای کار معلوم نمیکند login، قرارداد HTTP، migration، محیط CI و نمایش خطا با هم کار میکنند یا نه. برش عمودی، یک رفتار کوچک اما قابل مشاهده را از کاربر تا داده و بازخورد خودکار تحویل میدهد؛ بنابراین ریسک اتصال را زود آشکار میکند.
## اهداف یادگیری
پس از این درس میتوانی یک Story Map برای reschedule بسازی، Walking Skeleton را از «نمونهٔ نمایشی» جدا کنی، اولین مسیر UI → API → database → CI را تعریف کنی، و برای هر لایه evidence و rollback قابلبررسی بنویسی.
## جای درس در چرخهٔ سرتاسری
فصل ۴ رفتار `reschedule v1.0`، قرارداد خطاها، `RESCHEDULE_V1_ENABLED=false`، ADR-021 و P1 تا P6 را تثبیت کرد. در مرحلهٔ Slice، آن قراردادها را کوچکتر و قابل ادغام میکنیم؛ حق تغییر deadline، timezone، key scope یا خروجی race را نداریم. درس ۱۷ معیارهای شکستن کار را گسترش میدهد و درس ۱۸ سازگاری و ادغام تدریجی را عملی میکند.
## Story Map: نقشهٔ رفتار، نه فهرست component
Story Map فعالیت کاربر را افقی و انتخابهای تحویل را زیر آن میچیند. برای بیمار، ستونها میتوانند «یافتن وقت»، «دیدن زمان فعلی»، «انتخاب زمان تازه»، «تأیید جابهجایی» و «دیدن نتیجه» باشند. زیر ستون تأیید، داستانها بر اساس ارزش و ریسک قرار میگیرند: نخست مالکِ appointment confirmed با interval معتبر؛ سپس deadline بسته؛ بعد conflict همزمان و replay idempotent. این نقشه میگوید چه رفتار قابل عرضهای داریم، نه اینکه فایل React یا repository را چه زمانی بنویسیم.
*نمودار نشان میدهد هر ستون باریک عمودی یک رفتار قابل مشاهده را از UI تا DB میبرد، اما تکمیل یک لایهٔ افقی اتصال را به تأخیر میاندازد.*
Backbone نقشه به زبان کاربر نوشته میشود. «فرم reschedule» نام component است؛ «بیمار زمان تازه را ارسال میکند و نتیجهٔ قطعی میبیند» یک رفتار است. اگر آیتمی را نتوان با actor، trigger و outcome توضیح داد، احتمالاً کار فنیِ پنهان، research یا component است و باید به داستانی که آن را لازم دارد متصل شود. کار فنی حذف نمیشود؛ در Slice همان رفتار قرار میگیرد و معیار پایان مشترک دارد.
برای این فصل، release اول به معنی «همهٔ حالتها» نیست. از Scope پذیرفتهشده فقط یک مسیر بسیار باریک را فعال میکند: بیمار مالک، appointment confirmed، بازهٔ معتبر در timezone کلینیک، زمان آزاد، کلید معتبر و پاسخ ۲۰۰. endpoint قدیمی سازگار میماند و مسیر جدید `PATCH /v1/appointments/{appointmentId}/reschedule` فقط با flag در محیط staging باز میشود. این release کوچک، fake completeness نیست، چون صادقانه میگوید conflict، deadline، DST و retry هنوز Sliceهای بعدیاند.
## Walking Skeleton چیست؟
Walking Skeleton کوچکترین مسیر سرتاسری است که در محیط نزدیک به واقعیت راه میرود: کاربر در UI عملی انجام میدهد؛ درخواست authenticated به API میرود؛ API قرارداد را validate و data را persistent میکند؛ پاسخ به UI برمیگردد؛ و CI همان مسیر را اجرا میکند. وجود button، mock API، fixture در حافظه یا pipeline که فقط lint میکند Skeleton نیست. اینها اجزای مفیدند، اما ریسک اتصال واقعی را اثبات نمیکنند.
برای reschedule، Skeleton قابل مشاهده چنین است:
1. Playwright با بیمار `patient-latency-a` وارد صفحهٔ appointment خودش میشود و بازهٔ `2026-06-20T11:00` تا `11:30` را وارد میکند. 2. UI یک `Idempotency-Key` تازه را فقط بهعنوان ورودی request میسازد و `PATCH /v1/appointments/appt-latency-001/reschedule` را میفرستد. 3. middleware از credential، `tenantId` و `actorId` را میگیرد؛ body حق تعیین آنها را ندارد. 4. route فقط وقتی `RESCHEDULE_V1_ENABLED=true` است به service میرسد؛ در حالت پیشفرض ۴۰۴ میدهد. 5. service local time را با timezone کلینیک به UTC تبدیل میکند، transaction سازگار را اجرا میکند و appointment و audit را durable میسازد؛ در S1 جدول idempotency یا replay پاسخ ساخته نمیشود. 6. UI زمان UTC برگشتی را در نمایش محلی نشان میدهد؛ test E2E و integration در CI خروجی ۲۰۰، `id`، `startAt`، `endAt`، `version` و یک audit را بررسی میکنند.
کلید تازه در S1 فقط input قرارداد است؛ behavior و evidence این Slice به success/auth محدود میماند. S3 مالک جدول durable idempotency، fingerprint، replay همان status/body و outcomeهای `400 INVALID_IDEMPOTENCY_KEY` و `409 REQUEST_IN_PROGRESS` است؛ `IDEMPOTENCY_KEY_REUSED` نیز برای fingerprint متفاوت در همان S3 میماند. در اینجا «CI» یک جعبهٔ جادویی نیست. pipeline باید migration را فقط روی database ephemeral یا محیط staging مجاز اجرا کند، test integration و Playwright را با flag روشن اجرا کند، و artifact نتیجه را نگه دارد. command پیشنهادی برای مسیر API این است:
RESCHEDULE_V1_ENABLED=true pnpm exec vitest run apps/api/src/modules/appointments/reschedule.int.test.ts
RESCHEDULE_V1_ENABLED=true pnpm exec playwright test apps/web/tests/e2e/reschedule.spec.tsخروجی آموزشی مورد انتظار، pass نام test A-01/A-09 و response دارای `id`، `startAt`، `endAt` و `version` است؛ این متن ادعای اجرای واقعی در مخزن فرضی نیست. **Recovery:** اگر CI به database غیر ephemeral اشاره کرد، اجرا را متوقف کنید؛ URL و pipeline را اصلاح کنید. اگر E2E از mock عبور میکند، test را «Skeleton proof» نام ندهید و اتصال HTTP واقعی را اضافه کنید.
## مثال ضعیف و علت شکست
Sprint 1: همهٔ screenها و componentهای calendar
Sprint 2: تمام routeها و DTOها
Sprint 3: همهٔ migrationها
Sprint 4: اتصال و تست end-to-endاین decomposition افقی است. UI میتواند state نامعتبر را پنهان کند، API با response متفاوت ساخته شود و migration در CI lock بگیرد؛ اما هیچکدام تا Sprint 4 دیده نمیشود. همچنین «تمام» معیار رفتاری ندارد: آیا owner دیگر ۴۰۳ میگیرد؟ آیا flag خاموش است؟ آیا route قدیمی سازگار است؟ تیم کار زیاد تولید کرده ولی دانش deployable کمی به دست آورده است.
## مثال اصلاحشده: Slice نخست
S0 — contract، flag و route
Behavior: route جدید contract پذیرفته را دارد، در flag پیشفرض ۴۰۴ میدهد و route قدیمی سازگار میماند؛ mutation رخ نمیدهد.
Files: contracts/reschedule؛ feature-flags؛ route guard؛ contract test.
Tests: evidence P2 برای flag-default 404 و نبود parse/mutation؛ این evidence، A-09 نیست.
Evidence: route جدید هنگام false پنهان است؛ endpoint قدیمی سازگار میماند.
Rollback: RESCHEDULE_V1_ENABLED=false و reload config؛ schema یا audit حذف نمیشود.S0 عمداً مسیر موفق را جعل نمیکند: نقشهٔ ثابت S0 contract+flag+route؛ S1 success+auth؛ S2 eligibility+concurrency؛ S3 idempotency+replay؛ S4 privacy+isolation؛ S5 observability+CI+performance؛ و S6 rollout+rollback است. S1 نخستین Walking Skeleton را از UI تا API و database میبرد و از runner موجود برای proof استفاده میکند؛ S5 مالک matrix، artifact و performance در CI است. این مرز، contract و exposure را پیش از mutation ثابت میکند و به Sliceهای بعدی ownership روشن میدهد.
## ادامهٔ سناریوی booking-platform
پس از P1، تیم Story Map را کنار artifactهای فصل ۴ میگذارد. ستون «تأیید» با همین نقشهٔ ثابت تقسیم میشود. S0 flag و route guard را ثابت میکند؛ S2 سپس همان endpoint را با barrier دو transaction بررسی میکند و S5 matrix CI را ثبت میکند. E2E به تنهایی proof race نیست و ترتیب Sliceها نام P-taskها را عوض نمیکند.
## آزمایشگاه: Skeleton را روی نقشه بگذار
**ورودی:** Spec v1.0، ADR-021، ExecPlan P1–P6 و نمودار این درس. **گامها:** ۱) backbone بیمار را بنویس. ۲) برای هر ستون outcome قابل مشاهده تعریف کن. ۳) S0 را با contract، flag، route، test و rollback نام ببر. ۴) S1 را با UI، request، API، database، CI و rollback نام ببر. ۵) route flag-default و staging-enabled را جدا کن. ۶) یک test integration و یک E2E انتخاب کن و Oracle هر کدام را بنویس. ۷) dependencyهای S0→S1 را به P2/P3/P4 آشکار کن.
**خروجی مورد انتظار:** نقشه یک مسیر ۲۰۰ واقعی در S1 دارد، نه mock؛ S0 و S1 هر کدام behavior، file، test، dependency و rollback دارند؛ و non-goalهای refund، notification و doctor change وارد آن نشدهاند. **Recovery:** اگر Skeleton بیش از یک رفتار را لازم دارد، happy path را باریکتر کن؛ migration سازگار را در Slice مالک S2 یا S3 نگه دار، نه در یک epic DB جدا.
## خطاهای رایج و نشانههای تشخیص
- **راهرفتن با mock:** UI سبز است ولی network واقعی یا data persistent ندارد.
- **برش «happy path» بدون boundary:** flag، auth، error contract یا CI را حذف میکند و صرفاً demo میسازد.
- **نقشهٔ تکنولوژی:** ستونهای React، Node و PostgreSQL به جای فعالیت بیمار نوشته شدهاند.
- **کاملنمایی:** S0 را release کامل مینامد، در حالی که حالتهای باقیمانده معلوم نیستند.
- **rollback خیالی:** revert کد را برابر حذف migration یا audit پس از write جدید میداند.
## چکلیست Verification
- [ ] Story Map با actor و outcome کاربر شروع میشود.
- [ ] Skeleton یک UI، HTTP واقعی، database persistent و CI دارد.
- [ ] flag در حالت پیشفرض خاموش و در test/staging صریح روشن است.
- [ ] S0 به evidence P2 (flag-default 404) و compatibility route قدیمی trace میشود؛ S1 به A-01/A-09.
- [ ] CI فقط محیط دادهٔ امن را mutate میکند و artifact نتیجه دارد.
- [ ] rollback flag/code با حد schema-compatible فصل ۴ سازگار است.
## تمرین مستقل
برای «نمایش وضعیت reminder آینده» یک Story Map کوچک و Walking Skeleton طراحی کن؛ ارسال پیام یا provider خارجی را وارد Scope نکن.
**Rubric پاسخ:** پاسخ خوب actor و نتیجهٔ قابل مشاهده دارد؛ UI از API واقعی و database fixture میخواند؛ CI test مسیر را اجرا میکند؛ permission tenant را دارد؛ feature flag و rollback را نام میبرد؛ و مشاهده را با ارسال notification اشتباه نمیگیرد.
## جمعبندی
Story Map ارزش را به ترتیب رفتارهای کاربر نشان میدهد و Walking Skeleton سریعترین شاهد اتصال واقعی است. برش عمودی یعنی یک outcome باریک از UI تا CI، نه تکمیل یک لایه یا ادعای کاملبودن. با flag خاموش، migration سازگار و testهای مناسب، نخستین Slice یادگیری deployable میسازد و مسیر Sliceهای پرریسک بعدی را باز میکند.
## منابع مستقیم
- [Jeff Patton — User Story Mapping](https://jpattonassociates.com/story-mapping/) — روش جامعهمحور برای نقشهکردن فعالیت و انتشار؛ جای Spec رفتاری نیست.
- [Growing Object-Oriented Software, Guided by Tests](http://www.growing-object-oriented-software.com/) — منبع Walking Skeleton و بازخورد سرتاسری؛ معماری خاصی را الزام نمیکند.
- [Alistair Cockburn — Elephant Carpaccio](https://alistair.cockburn.us/elephant-carpaccio/) — تمرین برش عمودی کوچک؛ نه نسخهٔ ثابت برای اندازهٔ هر داستان.
- [Continuous Integration](https://martinfowler.com/articles/continuousIntegration.html) — ادغام کوچک و بازخورد خودکار بهعنوان روش جامعهمحور، نه تعریف pipeline واحد.
به زبان ساده
Story Map فعالیت و Outcome کاربر را نشان میدهد، در حالی که Walking Skeleton یک مسیر واقعی باریک از UI تا API، database و CI است. دکمهٔ متصل به mock، skeleton سرتاسری محسوب نمیشود.
مثال واقعی
در سناریوی فرضی دوره، S1 بیمار مالک را از فرم reschedule تا پاسخ UTC و version جدید میبرد و CI همان HTTP و persistence واقعی را روی fixture امن بررسی میکند.