📌 برایفرض کن داریم یک فروشگاه اینترنتی میسازیم وOrderفقط یکStatusداشته باشیم یا بریم سراغState Machine؟ 🤔
Orderمون چندتا وضعیت داره:Pending
Paid
Processing
Shipped
Delivered
Cancelled
خب معلومه دیگه 😎
یک
enum میسازیم:public enum OrderStatus
{
Pending,
Paid,
Processing,
Shipped,
Delivered,
Cancelled
}
بعد داخل
Order:public OrderStatus Status { get; private set; }هرجا هم خواستیم وضعیت رو عوض کنیم:
order.Status = OrderStatus.Paid;
تموم شد رفت. 😎
هم سادهست، هم خواناست، هم Database هم فقط یک ستون
Status داره.ولی یه لحظه صبر کن...
واقعاً هر
Statusای میتونه به هر Status دیگهای تبدیل بشه؟ 🤨مثلاً:
Pending → Paid
Paid → Processing
Processing → Shipped
Shipped → Delivered
اینها منطقی به نظر میرسن.
ولی این چی؟
Delivered → Pending
یا:
Shipped → Paid
یا حتی:
Cancelled → Shipped
احتمالاً نه!
پس مشکل از خود
Status نیست.مشکل اینجاست که اگر فقط یک
enum داشته باشیم، این enum بهتنهایی هیچ چیزی دربارهی قوانین انتقال بین وضعیتها نمیگه.یعنی این کد:
order.Status = OrderStatus.Delivered;
از نظر #C کاملاً معتبره.
ولی از نظر Business ممکنه کاملاً غیرمعتبر باشه.
اینجاست که معمولاً یکی میگه:
«پس قبلش if میذاریم.»
مثلاً:
if (order.Status != OrderStatus.Shipped)
throw new InvalidOperationException();
order.Status = OrderStatus.Delivered;
خب...
بعد یک ماه میشه:
if (status == OrderStatus.Pending)
{
...
}
else if (status == OrderStatus.Paid)
{
...
}
else if (status == OrderStatus.Processing)
{
...
}
بعد یک Requirement جدید میاد:
اگر Payment Failed شد، Order دوباره Payment بشه.
بعد یکی دیگه:
اگر Customer درخواست Cancellation داد، فقط قبل از Shipment اجازه بده.
بعد:
ءAdmin بتونه یک Order رو از حالت Processing به Cancelled ببره، ولی Customer نتونه.و ناگهان...💀 Business Ruleهای مربوط به Lifecycle سفارش پخش شدن توی:
Controller
Service
Handler
Domain
Background Job
...
و هرکس هم یک قانون متفاوت نوشته.
اینجا دقیقاً جاییه که State Machine میتونه ارزش پیدا کنه.
ءState Machine اساساً میگه:
«ءOrder فقط یک Status نداره؛ یک Lifecycle داره و فقط بعضی Transitionها مجاز هستند.»حالا بهجای اینکه هرجای سیستم بنویسیم:
order.Status = OrderStatus.Shipped;
میگیم:
order.Ship();
و خود Domain تصمیم میگیره آیا این Transition مجازه یا نه.
مثلاً:
public void Ship()
{
if (Status != OrderStatus.Processing)
throw new InvalidOperationException(
"Only processing orders can be shipped.");
Status = OrderStatus.Shipped;
}
حالا اگر کسی بگه:
order.Ship();
و Order هنوز
Pending باشه، خود Domain جلوش رو میگیره.این خیلی بهتر از اینه که امیدوار باشیم همهی Callerها قبلش
if درست نوشته باشن. 😏اماااا...
اینجا هم نباید سریع نتیجه بگیریم:
«پس State Machine همیشه بهتره!»
نه.
اگر سیستم ما یک Order خیلی ساده داره:
Pending → Completed
واقعاً لازم نیست برای دو وضعیت یک State Machine عظیم درست کنیم.
پس State Machine قرار نیست صرفاً چون اسمش خفنتره وارد پروژه بشه.
مسئله اینه که:
آیا Lifecycle موجودیت، خودش دارای پیچیدگی Business است؟
اینجا State Machine میتونه خیلی خواناتر و قابلکنترلتر باشه.
حتی میتونی Ruleهایی مثل این داشته باشی:
Customer فقط تا قبل از
Shippedمیتونه Cancellation درخواست کنه.
یا:
ءRefund فقط وقتی مجازه که Payment موفق بوده باشه.
یا:
ءShipment فقط بعد از Payment موفق ایجاد بشه.
اینها دیگه صرفاً
Status نیستن.اینها Business Rules مربوط به Transitionها هستن.
بعضیا فکر میکنن وقتی State Machine داریم، دیگه
Status لازم نیست.نه! State Machine و Status لزوماً رقیب هم نیستن.