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

· 4 دقیقه مطالعه

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

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

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

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

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

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

Mermaid diagram

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

مسیر command از تغییر محافظت می‌کند

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

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

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

مدل نوشتن لازم نیست همه‌ی فیلدهایی را که یک داشبورد نمایش می‌دهد داشته باشد. فقط باید آن‌قدر رفتار و وضعیت داشته باشد که بتواند درباره‌ی معتبر بودن 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 را بدون زیرساخت توزیع‌شده اضافه کند.

این کار چه چیزی نمی‌خرد

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

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

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

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

منابع

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

پانویس‌ها

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