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