Software Architecture

تسلط بر طراحی مبتنی بر دامنه: راهنمای عملی برای مدل‌سازی منطق کسب‌وکار پیچیده

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

زبان همگانی: پل ارتباطی

قبل از غرق شدن در ساختارهای فنی، باید مهم‌ترین جنبه DDD را ایجاد کنیم: زبان همگانی. این زبانی مشترک است که توسط توسعه‌دهندگان و متخصصان دامنه (ذی‌نفعان کسب‌وکار) به کار می‌رود. این زبان خطاهای ترجمه را حذف می‌کند؛ جایی که کلمه «سفارش» برای بخش فروش معنایی متفاوت و برای بخش لجستیک معنایی دیگر دارد.

با اطمینان از اینکه نام‌های کد با اصطلاحات کسب‌وکار مطابقت دارند، مستندسازی زنده‌ای ایجاد می‌کنیم که همیشه مرتبط باقی می‌ماند. اگر یک اصطلاح کسب‌وکار تغییر کند، کد نیز باید احتمالاً همراه با آن تغییر کند.

زمینه‌های محدود: مدیریت پیچیدگی در مقیاس

همان‌طور که دامنه‌ها رشد می‌کنند، یک زبان همگانی واحد اغلب ناکافی می‌شود. اینجا است که زمینه‌های محدود (Bounded Contexts) وارد عمل می‌شوند. یک زمینه محدود، مرزی را تعریف می‌کند که در آن یک مدل خاص اعمال می‌شود و زبان همگانی معتبر است. یک مفهوم یکسان ممکن است در زمینه‌های مختلف معانی متفاوتی داشته باشد. برای مثال، «حساب» در یک زمینه بانکی ممکن است به پروفایل مشتری اشاره کند، در حالی که در یک زمینه صورتحساب، به یک ردیف دفتر کل مالی اشاره دارد.

جداسازی این زمینه‌ها به تیم‌ها اجازه می‌دهد به صورت مستقل کار کنند و مناسب‌ترین راه‌حل‌های فنی را برای هر زیردامنه انتخاب کنند.

تجمیع‌ها: مرزهای یکپارچگی

در داخل یک زمینه محدود، مدل دامنه خود را با استفاده از تجمیع‌ها (Aggregates) سازماندهی می‌کنیم. یک تجمیع، خوشه‌ای از اشیاء مرتبط است که ما آن‌ها را به عنوان یک واحد برای تغییرات داده در نظر می‌گیریم. این شامل یک ریشه تجمیع و موجودیت‌های داخلی آن است.

قانون کلیدی این است: اشیاء خارجی تنها می‌توانند به ریشه تجمیع ارجاع دهند، نه به اشیاء داخلی. این امر یکپارچگی تراکنشی و یکدستی را تضمین می‌کند. هنگامی که یک تجمیع را به‌روزرسانی می‌کنید، آن را به عنوان یک کل به‌روزرسانی می‌کنید.

مثال: یک تجمیع سفارش


public class Order {
    private List items;
    private CustomerId customerId;
    private Money totalAmount;

    public void addItem(ProductId productId, int quantity) {
        // منطق کسب‌وکار برای افزودن یک آیتم
        this.items.add(new OrderItem(productId, quantity));
        this.recalculateTotal();
    }

    public void cancel() {
        if (this.items.isEmpty()) {
            throw new IllegalStateException("Cannot cancel an empty order");
        }
        // راه‌اندازی رویداد دامنه
        this.raiseEvent(new OrderCanceledEvent(this.id));
    }

    private void recalculateTotal() {
        this.totalAmount = this.items.stream()
            .map(item -> item.getPrice().multiply(item.getQuantity()))
            .reduce(Money.ZERO, Money::add);
    }
}

در این مثال، Order ریشه تجمیع است. OrderItem یک موجودیت در داخل تجمیع است. توجه کنید که چگونه روش cancel قوانین کسب‌وکار را در داخل اعمال می‌کند و یکپارچگی تجمیع را محافظت می‌نماید.

موجودیت‌ها در مقابل اشیاء ارزشی

تمایز مهمی در DDD بین موجودیت‌ها (Entities) و اشیاء ارزشی (Value Objects) وجود دارد:

  • موجودیت‌ها دارای یک هویت منحصر‌به‌فرد هستند که در طول زمان پایدار می‌ماند، حتی اگر ویژگی‌های آن‌ها تغییر کند (مانند شناسه سفارش).
  • اشیاء ارزشی غیرقابل تغییر هستند و توسط ویژگی‌هایشان تعریف می‌شوند. دو شیء ارزشی اگر تمام ویژگی‌هایشان برابر باشد، با هم برابرند (مانند یک شیء Money یا EmailAddress).

استفاده از اشیاء ارزشی برای ویژگی‌هایی مانند پول یا آدرس‌ها از وضعیت‌های نامعتبر جلوگیری کرده و مدل دامنه را غنی‌تر و بیانگرتر می‌سازد.

رویدادهای دامنه: جداسازی اثرات جانبی

دامنه‌های پیچیده اغلب به اثرات جانبی نیاز دارند، مانند ارسال ایمیل یا به‌روزرسانی شمارش موجودی. به جای جفت‌سازی مستقیم این اقدامات با منطق دامنه، ما از رویدادهای دامنه (Domain Events) استفاده می‌کنیم. این‌ها اعلان‌هایی هستند که نشان می‌دهند اتفاق مهمی در دامنه رخ داده است.

در مثال کد بالا، ما raiseEvent(new OrderCanceledEvent(this.id)) را مشاهده کردیم. این امر عمل لغو را از فرآیند اعلان جدا می‌کند. یک دستگیرنده رویداد جداگانه می‌تواند به OrderCanceled گوش دهد و اقدامات لازم در پایین‌دست را انجام دهد، بدون اینکه تجمیع Order را شلوغ کند.

نتیجه‌گیری

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

Share: