اصول طراحی میکروسرویس
طراحی صحیح میکروسرویسها مهمترین عاملی است که موفقیت یا شکست این معماری را تعیین میکند. در این فصل با اصولی که هر میکروسرویس باید رعایت کند آشنا میشویم.
۲.۱ مقدمه
طراحی صحیح میکروسرویسها مهمترین عاملی است که موفقیت یا شکست این معماری را تعیین میکند. در این فصل با اصولی که هر میکروسرویس باید رعایت کند آشنا میشویم.
هدف این فصل: یادگیری Domain-Driven Design (DDD)، تشخیص Bounded Context صحیح، اصول Single Responsibility و Database per Service، و درک “اندازه” مناسب یک میکروسرویس.
۲.۲ Domain-Driven Design (DDD)
Domain-Driven Design رویکردی است که توسط Eric Evans در کتاب معروف ۲۰۰۳ معرفی شد. این رویکرد به ما کمک میکند نرمافزار را حول دامنه تجاری (Business Domain) طراحی کنیم.
چرا DDD برای میکروسرویس مهم است؟
بزرگترین چالش میکروسرویس، تشخیص مرزهای صحیح بین سرویسهاست. DDD به ما ابزار فکری برای این کار میدهد. اگر مرزها اشتباه تعریف شوند، با یک «Distributed Monolith» (بدترین حالت ممکن) مواجه میشویم.
مفاهیم کلیدی DDD
📚 Domain (دامنه)
حوزه فعالیت تجاری که نرمافزار به آن میپردازد. مثلاً: «فروشگاه آنلاین کالاهای دیجیتال» یا «سیستم رزرو هتل».
🎯 Subdomain (زیر دامنه)
بخشهای مختلف یک دامنه. سه نوع داریم:
- Core Subdomain: منطق اصلی کسبوکار که مزیت رقابتی شما است (مثلاً سیستم توصیه محصول در Netflix)
- Supporting Subdomain: پشتیبان core (مثل سیستم اطلاعرسانی)
- Generic Subdomain: عمومی که میتوانید بخرید (مثل احراز هویت با Auth0)
🗣️ Ubiquitous Language (زبان مشترک)
یک زبان مشترک بین developer ها و domain expert ها. اگر در domain ما به مشتری «Customer» میگویند، در کد هم باید «Customer» باشد، نه «User» یا «Client».
📦 Bounded Context
مرزی که داخل آن، یک مدل و یک Ubiquitous Language معنا دارد. این مفهوم کلید طراحی میکروسرویس است.
🧱 Aggregate
گروهی از Entity ها که با هم یک واحد consistency تشکیل میدهند. هر Aggregate یک Root دارد که نقطه ورود است.
🔄 Domain Event
اتفاقی مهم در domain که سایر بخشها به آن واکنش نشان میدهند. مثل: OrderPlaced، PaymentReceived، UserRegistered.
۲.۳ Bounded Context — مهمترین مفهوم
یک Bounded Context یک مرز منطقی است که در آن یک مدل خاص معنا دارد. مفهوم «Customer» در context فروش با «Customer» در context ارسال متفاوت است.
مثال: یک فروشگاه آنلاین
کلمه «Product» در context های مختلف معانی متفاوتی دارد:
| Bounded Context | تعریف Product | فیلدهای مهم |
|---|---|---|
| Catalog | اطلاعات نمایشی محصول | name, description, images, category, brand, specs |
| Inventory | موجودی فیزیکی | sku, stock, warehouse_location, reserved |
| Pricing | قیمتگذاری | base_price, discounts, tax_rate, currency |
| Shipping | ابعاد و وزن برای ارسال | weight, dimensions, fragile, hazardous |
| Order | قلم خرید | line_item_id, qty, unit_price, total |
هر context ممکن است product_id مشترک داشته باشد، اما مدل داده هر کدام مستقل است. این مرزها معمولاً همان مرزهای میکروسرویسها هستند.
Context Map — نقشه روابط
┌──────────────────┐ Upstream/Downstream ┌──────────────────┐
│ │ ─────────────────────────────────►│ │
│ Catalog Context │ │ Order Context │
│ │ Customer-Supplier │ │
└──────────────────┘ └──────────────────┘
│ │
│ │
│ Conformist │ Anti-Corruption Layer
▼ ▼
┌──────────────────┐ Shared Kernel ┌──────────────────┐
│ │ ◄─────────────────────────────► │ │
│ Inventory Context│ │ Shipping Context │
│ │ │ │
└──────────────────┘ └──────────────────┘
الگوهای رابطه بین Context ها:
- Customer-Supplier: یکی به دیگری وابسته است (مثلاً Order به Catalog)
- Conformist: یک تیم بدون چون و چرا از مدل تیم دیگر پیروی میکند
- Anti-Corruption Layer (ACL): یک لایه واسطه که مدل خارجی را به مدل داخلی ترجمه میکند
- Shared Kernel: دو context بخشی از مدل را به اشتراک میگذارند (با احتیاط استفاده شود!)
- Open Host Service: یک API استاندارد که چندین context از آن استفاده میکنند
- Published Language: یک زبان (schema) مشترک برای ارتباط (مثل JSON Schema)
۲.۴ اصل Single Responsibility
هر میکروسرویس باید یک مسئولیت اصلی داشته باشد. این اصل به سادگی میگوید: «دلیل تغییر سرویس باید یک چیز باشد.»
چگونه بفهمیم سرویس مسئولیتهای زیادی دارد؟
🚩 علائم هشدار:
- تیمهای مختلف برای یک سرویس کد مینویسند
- تغییر در یک قابلیت، نیاز به تغییر در ۳+ بخش دیگر دارد
- سرویس بیش از ۱۰-۲۰ endpoint دارد
- کدبیس بیش از ۵۰,۰۰۰ خط کد دارد
- ساختار database پیچیده با ۲۰+ جدول است
- زمان build/test سرویس از ۵ دقیقه بیشتر است
مثال درست و غلط
❌ غلط: User Service تورم کرده
- ثبتنام و لاگین
- پروفایل کاربر
- کیف پول و تراکنشها
- پیامهای داخلی
- اطلاعیهها
- سابقه خرید
این یک Mini-Monolith است!
✅ درست: تفکیک به سرویسهای مجزا
- Auth Service: ثبتنام، لاگین، توکن
- Profile Service: اطلاعات شخصی
- Wallet Service: کیف پول و تراکنش
- Messaging Service: چت داخلی
- Notification Service: اعلانها
هر سرویس مسئولیت مشخص دارد
۲.۵ Database per Service
این یکی از اصول بنیادین میکروسرویس است: هر سرویس باید پایگاه داده مستقل خود را داشته باشد. سرویسهای دیگر هیچگاه نباید مستقیماً به database سرویس دیگری دسترسی داشته باشند.
چرا؟
- Encapsulation: ساختار داخلی دادهها مخفی میماند و میتوانید بدون تأثیر روی سایر سرویسها schema را تغییر دهید
- Loose Coupling: سرویسها به جزئیات یکدیگر وابسته نیستند
- Polyglot Persistence: هر سرویس میتواند بهترین DB را برای کار خود انتخاب کند (PostgreSQL، MongoDB، Redis، Elasticsearch)
- Scalability: هر DB مستقل scale میشود
- Fault Isolation: اگر یک DB down شود، سایر سرویسها مشکلی ندارند
الگوهای پیادهسازی
۱. Database per Service (توصیهشده)
هر سرویس DB کاملاً مستقل خود را دارد، حتی روی سرور فیزیکی متفاوت.
User Service ──► postgres-users:5432 (DB: users_db)
Order Service ──► postgres-orders:5432 (DB: orders_db)
Product Service ──► mongodb-products:27017 (DB: products)
۲. Schema per Service
یک سرور DB، اما هر سرویس schema (یا database داخلی) جداگانه دارد. مناسب برای پروژههای متوسط که هزینه چند سرور DB توجیه ندارد.
-- یک PostgreSQL Server
CREATE DATABASE users_db;
CREATE DATABASE orders_db;
CREATE DATABASE products_db;
-- هر سرویس فقط به DB خودش دسترسی دارد
GRANT ALL ON users_db.* TO 'user_service'@'%';
GRANT ALL ON orders_db.* TO 'order_service'@'%';
۳. Shared Database (Anti-pattern!)
چند سرویس از یک DB استفاده میکنند. این الگو نقض اصل اساسی میکروسرویس است و فقط به عنوان مرحله انتقالی در migration از Monolith قابل قبول است.
مشکل: چگونه دادههای سرویس دیگر را بخوانیم؟
وقتی Order Service نیاز به اطلاعات Product دارد، چه باید کرد؟
راهحل ۱: API Call
درخواست HTTP به Product Service بزنیم.
مزیت: ساده. عیب: latency و coupling زمانی.
راهحل ۲: Data Replication (Event-Driven)
Product Service event منتشر میکند، Order Service یک read-model محلی میسازد.
مزیت: سریع و مقاوم. عیب: eventual consistency.
راهحل ۳: API Composition
API Gateway یا BFF دادهها را از چند سرویس جمع میکند.
مزیت: ساده برای client. عیب: gateway میتواند bottleneck شود.
راهحل ۴: CQRS
یک read-database مجزا با دادههای ترکیبی از چند سرویس.
مزیت: query سریع. عیب: پیچیدگی بالا.
۲.۶ اندازه مناسب یک میکروسرویس
یکی از پرتکرارترین سوالات: «اندازه میکروسرویس چقدر باید باشد؟» پاسخ کوتاه: اندازه ثابتی وجود ندارد.
معیارهای اندازهگیری
📏 خطوط کد
به طور میانگین ۵۰۰ تا ۵۰۰۰ خط کد. اما این معیار قابل اعتمادی نیست.
👥 اندازه تیم (Two Pizza Rule)
یک تیم ۵-۹ نفره باید بتواند سرویس را به طور کامل مدیریت کند.
⏰ زمان بازنویسی
اگر تیم بتواند در ۲ هفته کامل سرویس را بازنویسی کند، اندازه مناسب است.
🎯 یک Bounded Context
بهترین معیار: هر سرویس یک Bounded Context را پیادهسازی میکند.
🔄 یک قابلیت تجاری
هر سرویس باید یک business capability مشخص را پوشش دهد.
💾 ۳-۱۰ جدول
معمولاً هر سرویس ۳ تا ۱۰ جدول دارد. بیشتر از ۲۰ یعنی شاید باید تقسیم شود.
توصیه عملی
به جای فکر کردن به «micro»، روی «independently deployable» تمرکز کنید. اگر میتوانید یک قسمت را مستقل از بقیه تغییر و deploy کنید، اندازه آن مناسب است.
۲.۷ چکلیست طراحی میکروسرویس
قبل از شروع کدزنی هر میکروسرویس، این سوالات را پاسخ دهید:
✓ مسئولیت سرویس چیست؟
در یک جمله بیان کنید. اگر نمیتوانید، احتمالاً مسئولیتهای متعدد دارد.
✓ کدام Bounded Context را پوشش میدهد؟
یک سرویس = یک Bounded Context (در حالت ایدهآل).
✓ Aggregate Root های سرویس کدامند؟
کدام موجودیتها واحد consistency تشکیل میدهند؟
✓ کدام دادهها متعلق به این سرویس هستند؟
سایر سرویسها نباید این دادهها را owner باشند.
✓ ارتباط با سایر سرویسها چگونه است؟
API همگام؟ Event ناهمگام؟ ترکیب؟
✓ کدام Domain Events منتشر میکند؟
چه اتفاقاتی برای سایر سرویسها مهم است؟
✓ کدام Domain Events را گوش میدهد؟
به چه اتفاقاتی واکنش نشان میدهد؟
✓ پایگاه داده و تکنولوژی مناسب چیست؟
PostgreSQL برای relational، MongoDB برای document، Redis برای cache.
✓ SLA و الزامات مقیاسپذیری چیست؟
چند درخواست در ثانیه؟ چه latency؟ چه uptime؟
✓ تیم مالک کیست؟
یک سرویس باید مالک مشخص داشته باشد.
۲.۸ الگوهای طراحی کلیدی
۱. Strangler Fig Pattern (مهاجرت تدریجی)
الگوی نامگذاری شده توسط Martin Fowler از درخت انجیر strangler. به جای بازنویسی کامل Monolith، به تدریج قابلیتها را به میکروسرویس منتقل میکنیم تا monolith قدیمی «خفه» شود.
مرحله ۱: همه ترافیک به Monolith میرود
[Client] ──► [Monolith]
مرحله ۲: یک قابلیت جدید در میکروسرویس ساخته میشود
[Client] ──► [API Gateway] ──┬──► [Monolith]
└──► [New Microservice]
مرحله ۳: قابلیتهای بیشتر منتقل میشوند
[Client] ──► [API Gateway] ──┬──► [Monolith (کوچکتر)]
├──► [Service A]
├──► [Service B]
└──► [Service C]
مرحله ۴: Monolith کاملاً حذف میشود
[Client] ──► [API Gateway] ──┬──► [Service A]
├──► [Service B]
├──► [Service C]
└──► [Service D]
۲. Backend for Frontend (BFF)
هر نوع client (موبایل، وب، دسکتاپ) BFF مخصوص خود دارد که دادههای لازم برای آن client را جمع میکند.
[Mobile App] ──► [Mobile BFF] ─┐
├──► [Microservices]
[Web App] ──► [Web BFF] ─┤
│
[Admin Panel] ──► [Admin BFF] ─┘
۳. Saga Pattern
برای مدیریت تراکنشهای توزیعشده. یک تراکنش بزرگ به چندین تراکنش محلی شکسته میشود و در صورت خطا، عملیات جبرانی اجرا میشود. (در فصل ۷ به طور کامل بررسی میشود.)
۴. CQRS (Command Query Responsibility Segregation)
جدا کردن مدل خواندن (Query) از مدل نوشتن (Command). هر کدام میتوانند DB متفاوتی داشته باشند. (در فصل ۸ به طور کامل بررسی میشود.)
۵. Event Sourcing
به جای ذخیره وضعیت فعلی، تمام تغییرات (Events) ذخیره میشوند و وضعیت فعلی با reply کردن آنها بازسازی میشود.
۶. Anti-Corruption Layer (ACL)
وقتی با یک سیستم legacy یا third-party کار میکنید، یک لایه واسطه ایجاد کنید که مدل خارجی را به مدل داخلی ترجمه کند تا مدل شما “آلوده” نشود.
۲.۹ آنتیپترنها (الگوهای اشتباه)
❌ Distributed Monolith
سرویسها بهقدری به یکدیگر coupled هستند که برای تغییر یک قابلیت، باید چند سرویس را با هم deploy کنید. این بدترین حالت ممکن است: پیچیدگی Distributed بدون مزایای میکروسرویس.
نشانهها: همه سرویسها با هم release میشوند، تغییر در یک سرویس باعث break شدن دیگران، database مشترک، شبکه پیچیده RPC.
❌ Shared Database
چند سرویس از یک DB استفاده میکنند. این الگو همه مزایای encapsulation را از بین میبرد.
راهحل: هر سرویس DB مستقل، ارتباط از طریق API یا Event.
❌ God Service
یک سرویس مرکزی که همه چیز را کنترل میکند. این باعث میشود همه سرویسها به آن وابسته شوند.
راهحل: Decentralization، event-driven communication.
❌ Chatty Services
برای انجام یک کار ساده، چندین سرویس باید با هم ارتباط برقرار کنند. این باعث latency بالا و reliability پایین میشود.
راهحل: طراحی مجدد مرزها، استفاده از API Composition، caching.
❌ Nano-Services
سرویسهای بیش از حد کوچک (مثلاً یک endpoint). overhead عملیاتی بیش از value است.
راهحل: ادغام با سرویس مرتبط بزرگتر.
❌ Premature Decomposition
تقسیم زودهنگام قبل از درک کامل domain. باعث میشود مرزها اشتباه باشند و بعداً نیاز به refactoring دردناک شود.
راهحل: با Modular Monolith شروع کنید، بعد از پایداری مرزها به میکروسرویس مهاجرت کنید.
۲.۱۰ مثال عملی: طراحی فروشگاه آنلاین
بیایید یک فروشگاه آنلاین کالاهای دیجیتال را طراحی کنیم.
گام ۱: شناسایی Subdomain ها
- Core: Catalog، Order، Pricing (مزیت رقابتی)
- Supporting: Notification، Review، Recommendation
- Generic: Auth، Payment Gateway، Email Service
گام ۲: تعریف Bounded Context ها
┌─────────────────────────────────────────────────────────────┐
│ فروشگاه آنلاین (Domain) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Identity │ │ Catalog │ │ Inventory │ │
│ │ Context │ │ Context │ │ Context │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Pricing │ │ Order │ │ Payment │ │
│ │ Context │ │ Context │ │ Context │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Shipping │ │Notification │ │ Review │ │
│ │ Context │ │ Context │ │ Context │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
گام ۳: تعریف هر سرویس
Identity Service
- مسئولیت: ثبتنام، ورود، مدیریت کاربران
- Aggregates: User، Role
- DB: PostgreSQL
- Tech: Django + DRF + JWT
- Events Published: UserRegistered، UserDeactivated
- API: POST /register، POST /login، GET /me
Catalog Service
- مسئولیت: مدیریت محصولات و دستهبندیها
- Aggregates: Product، Category
- DB: PostgreSQL + Elasticsearch (search)
- Tech: FastAPI
- Events Published: ProductCreated، ProductUpdated
- API: GET /products، GET /products/{id}، GET /search
Order Service
- مسئولیت: ثبت و مدیریت سفارشها
- Aggregates: Order (با OrderItem ها)
- DB: PostgreSQL
- Tech: Django + DRF + Celery
- Events Published: OrderPlaced، OrderCancelled
- Events Consumed: PaymentCompleted، InventoryReserved
(و به همین ترتیب برای سایر سرویسها)
گام ۴: تعریف Context Map
چه کسی به چه کسی وابسته است؟ چه ارتباطی sync و چه ارتباطی async است؟
┌──────────┐
│ Identity │
└────┬─────┘
│ JWT validates all requests
▼
┌────────────────────┐
│ API Gateway │
└─┬──────────────────┘
│
┌─────────┴───────────────────────┐
▼ ▼
┌─────────┐ sync(REST) ┌──────────┐
│ Catalog │ ◄───────────────── │ Order │
└────┬────┘ └────┬─────┘
│ │
│ async(events) │ async(events)
▼ ▼
[RabbitMQ Event Bus] ◄─────► [All Services Subscribe]
۲.۱۱ بهترین تجربیات (Best Practices)
- قبل از تقسیم، Domain را عمیق درک کنید. Event Storming یک تکنیک عالی برای این کار است.
- با Modular Monolith شروع کنید. وقتی مرزها پایدار شدند، به میکروسرویس مهاجرت کنید.
- هر سرویس باید independently deployable باشد. اگر این نیست، تقسیم اشتباه است.
- هیچگاه DB را به اشتراک نگذارید. این قانون طلایی است.
- API ها را از ابتدا versioned طراحی کنید.
/api/v1/products - از Domain Events استفاده کنید. این event ها را به عنوان first-class citizen بدانید.
- Backward compatibility را حفظ کنید. هیچگاه breaking change به API نزنید.
- Documentation را خودکار کنید. از OpenAPI/Swagger استفاده کنید.
- برای failure طراحی کنید. فرض کنید همه چیز fail میشود.
- Observability از روز اول. Logging، Metrics، Tracing را همان ابتدا اضافه کنید.
۲.۱۲ خلاصه فصل
آنچه آموختیم:
- Domain-Driven Design پایه طراحی صحیح میکروسرویسهاست
- Bounded Context مرز یک مدل با معنای مشخص است و معمولاً معادل یک سرویس میشود
- هر سرویس باید یک مسئولیت اصلی (Single Responsibility) داشته باشد
- Database per Service یک اصل بنیادین است که نباید نقض شود
- اندازه مناسب سرویس بسته به context متفاوت است؛ معیار اصلی «independently deployable» بودن آن است
- الگوهای کلیدی: Strangler Fig، BFF، Saga، CQRS، Event Sourcing، Anti-Corruption Layer
- آنتیپترنهای اصلی: Distributed Monolith، Shared Database، God Service، Chatty Services، Nano-Services
- قبل از تقسیم، با Modular Monolith شروع کنید و Domain را به خوبی درک کنید