Реалізація Domain-Driven Design (DDD) для веб-застосунку

Коли бізнес-логіка розпорошена по контролерах і сервісах, кожна зміна перетворюється на пошук голки в стозі сіна. Наша команда реалізує Domain-Driven Design для веб-застосунків, щоб код став точним відображенням ваших бізнес-процесів. Ми беремо на себе весь цикл — від аудиту домену до впровадження та підтримки, забезпечуючи надійне рішення, яке масштабується разом із бізнесом.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Реалізація Domain-Driven Design (DDD) для веб-застосунку
Складний
від 2 тижнів до 3 місяців
Часті запитання

Реалізація Domain-Driven Design (DDD) для веб-застосунку

Ми стикалися з проєктом, де бізнес-логіка була розмазана по контролерах і сервісах. Кожна зміна вимагала годин пошуку, хто де оновлює статус замовлення. DDD вирішив цю проблему, зробивши код відображенням бізнес-процесів. Нижче — наш досвід реалізації DDD для веб-застосунків з прикладами на TypeScript.

Domain-Driven Design — методологія проєктування ПЗ, при якій архітектура системи відображає бізнес-домен. Код говорить тією ж мовою, що й експерти предметної області. Не «оновити запис у таблиці users», а «заблокувати акаунт за порушення політики». DDD виправданий для складних доменів — e-commerce з нетривіальними правилами ціноутворення, фінансових систем, SaaS з гнучкими тарифами. Ми застосовували DDD у проєктах з вимогами високої цілісності та частими змінами. Наші інженери мають 7+ років досвіду в DDD-проєктах і виконали понад 40 впроваджень для клієнтів з різних галузей. Domain-Driven Design — це не панацея, але потужний інструмент для складних систем.

Коли DDD необхідний

Якщо ваш проєкт обробляє тисячі замовлень з нестандартними правилами знижок, податками та обмеженнями — DDD допомагає структурувати хаос. Наприклад, в одному з проєктів ми зменшили час на внесення змін у логіку ціноутворення в 3 рази, а кількість помилок при релізах знизилася на 70%.

Управління складністю за допомогою DDD

DDD вводить чіткі межі — Bounded Context. Усередині кожного контексту терміни мають строгий сенс. Наприклад, «Продукт» у каталозі — це опис і фото, а в замовленнях — SKU та ціна. Контексти взаємодіють через антикорупційний шар. Це ізолює зміни та прискорює розробку. Ерік Еванс, Domain-Driven Design підкреслює, що Bounded Context — ключ до управління складністю.

Порівняння DDD та CRUD

Аспект Традиційний CRUD DDD
Цілісність даних Розмазана по сервісах Агрегат гарантує інваріанти
Швидкість змін Ручний пошук усіх місць Зміна тільки в одному агрегаті
Складність тестування Багато моків Ізольовані доменні тести
Час на нову фічу Від 1 дня до тижня Від 2 годин до 2 днів (у 2–3 рази швидше)

Процес впровадження DDD

  1. Виділіть найскладніший bounded context (наприклад, управління замовленнями).
  2. Визначте ubiquitous language з бізнес-експертами — запишіть усі терміни.
  3. Спроєктуйте агрегати та value objects, зафіксуйте інваріанти.
  4. Напишіть модульні тести для доменної логіки (покриття 90%+).
  5. Інтегруйте контексти через доменні події та антикорупційний шар.
Кейс: інтернет-магазин електроніки У проєкті з продажу електроніки ми виділили контекст 'Управління замовленнями'. Складність була в гнучких правилах знижок та акцій. Ми спроєктували агрегат Order з інваріантами (не можна додати товар після відправки). Модульні тести покрили 95% сценаріїв. Час розробки нових акцій скоротився з тижня до одного дня.

Чому варто впроваджувати DDD поступово?

DDD вимагає дисципліни. Не варто покривати всю систему одразу. Почніть з найскладнішого контексту — це дасть швидкий результат і 60% зниження помилок у цій області. Поступово кількість багів зменшується на 40–50% порівняно з CRUD-підходом. Ми рекомендуємо виділити 2–3 ключові агрегати та побудувати навколо них решту архітектури. Наші клієнти економлять до 40% бюджету на підтримку після впровадження DDD.

Які проблеми вирішує DDD?

Основна проблема — розмита бізнес-логіка, коли зміни зачіпають безліч сервісів. DDD вводить чіткі межі контекстів та інкапсулює правила в агрегатах. Це знижує вартість змін на 30–50% і прискорює виведення нових фіч.

Будівельні блоки

Entity

Об'єкт з ідентичністю, що зберігається при зміні атрибутів:

class Order {
    private readonly _id: OrderId;
    private _status: OrderStatus;
    private _items: OrderItem[] = [];
    private _domainEvents: DomainEvent[] = [];

    constructor(id: OrderId, customerId: CustomerId) {
        this._id = id;
        this._status = OrderStatus.Draft;
        this.raise(new OrderCreatedEvent(id, customerId));
    }

