CQRS حداقلی: مسیرهای کد را جدا کنید، یک برنامه و یک پایگاهداده نگه دارید
· 4 دقیقه مطالعهمهندسی معماری نرمافزار #architecture #cqrs #cqs #dotnet #csharp
در نوشتهی اول دیدیم که تغییر یک DTO چطور میتواند یک عمل کسبوکار را پنهان کند.1 در نوشتهی دوم، آن عمل را با commandها صریح کردیم.2
گام بعدی به سرویس جدید، queue یا پایگاهدادهی دوم نیاز ندارد. یک برنامه و یک پایگاهداده را نگه دارید و مسیرهای کد را بر اساس کاری که انجام میدهند جدا کنید.
در این نوشته، این شکل را CQRS حداقلی مینامم: مسیرها و مدلهای جدا برای command و query، در یک برنامه و یک پایگاهداده.
یک برنامه، دو مسئولیت
command وضعیت سیستم را تغییر میدهد. دادهی لازم برای اجرای قانون را میخواند، معتبر بودن درخواست را بررسی میکند و نتیجه را ذخیره میکند.
query به یک سؤال پاسخ میدهد. داده را در شکلی برمیگرداند که فراخواننده لازم دارد و نباید فقط برای ساختن یک صفحه، قواعد مسیر command را اجرا کند.
هر دو مسیر از یک پایگاهداده استفاده میکنند و با هم منتشر میشوند. تفاوتشان در کد است، چون کار متفاوتی انجام میدهند. راهنمای 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 و دادهای که تغییر میدهد میتوانند در یک تراکنش باشند.
- query که بلافاصله پس از command موفق اجرا میشود، وضعیت جدید را میخواند.
- فقط یک استقرار داریم و مشکلات persistence در همان برنامه قابل بررسی هستند.
اما این طراحی، مقیاسپذیری مستقل مسیر خواندن، نمایشهای غیرنرمال برای گزارشگیری یا جداسازی از بار زیاد نوشتن را فراهم نمیکند. هر کدام از اینها هزینه و تصمیم جداگانهای هستند. نوشتهی بعدی بررسی میکند وقتی یک تیم مدلها یا مخزنهای داده را بیشتر جدا میکند، چه چیزهایی تغییر میکند.
از مرزی شروع کنید که واقعاً لازم دارید
وقتی مسیر command قواعد معناداری دارد یا درخواستهای خواندن به دادهای با شکلی متفاوت از مدل نوشتن نیاز دارند، این مسیرها را جدا کنید. تا وقتی مسئلهی واقعی به جداسازی بیشتر نیاز ندارد، برنامه و پایگاهداده را کنار هم نگه دارید.