microservices

Anti-Corruption Layer и маршрутизация

Защита доменной модели от внешних систем с помощью Anti-Corruption Layer

#microservices #patterns #ddd #integration

Anti-Corruption Layer и маршрутизация

Anti-Corruption Layer (ACL) — защитный слой, который изолирует доменную модель от внешних систем и предотвращает “загрязнение” вашей архитектуры чужими концепциями.

Зачем нужен ACL?

Проблема без ACL

// Плохо: прямая зависимость от внешней системы
class OrderService {
  async createOrder(customerId: string) {
    // Используем модель legacy системы напрямую
    const legacyCustomer = await legacyAPI.getCustomer(customerId);
    
    const order = new Order({
      // Вынуждены использовать структуру legacy системы
      customerName: legacyCustomer.CUST_NAME,
      customerAddress: legacyCustomer.ADDR_LINE_1 + ' ' + legacyCustomer.ADDR_LINE_2,
      customerType: legacyCustomer.TYPE_CD === 'P' ? 'Premium' : 'Regular'
    });
  }
}

Решение с ACL

// Хорошо: изолируем через адаптер
class LegacyCustomerAdapter {
  async getCustomer(customerId: string): Promise<Customer> {
    const legacyData = await this.legacyAPI.getCustomer(customerId);
    return this.translate(legacyData);
  }
  
  private translate(legacy: LegacyCustomer): Customer {
    return {
      id: legacy.CUST_ID,
      name: legacy.CUST_NAME,
      address: new Address({
        street: `${legacy.ADDR_LINE_1} ${legacy.ADDR_LINE_2}`,
        city: legacy.CITY,
        zipCode: legacy.ZIP
      }),
      tier: this.mapCustomerTier(legacy.TYPE_CD)
    };
  }
  
  private mapCustomerTier(typeCode: string): CustomerTier {
    const mapping: Record<string, CustomerTier> = {
      'P': CustomerTier.Premium,
      'R': CustomerTier.Regular,
      'B': CustomerTier.Basic
    };
    return mapping[typeCode] || CustomerTier.Regular;
  }
}

class OrderService {
  constructor(private customerAdapter: LegacyCustomerAdapter) {}
  
  async createOrder(customerId: string) {
    // Работаем с нашей чистой моделью
    const customer = await this.customerAdapter.getCustomer(customerId);
    const order = new Order(customer);
  }
}

Компоненты ACL

1. Адаптер (Adapter)

Преобразует интерфейс внешней системы:

interface PaymentGateway {
  processPayment(payment: Payment): Promise<PaymentResult>;
}

class StripeAdapter implements PaymentGateway {
  constructor(private stripe: Stripe) {}
  
  async processPayment(payment: Payment): Promise<PaymentResult> {
    const stripePayment = {
      amount: payment.amount.cents,
      currency: payment.amount.currency.toLowerCase(),
      source: payment.token,
      description: payment.description
    };
    
    const result = await this.stripe.charges.create(stripePayment);
    
    return {
      success: result.status === 'succeeded',
      transactionId: result.id,
      timestamp: new Date(result.created * 1000)
    };
  }
}

2. Фасад (Facade)

Упрощает сложный API:

class LegacySystemFacade {
  constructor(
    private authService: LegacyAuthService,
    private dataService: LegacyDataService,
    private validationService: LegacyValidationService
  ) {}
  
  async getValidatedCustomerData(customerId: string): Promise<Customer> {
    // Скрываем сложность взаимодействия с legacy системой
    const token = await this.authService.authenticate();
    const rawData = await this.dataService.fetchCustomer(customerId, token);
    const validated = await this.validationService.validate(rawData);
    
    return this.mapToCustomer(validated);
  }
}

3. Транслятор (Translator)

Преобразует модели данных:

class ProductTranslator {
  toExternal(product: Product): ExternalProduct {
    return {
      product_id: product.id,
      product_name: product.name,
      price_amount: product.price.amount,
      price_currency: product.price.currency,
      category_code: this.getCategoryCode(product.category)
    };
  }
  
  fromExternal(external: ExternalProduct): Product {
    return new Product({
      id: external.product_id,
      name: external.product_name,
      price: new Money(external.price_amount, external.price_currency),
      category: this.getCategory(external.category_code)
    });
  }
  
  private getCategoryCode(category: Category): string {
    // Маппинг между нашей и внешней моделью
  }
}

