Определение доменной области (Bounded Context)
Как правильно определить границы микросервисов с помощью концепции Bounded Context из DDD
Определение доменной области (Bounded Context)
Bounded Context (ограниченный контекст) — ключевая концепция Domain-Driven Design, которая помогает определить правильные границы микросервисов.
Что такое Bounded Context?
Bounded Context — это явная граница, внутри которой модель домена имеет определенное значение и применение.
Пример: E-commerce система
// Контекст "Каталог товаров"
namespace Catalog {
interface Product {
id: string;
name: string;
description: string;
price: Money;
images: string[];
specifications: Record<string, any>;
}
}
// Контекст "Корзина покупок"
namespace ShoppingCart {
interface Product {
id: string;
name: string;
price: Money;
quantity: number;
}
}
// Контекст "Склад"
namespace Inventory {
interface Product {
id: string;
sku: string;
stockLevel: number;
location: string;
reorderPoint: number;
}
}
Как определить границы
1. Анализ бизнес-процессов
Изучите, как разные отделы работают с данными:
// Маркетинг работает с продуктом как с контентом
class MarketingContext {
updateProductDescription(productId: string, description: string) {
// Фокус на презентации
}
}
// Склад работает с продуктом как с физическим объектом
class WarehouseContext {
updateStockLevel(sku: string, quantity: number) {
// Фокус на логистике
}
}
2. Выявление ubiquitous language
Каждый контекст имеет свой язык:
// В контексте продаж
interface Order {
customer: Customer;
items: OrderItem[];
total: Money;
discount: Discount;
}
// В контексте доставки
interface Shipment {
recipient: Address;
packages: Package[];
carrier: string;
trackingNumber: string;
}
3. Идентификация агрегатов
Определите корневые сущности и их границы:
// Агрегат "Заказ"
class Order {
private items: OrderItem[] = [];
addItem(product: Product, quantity: number) {
// Бизнес-логика внутри агрегата
const item = new OrderItem(product, quantity);
this.items.push(item);
this.recalculateTotal();
}
private recalculateTotal() {
// Инварианты поддерживаются внутри агрегата
}
}
Context Mapping
Типы взаимодействий между контекстами
// 1. Shared Kernel - общее ядро
namespace Shared {
export interface Money {
amount: number;
currency: string;
}
}
// 2. Customer-Supplier - поставщик-потребитель
class OrderService {
constructor(private inventoryService: InventoryService) {}
async createOrder(items: OrderItem[]) {
// Проверяем наличие через API поставщика
const available = await this.inventoryService.checkAvailability(items);
if (!available) throw new Error('Out of stock');
}
}
// 3. Anti-Corruption Layer - защитный слой
class LegacySystemAdapter {
async getProduct(id: string): Promise<Product> {
const legacyData = await this.legacySystem.fetchProduct(id);
return this.translateToModernFormat(legacyData);
}
private translateToModernFormat(legacy: any): Product {
// Изолируем домен от устаревшей модели
return {
id: legacy.PROD_ID,
name: legacy.PROD_NAME,
price: { amount: legacy.PRICE, currency: 'USD' }
};
}
}
Практические рекомендации
1. Event Storming
Проведите сессию Event Storming для выявления границ:
// События помогают найти границы
interface DomainEvent {
eventType: string;
aggregateId: string;
timestamp: Date;
}
class OrderPlaced implements DomainEvent {
eventType = 'OrderPlaced';
constructor(
public aggregateId: string,
public timestamp: Date,
public customerId: string,
public items: OrderItem[]
) {}
}
2. Избегайте преждевременного разделения
// Плохо: слишком мелкое разделение
class ProductNameService {} // Только имя?
class ProductPriceService {} // Только цена?
// Хорошо: связанная функциональность вместе
class ProductCatalogService {
updateProduct(id: string, updates: ProductUpdates) {
// Вся информация о продукте в одном контексте
}
}
3. Используйте контрактное тестирование
// Тесты для проверки контрактов между контекстами
describe('Order-Inventory Contract', () => {
it('should respect inventory API contract', async () => {
const response = await inventoryService.checkAvailability([
{ productId: '123', quantity: 5 }
]);
expect(response).toHaveProperty('available');
expect(response).toHaveProperty('estimatedRestockDate');
});
});
Признаки правильных границ
✅ Каждый контекст имеет четкую ответственность
✅ Минимальная связанность между контекстами
✅ Изменения в одном контексте редко требуют изменений в других
✅ Команда может работать независимо внутри контекста
Заключение
Правильное определение Bounded Context — основа успешной микросервисной архитектуры. Это требует глубокого понимания бизнес-домена и тесного сотрудничества с экспертами предметной области.