ثبت تصمیمهای معماری (ADR)
هر تصمیم مهم معماری را در یک سند کوتاه بنویس. بنویس چرا لازم بود، چه انتخاب کردی و چه هزینهای دارد. سند را کنار کد نگه دار و هیچ وقت پاکش نکن. اگر تصمیم عوض شد، یک سند جدید بنویس.
نویسنده: bezzad
مشکل: «چرا این را انتخاب کردیم؟»
دو سال پیش، تیم فروشگاه اینترنتی ما برای رویدادهای سفارش، RabbitMQ را انتخاب کرد. امروز یک برنامهنویس جدید میپرسد: «چرا Kafka نه؟ همه از Kafka استفاده میکنند.»
هیچ کس جواب دقیق را نمیداند. دو نفری که آن تصمیم را گرفتند، از شرکت رفتهاند.
حالا تیم دو راه بد دارد:
- تقلید کورکورانه. «لابد دلیلی داشته، دستش نزنیم.» حتی اگر شرایط عوض شده باشد و تصمیم دیگر درست نباشد.
- تغییر بیدلیل. «این قدیمی است، عوضش کنیم.» شاید دلیلی مهم بوده که هنوز هم درست است. تیم چند ماه کار میکند و به همان مشکل قدیمی میرسد.
هر دو راه از یک کمبود میآیند: دلیل تصمیم جایی نوشته نشده است.
ایده: یک سند کوتاه برای هر تصمیم
سند ثبت تصمیم معماری (Architecture Decision Record یا ADR) یک فایل متنی کوتاه است. برای هر تصمیم مهم یک فایل مینویسیم. این ایده را مایکل نایگارد (Michael Nygard) در سال ۲۰۱۱ مطرح کرد و حالا در خیلی از تیمها رایج است.
ویژگیهای یک ADR خوب:
- کوتاه است. یکی دو صفحه. اگر نوشتنش یک روز طول بکشد، هیچ کس آن را نمینویسد.
- کنار کد است. در همان مخزن گیت، نه در یک ویکی که کسی پیدایش نمیکند.
- یک تصمیم دارد. هر فایل فقط یک تصمیم.
- شماره دارد. شمارهها پشت سر هم هستند و هیچ وقت دوباره استفاده نمیشوند.
- تغییر نمیکند. اگر تصمیم عوض شد، یک ADR جدید مینویسیم.
یک ADR چه بخشهایی دارد؟
قالب اصلی نایگارد پنج بخش دارد. قالبهای دیگری هم هست، ولی تقریباً همه همین ایده را دارند.
- عنوان. خود تصمیم، در یک جمله کوتاه. «استفاده از RabbitMQ برای رویدادهای سفارش».
- وضعیت. پیشنهاد، پذیرفته، ردشده یا جایگزینشده.
- زمینه (Context). چه مشکلی داریم؟ چه محدودیتهایی؟ چه گزینههایی را بررسی کردیم؟ این مهمترین بخش است. بدون آن، خواننده نمیفهمد آیا تصمیم هنوز درست است.
- تصمیم (Decision). چه انتخاب کردیم. با جملههای روشن و فعال: «ما … استفاده میکنیم».
- پیامدها (Consequences). چه چیزی آسانتر و چه چیزی سختتر میشود. هر تصمیم هزینهای دارد. اگر هیچ پیامد بدی ننوشتی، احتمالاً خوب فکر نکردهای.
نمونه کامل در فروشگاه
این همان تصمیم دو سال پیش است، اگر آن روز نوشته شده بود. متن ADR را معمولاً به انگلیسی و با Markdown مینویسند:
# 4. Use RabbitMQ for order events
Date: 2024-03-12
Status: Accepted
Deciders: Sara (lead), Reza, Mina
## Context
- Order, Warehouse and Shipping services must react to order events.
- Peak load is about 200 orders per minute.
- The team has 4 developers. Two already run RabbitMQ in production.
- We do not need to replay old events. Each event is handled once.
- Options we looked at: RabbitMQ, Kafka, Azure Service Bus.
## Decision
We will use RabbitMQ (self-hosted) for all order events.
## Consequences
- Good: the team already knows how to run and monitor it.
- Good: simple queues and retries are enough for our load.
- Bad: we cannot replay old events. If we need event replay
or analytics on the event stream, we must revisit this decision.
- Bad: one more server for the ops team to patch and back up.
دو سال بعد، برنامهنویس جدید این فایل را میخواند و جوابش را میگیرد:
- دلیل را میفهمد. تیم کوچک بود، بار کم بود و بازپخش رویداد لازم نبود.
- میفهمد کی تصمیم باید عوض شود. خود سند گفته است: «اگر بازپخش رویداد لازم شد، دوباره بررسی کن.»
- دیگر حدس نمیزند. اگر امروز تیم بزرگتر شده و تحلیل رویدادها لازم است، شرایط عوض شده است. پس تغییر، دلیل روشن دارد.
چرخه عمر: ADR را پاک نکن
یک سال بعد، تیم تحلیل داده به رویدادهای قدیمی نیاز پیدا میکند. تیم تصمیم میگیرد به Kafka برود. ADR شماره ۴ را تغییر نمیدهیم. یک ADR جدید با شماره ۹ مینویسیم.
چرا متن قدیمی را عوض نمیکنیم؟
- تاریخچه میماند. کسی که کد قدیمی را میخواند، میفهمد چرا آن روز این شکل بود.
- اشتباه تکرار نمیشود. اگر تصمیم جدید هم شکست خورد، میشود دید که چه گزینههایی قبلاً امتحان شده است.
- اعتماد بیشتر است. وقتی میدانی سندها بعداً بیصدا تغییر نمیکنند، به آنها تکیه میکنی.
در ADR شماره ۴ فقط وضعیت را عوض میکنیم و یک لینک به ADR شماره ۹ اضافه میکنیم. ADR شماره ۹ هم در زمینهاش به شماره ۴ لینک میدهد.
کجا و چطور؟
یک پوشه در مخزن کافی است:
docs/
adr/
0001-record-architecture-decisions.md
0002-use-postgresql-for-orders.md
0003-modular-monolith-first.md
0004-use-rabbitmq-for-order-events.md
یک روند ساده که با کار روزانه تیم جور است:
- پیشنهاد در Pull Request. کسی که تصمیم را پیشنهاد میکند، فایل را با وضعیت «پیشنهاد» میسازد.
- بحث در همان Pull Request. تیم نظر میدهد، مثل Code Review. گزینههای دیگر به بخش زمینه اضافه میشوند.
- ادغام یعنی پذیرش. وقتی Pull Request ادغام شد، وضعیت «پذیرفته» است.
- کد و تصمیم با هم. اگر میشود، ADR را در همان Pull Request که کد را تغییر میدهد بیاور.
چه تصمیمی ADR لازم دارد؟
همه تصمیمها ADR لازم ندارند. اسم یک متغیر یا انتخاب بین دو کتابخانه کوچک، ADR نمیخواهد. یک سؤال ساده بپرس: اگر این تصمیم غلط باشد، عوض کردنش گران است؟
بنویس
- انتخاب دیتابیس، صف پیام یا فریمورک اصلی.
- سبک معماری، مثل Monolith ماژولار یا Microservices.
- روش احراز هویت بین سرویسها.
- قانونی که همه تیمها باید رعایت کنند، مثل «هیچ سرویسی دیتابیس سرویس دیگر را نمیخواند».
- تصمیمی که سر آن بحث زیادی شد.
لازم نیست
- اسم کلاسها و جای فایلها.
- انتخاب یک کتابخانه کوچک که راحت عوض میشود.
- چیزی که قانون کدنویسی یا Linter آن را مشخص میکند.
- کاری که تصمیم نیست، مثل گزارش جلسه.
اشتباههای رایج
| اشتباه | نتیجه | راه درست |
|---|---|---|
| نوشتن ADR بعد از چند ماه | دلیلهای واقعی فراموش شدهاند. سند فقط تصمیم را تکرار میکند. | همان روز تصمیم، یا قبل از آن. |
| زمینه خالی یا یک خطی | خواننده نمیفهمد تصمیم هنوز معتبر است یا نه. | محدودیتها، عددها و گزینهها را بنویس. |
| فقط پیامدهای خوب | به نظر تبلیغ میآید و اعتماد را از بین میبرد. | هزینهها و ریسکها را صادقانه بنویس. |
| ویرایش ADR قدیمی | تاریخچه و دلیلهای قبلی گم میشوند. | یک ADR جدید که جایگزین قبلی میشود. |
| سند بیست صفحهای | هیچ کس نمینویسد و هیچ کس نمیخواند. | یکی دو صفحه. جزئیات را لینک بده. |
| نگه داشتن در جای دور از کد | کسی پیدایش نمیکند و با کد هماهنگ نمیماند. | پوشهای در همان مخزن. |
خلاصه در پنج خط
- کد نشان میدهد «چه» ساختیم. ADR نشان میدهد «چرا».
- هر ADR کوتاه است و پنج بخش دارد: عنوان، وضعیت، زمینه، تصمیم و پیامدها.
- زمینه و پیامدهای بد مهمترین بخشها هستند.
- سند را کنار کد نگه دار و با Pull Request مرورش کن.
- سند قدیمی را پاک یا ویرایش نکن. یک ADR جدید بنویس که جایگزینش شود.