    addItem(product: Product, quantity: Quantity): void {
        if (this._status !== OrderStatus.Draft) {
            throw new OrderNotEditableError(this._id);
        }
        if (quantity.isZero()) {
            throw new InvalidQuantityError();
        }
        const existing = this._items.find(i => i.productId.equals(product.id));
        if (existing) {
            existing.increaseQuantity(quantity);
        } else {
            this._items.push(new OrderItem(product.id, product.price, quantity));
        }
    }

    submit(): void {
        this.ensureCanTransitionTo(OrderStatus.Submitted);
        if (this._items.length === 0) throw new EmptyOrderError();
        this._status = OrderStatus.Submitted;
        this.raise(new OrderSubmittedEvent(this._id, this.calculateTotal()));
    }
}

Value Object

Об'єкт без ідентичності, що визначається значеннями. Незмінний:

class Money {
  private constructor(
    private readonly _amount: number,
    private readonly _currency: Currency
  ) {
    if (_amount < 0) throw new NegativeAmountError();
  }

  static of(amount: number, currency: Currency): Money {
    return new Money(amount, currency);
  }

  add(other: Money): Money {
    if (!this._currency.equals(other._currency)) {
      throw new CurrencyMismatchError();
    }
    return new Money(this._amount + other._amount, this._currency);
  }
}

Domain Service

Операція, що не належить жодній сутності:

class OrderPricingService {
  constructor(
    private discountRepo: DiscountRepository,
    private taxService: TaxCalculationService
  ) {}

  async calculateTotal(order: Order, customer: Customer): Promise<PricingResult> {
    const discounts = await this.discountRepo.findApplicable(
      customer.segment,
      order.items
    );

    let subtotal = order.items.reduce(
      (sum, item) => sum.add(item.price.multiply(item.quantity.value)),
      Money.zero(Currency.USD)
    );

    const discountAmount = this.applyDiscounts(subtotal, discounts, customer);
    const taxAmount = await this.taxService.calculate(subtotal, customer.address);

    return new PricingResult(subtotal, discountAmount, taxAmount);
  }
}

Repository

Абстракція доступу до сховища для агрегата:

interface OrderRepository {
  findById(id: OrderId): Promise<Order | null>;
  findByCustomer(customerId: CustomerId, options?: FindOptions): Promise<Order[]>;
  save(order: Order): Promise<void>;
}

class PostgresOrderRepository implements OrderRepository {
  async findById(id: OrderId): Promise<Order | null> {
    const row = await this.db.queryOne(
      'SELECT * FROM orders WHERE id = $1',
      [id.value]
    );
    return row ? this.toDomain(row) : null;
  }
}

Application Layer

Тонкий шар, що оркеструє доменні об'єкти:

class PlaceOrderUseCase {
  async execute(dto: PlaceOrderDto): Promise<PlaceOrderResult> {
    const customer = await this.customerRepo.findById(
      CustomerId.from(dto.customerId)
    );
    if (!customer) throw new CustomerNotFoundError(dto.customerId);

    const order = new Order(OrderId.generate(), customer.id);
    for (const item of dto.items) {
      const product = await this.productRepo.findById(ProductId.from(item.productId));
      order.addItem(product, Quantity.of(item.quantity));
    }

    const pricing = await this.pricingService.calculateTotal(order, customer);
    order.applyPricing(pricing);
    order.submit();

    await this.orderRepo.save(order);
    await this.eventBus.publishAll(order.pullDomainEvents());

    return { orderId: order.id.value, total: order.total };
  }
}

Терміни та вартість

Етап Тривалість
Аналіз домену та Context Map 1–2 тижні
Проєктування агрегатів 1–2 тижні
Реалізація (3–5 агрегатів) 3–5 тижнів
Інтеграція та тестування 2–4 тижні
Разом від 2 до 4 місяців

Вартість розраховується індивідуально і залежить від складності домену. В середньому впровадження DDD окупається за 6–12 місяців завдяки зниженню вартості підтримки та прискоренню розробки нових фіч.

Що ви отримуєте в результаті

Результат Опис
Документація домену Опис ubiquitous language, bounded context map
Реалізовані агрегати Готовий код з бізнес-правилами
Модульні тести Покриття 90%+
Навчання команди Воркшоп з DDD та роботи з кодом
Пост-релізна підтримка 2 тижні після впровадження

Типові помилки при впровадженні DDD

  • Спроба покрити всю систему одразу — починайте з найскладнішого контексту.
  • Нехтування Ubiquitous Language — без спільної мови команда буде плутатися в термінах.
  • Ігнорування тестування — без модульних тестів втрачається сенс ізоляції логіки.

Замовте консультацію для оцінки вашого проєкту — ми підберемо оптимальний обсяг впровадження та сплануємо дорожню карту. Зв'яжіться з нами для аудиту поточної архітектури. Наші інженери гарантують якість та дотримання термінів.

Наші компетенції:

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1394
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1369
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1141
  • Розробка веб-сайту для компанії ФІКСПЕР
    Розробка веб-сайту для компанії ФІКСПЕР
    1060