CQRS حداقلی: مسیرهای کد را جدا کنید، یک برنامه و یک دیتابیس نگه دارید

منتشر شده در · 5 دقیقه مطالعه

مهندسی معماری نرم‌افزار #architecture #cqrs #cqs #dotnet #csharp

مقدمهٔ مجموعه: CQRS: روشن‌کردن مرزهای برنامه

در نوشته‌ی اول دیدیم که تغییر یک DTO چطور می‌تواند یک عمل کسب‌وکار را پنهان کند.1 در نوشته‌ی دوم، آن عمل را با command‌ها صریح کردیم.2

گام بعدی به سرویس جدید، queue یا دیتابیس دوم نیاز ندارد. یک برنامه و یک پایگاه‌داده را نگه دارید و مسیرهای کد را بر اساس کاری که انجام می‌دهند جدا کنید.

در این نوشته، این شکل را CQRS حداقلی می‌نامم: مسیرها و مدل‌های جدا برای command و query، در یک برنامه و یک پایگاه‌داده.

یک برنامه، دو مسئولیت

command وضعیت سیستم را تغییر می‌دهد. داده‌ی لازم برای اجرای قانون را می‌خواند، معتبر بودن درخواست را بررسی می‌کند و نتیجه را ذخیره می‌کند.

query به یک سؤال پاسخ می‌دهد. داده را در شکلی برمی‌گرداند که فراخواننده لازم دارد و نباید فقط برای ساختن یک صفحه، قواعد مسیر command را اجرا کند.

Mermaid diagram

هر دو مسیر از یک پایگاه‌داده استفاده می‌کنند و با هم منتشر می‌شوند. تفاوتشان در کد است، چون کار متفاوتی انجام می‌دهند. راهنمای CQRS مایکروسافت این الگو را جداسازی مدل‌های خواندن و نوشتن می‌داند و در عین حال می‌گوید استفاده از یک مخزن داده‌ی مشترک می‌تواند همچنان انتخاب مناسبی باشد.[2]

مسیر command مسئول تغییر است

فرض کنید باید سفارشی ثبت شود. درخواست، یک command است، نه یک موجودیت سفارش که ویرایش شده باشد:

public sealed record PlaceOrder(Guid CustomerId, IReadOnlyList<OrderLine> Lines);

handler، مشتری و اقلام لازم را می‌خواند، موجودی و قوانین ثبت سفارش را بررسی می‌کند، سفارش را می‌سازد و تراکنش را commit می‌کند. ممکن است command رد شود، چون درخواست مجاز نیست. این بخشی از قرارداد API است.

write model لازم نیست همه‌ی فیلدهایی را که یک داشبورد نمایش می‌دهد داشته باشد. فقط به رفتار و وضعیتی نیاز دارد که برای تصمیم‌گیری درباره‌ی معتبر بودن PlaceOrder لازم است.

مسیر query داده را شکل می‌دهد

فهرست سفارش سؤال دیگری می‌پرسد:

public sealed record OrderSummaryDto(
    Guid Id,
    string Number,
    string CustomerName,
    decimal Total,
    string Status);

GetOrderSummary می‌تواند داده را مستقیماً از همان دیتابیس به این DTO نگاشت کند. لازم نیست یک aggregate از نوع Order را دوباره بسازد، قوانین PlaceOrder را اجرا کند یا فیلدهایی را برگرداند که صفحه‌ی فهرست کنار می‌گذارد.

دیتابیس مشترک است، اما لازم نیست مدل‌ها یکسان باشند. گرگ یانگ CQRS را جداسازی مسئولیت‌های خواندن و نوشتن می‌داند؛ مدل‌ها هم فقط زمانی متفاوت می‌شوند که این مسئولیت‌ها به مدل متفاوتی نیاز داشته باشند.[1]

CQS و CQRS چیزهای متفاوتی را جدا می‌کنند

CQS CQRS حداقلی
مرز اصلی یک عملیات یا وضعیت را تغییر می‌دهد یا اطلاعات برمی‌گرداند. کد command و query مسیرهای جدا دارند.
محدوده یک متد یا عملیات API. ساختار برنامه پیرامون این عملیات.
دیتابیس تغییری نمی‌کند. همچنان یک پایگاه‌داده است.
نمونه PlaceOrder و GetOrderSummary قراردادهای متفاوت دارند. handler‌ها و مدل‌های جدا این درخواست‌ها را پردازش می‌کنند.

CQS فهم هر عملیات را ساده‌تر می‌کند. CQRS این جداسازی را در ساختار برنامه نشان می‌دهد. یک سیستم می‌تواند CQS داشته باشد، بدون اینکه کدش را بازآرایی کند؛ همچنین می‌تواند این مرز حداقلی CQRS را بدون زیرساخت توزیع‌شده اضافه کند.

این طراحی چه چیزهایی را حل نمی‌کند

یک برنامه و یک پایگاه‌داده، چند ویژگی مفید را حفظ می‌کنند:

اما این طراحی، مقیاس‌پذیری مستقل query path، projectionهای نرمال‌نشده برای گزارش‌گیری یا جداسازی از بار زیاد نوشتن را ایجاد نمی‌کند. هر کدام از این‌ها هزینه و تصمیم جداگانه‌ای هستند. نوشته‌ی بعدی بررسی می‌کند وقتی یک تیم مدل‌ها یا مخزن‌های داده را بیشتر جدا می‌کند، چه چیزهایی تغییر می‌کند.

از مرزی شروع کنید که واقعاً لازم دارید

وقتی command path قواعد معناداری دارد یا query path به شکلی از داده نیاز دارد که جای آن در write model نیست، مسیر command و query را جدا کنید. تا وقتی مسئله‌ی واقعی به جداسازی بیشتر نیاز ندارد، برنامه و دیتابیس را کنار هم نگه دارید.

منابع

  1. Greg Young, CQRS Documents
  2. Microsoft Learn, "CQRS pattern"

پانویس‌ها

  1. وقتی CRUD دیگر زبان مناسبی برای مسئله نیست
  2. CQS و command‌ها: کاری کنید برنامه هدف کاربر را بفهمد
اشتراک‌گذاری در LinkedIn