Маршрутизация через ACL

API Gateway с ACL

class APIGatewayWithACL {
  private adapters: Map<string, ServiceAdapter> = new Map();
  
  constructor() {
    this.registerAdapters();
  }
  
  private registerAdapters() {
    this.adapters.set('legacy-crm', new LegacyCRMAdapter());
    this.adapters.set('modern-api', new ModernAPIAdapter());
    this.adapters.set('third-party', new ThirdPartyAdapter());
  }
  
  async routeRequest(request: Request): Promise<Response> {
    const service = this.determineService(request);
    const adapter = this.adapters.get(service);
    
    if (!adapter) {
      throw new Error(`No adapter found for service: ${service}`);
    }
    
    // Адаптер изолирует нас от деталей внешней системы
    return await adapter.handle(request);
  }
}

Умная маршрутизация

class IntelligentRouter {
  async route(request: ServiceRequest): Promise<Response> {
    // Выбираем лучший источник данных
    const sources = this.getAvailableSources(request.dataType);
    
    for (const source of sources) {
      try {
        const adapter = this.getAdapter(source);
        const response = await adapter.fetch(request);
        
        if (this.isValidResponse(response)) {
          return response;
        }
      } catch (error) {
        this.logger.warn(`Source ${source} failed, trying next`, error);
        continue;
      }
    }
    
    throw new Error('All sources failed');
  }
  
  private getAvailableSources(dataType: string): string[] {
    // Приоритизируем источники
    return ['primary-db', 'cache', 'legacy-system', 'external-api'];
  }
}

Обработка несовместимостей

Версионирование

class VersionedAdapter {
  async getData(id: string, version: string): Promise<Data> {
    switch (version) {
      case 'v1':
        return this.getV1Data(id);
      case 'v2':
        return this.getV2Data(id);
      default:
        return this.getLatestData(id);
    }
  }
  
  private async getV1Data(id: string): Promise<Data> {
    const legacy = await this.legacyAPI.fetch(id);
    return this.translateV1(legacy);
  }
  
  private async getV2Data(id: string): Promise<Data> {
    const modern = await this.modernAPI.fetch(id);
    return this.translateV2(modern);
  }
}

Обогащение данных

class EnrichingAdapter {
  async getEnrichedCustomer(customerId: string): Promise<Customer> {
    // Получаем базовые данные из legacy системы
    const basicData = await this.legacyAdapter.getCustomer(customerId);
    
    // Обогащаем данными из других источников
    const [preferences, orderHistory, loyaltyPoints] = await Promise.all([
      this.preferencesService.get(customerId),
      this.orderService.getHistory(customerId),
      this.loyaltyService.getPoints(customerId)
    ]);
    
    return {
      ...basicData,
      preferences,
      orderHistory,
      loyaltyPoints
    };
  }
}

Best Practices

1. Изолируйте изменения

// Изменения во внешней системе не влияют на домен
class ResilientAdapter {
  async fetchData(id: string): Promise<DomainData> {
    try {
      const external = await this.externalAPI.get(id);
      return this.translate(external);
    } catch (error) {
      if (this.isSchemaChange(error)) {
        // Логируем и используем fallback
        this.logger.error('External schema changed', error);
        return this.getFallbackData(id);
      }
      throw error;
    }
  }
}

2. Кэшируйте переводы

class CachedTranslator {
  private cache = new Map<string, DomainModel>();
  
  async translate(external: ExternalModel): Promise<DomainModel> {
    const cacheKey = this.getCacheKey(external);
    
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey)!;
    }
    
    const translated = this.performTranslation(external);
    this.cache.set(cacheKey, translated);
    return translated;
  }
}

3. Тестируйте адаптеры

describe('LegacyCustomerAdapter', () => {
  it('should translate legacy format to domain model', () => {
    const legacy = {
      CUST_ID: '123',
      CUST_NAME: 'John Doe',
      TYPE_CD: 'P'
    };
    
    const customer = adapter.translate(legacy);
    
    expect(customer.id).toBe('123');
    expect(customer.name).toBe('John Doe');
    expect(customer.tier).toBe(CustomerTier.Premium);
  });
});

Заключение

Anti-Corruption Layer — критически важный паттерн для защиты вашей доменной модели от влияния внешних систем. Он обеспечивает гибкость, изолирует изменения и поддерживает чистоту архитектуры.