microservices

Определение доменной области (Bounded Context)

Как правильно определить границы микросервисов с помощью концепции Bounded Context из DDD

#microservices #ddd #bounded-context #domain-driven-design

Определение доменной области (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 — основа успешной микросервисной архитектуры. Это требует глубокого понимания бизнес-домена и тесного сотрудничества с экспертами предметной области.