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

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

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

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

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

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

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

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

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

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

یک برنامه، command را از مسیر قواعد کسب‌وکار و query را مستقیم به همان پایگاه‌داده می‌فرستد.

هر دو مسیر از یک پایگاه‌داده استفاده می‌کنند و با هم منتشر می‌شوند. تفاوتشان در کد است، چون کار متفاوتی انجام می‌دهند. راهنمای 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 را بدون زیرساخت توزیع‌شده اضافه کند.

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

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

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

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

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

منابع

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

پانویس‌ها

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