
یه زمانی یه «مدل واحد» برای نوشتن و خوندن دیتا کافی بود…
ولی الان دیگه واقعاً نه 😄
وقتی سیستم بزرگ میشه، همزمانی زیاد میشه، و نیاز به سرعتِ خوندن و گزارشگیری میاد وسط، اون مدل واحد کمکم کند و پیچیده میشه. (و تازه دردِ مقیاسپذیری هم اضافه میشه.)
پس حداقل باید CQRS رو بفهمیم.
CQRS مخففِ Command Query Responsibility Segregation ـه.
یعنی:
Command (فرمان) = هر چیزی که «وضعیت رو تغییر میده»
Query (پرسوجو) = هر چیزی که «فقط میخونه و نمایش میده»
ایدهی اصلی اینه که مدلی که باهاش مینویسی، لزوماً همون مدلی نیست که باهاش میخونی.
این جدا کردن، تو بعضی سیستمها خیلی ارزش داره…
ولی حواست باشه: برای خیلی از پروژههای کوچیک، CQRS میتونه «پیچیدگی اضافه» بیاره. (martinfowler.com)
چون میتونی هر سمت رو جداگانه بهینه کنی:
سمت نوشتن: قوانین کسبوکار، اعتبارسنجی، همزمانی، جلوگیری از خرابکاری
سمت خوندن: سرعت، سرچ، گزارش، مدل ساده برای UI
مایکروسافت هم دقیقاً همین رو میگه: با جدا کردن مدلِ خواندن/نوشتن، میتونی کارایی و مقیاسپذیری رو بهتر کنی.
توی خیلی از پیادهسازیهای CQRS، نوشتن که انجام شد، مدلِ خواندن با کمی تأخیر آپدیت میشه.
یعنی ممکنه کاربر «ثبت کرد»، ولی «لیست» یک لحظه بعد آپدیت شه.
این طبیعیـه… ولی باید تو طراحی UI و تجربه کاربر حواست باشه.
Axon Framework یه فریمورک جاوا/اسپرینگ برای ساخت سیستمهای رویدادمحور (event-driven) با محوریتِ DDD + CQRS + Event Sourcing ـه. (GitHub)
به زبان خودمونی:
Axon خیلی از «سیمکشیهای تکراری» رو برات انجام میده تا تو روی مدلِ دامنه و قوانین تمرکز کنی.
چیزهایی که معمولاً تو Axon زیاد میبینی:
Aggregate (هستهی دامنه برای نوشتن)
Command Handler (جایی که فرمان رو میگیره)
Event (خروجیِ تغییر)
Event Sourcing Handler (بازسازی وضعیت با رویدادها)
Projection / Read Model (مدلِ خواندن)
Axon وقتی Event Sourcing استفاده میکنی، وضعیتِ Aggregate رو با ریپلی کردن رویدادها بازسازی میکنه.
سناریو:
کاربر سفارش ثبت میکنه.
بعد میخواد لیست سفارشهاشو ببینه.
فرمان (Command):
«سفارش بساز»
رویداد (Event):
«سفارش ساخته شد»
اینجا حداقل یک قانون داریم:
سفارش بدون شناسه و آیتمها معنی نداره.
اینجا تصمیم میگیریم آیا فرمان مجازه یا نه.
اگر مجازه، رویداد تولید میکنیم.
نکتهی Axon: خیلی وقتها عمر Aggregate با یه «سازندهی هندلرِ فرمان» شروع میشه.
یک شبهکد خیلی خلاصه (جاوا/اسپرینگ + Axon)، فقط برای اینکه تصویر بسازه:
@Aggregate public class OrderAggregate { @AggregateIdentifier private String orderId; private boolean created; public OrderAggregate() {} @CommandHandler public OrderAggregate(CreateOrderCommand cmd) { // اعتبارسنجی خیلی ساده if (cmd.items().isEmpty()) throw new IllegalArgumentException("سفارش خالیه"); AggregateLifecycle.apply(new OrderCreatedEvent(cmd.orderId(), cmd.items())); } @EventSourcingHandler public void on(OrderCreatedEvent evt) { this.orderId = evt.orderId(); this.created = true; } }
ایدهی اصلی:
Command میاد.
اگر معتبر بود، Event صادر میشه.
با EventSourcingHandler وضعیت ذخیره/بازسازی میشه.
اینجا دیگه دنبال قوانین سخت نیستیم.
دنبال یه جدول/مدل سادهایم که UI راحت بخونه.
پس رویداد OrderCreatedEvent رو میگیریم و یه رکورد تو دیتابیسِ خواندن مینویسیم:
@Component public class OrderProjection { private final OrderViewRepository repo; public OrderProjection(OrderViewRepository repo) { this.repo = repo; } @EventHandler public void on(OrderCreatedEvent evt) { repo.save(new OrderView(evt.orderId(), evt.items())); } @QueryHandler public List<OrderView> handle(GetOrdersQuery q) { return repo.findByUserId(q.userId()); } }
اینجا دقیقاً همون جداسازی اتفاق افتاده:
Aggregate = نوشتن و قوانین
Projection = خواندن و نمایش
و چون این دو جدا هستن، میتونی Read Model رو برای سرچ و سرعت، خیلی راحتتر بهینه کنی.
حداقل وقتی که اینها رو داری:
دامنهی پیچیده و قانون زیاد
نیاز جدی به گزارش و خواندن سریع
مقیاسپذیری و رویدادمحور بودن برات مهمه
«ردپا» و تاریخچهی تغییرات ارزش داره (اینجا Event Sourcing هم میاد وسط)
ولی اگر پروژهت کوچیکه و CRUD ساده جواب میده، خیلی وقتها CQRS فقط هزینهی ذهنی میذاره.
CQRS میگه:
«نوشتن و خوندن رو قاطی نکن.»
Axon کمک میکنه این جداسازی رو تمیزتر بسازی:
Aggregate برای نوشتن، Projection برای خوندن، و اگر خواستی Event Sourcing برای تاریخچه و بازسازی.
پ.ن: لینک پروژه گیت هاب که یک سمپل خوب برای شروع هست:
https://github.com/a-rostami/CQRS-Axon-Implementation