Ответ
Domain-Driven Design (DDD) — это подход к разработке сложных бизнес-приложений, где основное внимание уделяется моделированию предметной области (бизнес-логики) в коде. В Node.js-проектах я применяю DDD для создания поддерживаемых и масштабируемых приложений.
Ключевые строительные блоки DDD в Node.js:
-
Сущность (Entity) — объект с уникальным идентификатором:
class User { constructor( public readonly id: string, public email: string, private passwordHash: string ) {} changeEmail(newEmail: string) { // Бизнес-правило: валидация email if (!this.isValidEmail(newEmail)) { throw new Error('Invalid email'); } this.email = newEmail; } } -
Объект-значение (Value Object) — неизменяемый объект без идентификатора:
class Money { constructor( public readonly amount: number, public readonly currency: string ) {} add(other: Money): Money { if (this.currency !== other.currency) { throw new Error('Cannot add different currencies'); } return new Money(this.amount + other.amount, this.currency); } } -
Агрегат (Aggregate) — группа связанных объектов с корневым Aggregate Root:
class Order { private items: OrderItem[] = []; constructor( public readonly id: string, private customerId: string, private status: OrderStatus ) {} addItem(productId: string, quantity: number, price: Money) { // Инвариант: нельзя добавлять товары в завершенный заказ if (this.status === OrderStatus.COMPLETED) { throw new Error('Cannot modify completed order'); } this.items.push(new OrderItem(productId, quantity, price)); } calculateTotal(): Money { return this.items.reduce( (total, item) => total.add(item.calculateSubtotal()), new Money(0, 'USD') ); } }
Практическое применение в Node.js:
- Использую TypeScript для явного моделирования домена через интерфейсы и классы
- Разделяю код на слои: Domain (чистая бизнес-логика), Application (координация), Infrastructure (база данных, внешние API)
- Применяю паттерн Repository для доступа к данным, сохраняя доменную модель независимой от базы данных
- Использую события домена (Domain Events) для связи между агрегатами
В последнем проекте электронной коммерции DDD помог нам четко отделить сложные бизнес-правила расчета скидок и налогов от инфраструктурного кода, что упростило тестирование и позволило быстрее реагировать на изменения требований бизнеса.
Ответ 18+ 🔞
Слушай, я тут подумал — Domain-Driven Design, эта штука, она как хороший алкоголь: если правильно применять, то всё становится ясно и весело, а если накосячить — на утро будет пиздец, голова болит и код не компилируется. Так вот, DDD — это подход, когда ты не просто пишешь код, а строишь целую вселенную из бизнес-логики, чтобы потом в ней не заблудиться, как последний распиздяй.
Основные кирпичики этой вселенной в Node.js:
-
Сущность (Entity) — это как паспорт, у него есть уникальный номер, и с ним можно что-то делать, но номер не меняется. Представь себе пользователя:
class User { constructor( public readonly id: string, // Вот этот хуй с горы — его не перепишешь public email: string, private passwordHash: string ) {} changeEmail(newEmail: string) { // А тут бизнес-правило: если email — хуйня, то и не суйся if (!this.isValidEmail(newEmail)) { throw new Error('Invalid email'); } this.email = newEmail; } } -
Объект-значение (Value Object) — это как наличные в кармане: нет идентификатора, просто сумма и валюта. Создал, использовал — выкинул, ёпта.
class Money { constructor( public readonly amount: number, public readonly currency: string ) {} add(other: Money): Money { // Нельзя сложить доллары с рублями, это же бред, ядрёна вошь! if (this.currency !== other.currency) { throw new Error('Cannot add different currencies'); } return new Money(this.amount + other.amount, this.currency); } } -
Агрегат (Aggregate) — это уже целая банда объектов, у которой есть главарь — Aggregate Root. Все ходят за ним и слушаются, а если кто-то левый суётся — получает в табло.
class Order { private items: OrderItem[] = []; constructor( public readonly id: string, private customerId: string, private status: OrderStatus ) {} addItem(productId: string, quantity: number, price: Money) { // Инвариант, блядь: в завершённый заказ хуй что добавишь! if (this.status === OrderStatus.COMPLETED) { throw new Error('Cannot modify completed order'); } this.items.push(new OrderItem(productId, quantity, price)); } calculateTotal(): Money { return this.items.reduce( (total, item) => total.add(item.calculateSubtotal()), new Money(0, 'USD') ); } }
Как я это в Node.js впариваю на практике:
- Беру TypeScript и начинаю лепить домен из интерфейсов и классов, как из пластилина. Чтоб всё было строго и понятно, а не как у того полупидора, который всё в
anyоборачивает. - Дробил код на слои: Domain (там чистая бизнес-логика, святое место), Application (это как дирижёр, который всем рулит) и Infrastructure (тут уже базы данных, внешние API — всё, что пачкает руки).
- Использую паттерн Repository, чтобы доменная модель жила в своём мире и не парилась, как там данные из базы выковыривают. Доверия ебать ноль к этим ORM, которые всё пытаются сами сделать.
- Запускаю события домена (Domain Events), когда один агрегат другому должен сообщить новость. Типа «эй, заказ создан, можешь инвойс выписывать», а не чтобы они друг у друга из базы данные таскали, как последние мартышлюшки.
В одном проекте для интернет-магазина DDD нас просто спас. Мы там скидки, налоги, акции — овердохуища логики было. Так вот, благодаря тому, что мы всю эту пиздопроебибну от инфраструктуры отсекли, тестировать стало в кайф, и когда бизнес приходил с новой хитрой жопой (прости, с идеей), мы её впиливали за день, а не переписывали пол-проекта. Красота, а не работа.