Код IT
← Каталог

Паттерны проектирования — Пример — ACL для интеграции с legacy-бухгалтерией (C#)

Фрагмент из «Паттерны проектирования»: Пример — ACL для интеграции с legacy-бухгалтерией (C#).

csharp projectencyclopedia7-06-proektirovanie-i-arhitektura-115 embed URL статья в энциклопедии
C# main.cs
// 1. Транслятор: преобразование доменных объектов ↔ внешние форматы
internal class TaxRequestTranslator
{
    public string ToLegacyXml(TaxCalculationRequest request)
    {
        // Валидация и преобразование
        if (request.Amount <= 0) 
            throw new ArgumentException("Amount must be positive");
        
        return $@"<TaxRequest>
                    <Base>{request.Amount / (1 + request.TaxRate)}</Base>
                    <Rate>{request.TaxRate}</Rate>
                  </TaxRequest>";
    }

    public TaxCalculationResult FromLegacyXml(string xml)
    {
        var doc = XDocument.Parse(xml);
        var taxAmount = decimal.Parse(doc.Root.Element("TaxAmount").Value);
        // Внешняя система возвращает НДС отдельно — домен ожидает итог
        return new TaxCalculationResult(
            total: request.Amount + taxAmount, 
            tax: taxAmount
        );
    }
}

// 2. Адаптер + Защитный прокси
public class ResilientLegacyTaxAdapter : ITaxCalculator
{
    private readonly AccountingSystem _legacy;
    private readonly TaxRequestTranslator _translator;
    private readonly ILogger _logger;

    public ResilientLegacyTaxAdapter(
        AccountingSystem legacy,
        TaxRequestTranslator translator,
        ILogger<ResilientLegacyTaxAdapter> logger)
    {
        _legacy = legacy;
        _translator = translator;
        _logger = logger;
    }

    public async Task<TaxCalculationResult> Calculate(TaxCalculationRequest request)
    {
        // Circuit Breaker (через Polly)
        return await Policy
            .Handle<Exception>()
            .CircuitBreakerAsync(
                exceptionsAllowedBeforeBreaking: 3,
                durationOfBreak: TimeSpan.FromMinutes(1))
            .ExecuteAsync(async () => 
            {
                // Retry (тоже через Polly)
                return await Policy
                    .Handle<TimeoutException>()
                    .WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i)))
                    .ExecuteAsync(async () => 
                    {
                        try
                        {
                            var xmlInput = _translator.ToLegacyXml(request);
                            var xmlOutput = await Task.Run(() => 
                                _legacy.CalcTax(xmlInput), 
                                CancellationToken.None);
                            return _translator.FromLegacyXml(xmlOutput);
                        }
                        catch (Exception ex)
                        {
                            _logger.LogWarning(ex, "Legacy tax calculation failed");
                            throw;
                        }
                    });
            });
    }
}

// 3. Фасад для клиента
public class TaxCalculationService
{
    private readonly ITaxCalculator _calculator;
    public TaxCalculationService(ITaxCalculator calculator) => _calculator = calculator;

    // Доменный метод — не знает о legacy
    public async Task<OrderWithTaxes> ApplyTaxesToOrder(Order order)
    {
        var taxRequest = new TaxCalculationRequest(
            amount: order.Total,
            taxRate: order.Country.TaxRate);

        var result = await _calculator.Calculate(taxRequest);
        
        return order.WithTaxes(
            totalInclTax: result.Total,
            taxAmount: result.Tax);
    }
}
// 1. Транслятор: преобразование доменных объектов ↔ внешние форматы
internal class TaxRequestTranslator
{
    public string ToLegacyXml(TaxCalculationRequest request)
    {
        // Валидация и преобразование
        if (request.Amount <= 0) 
            throw new ArgumentException("Amount must be positive");
        
        return $@"<TaxRequest>
                    <Base>{request.Amount / (1 + request.TaxRate)}</Base>
                    <Rate>{request.TaxRate}</Rate>
                  </TaxRequest>";
    }

    public TaxCalculationResult FromLegacyXml(string xml)
    {
        var doc = XDocument.Parse(xml);
        var taxAmount = decimal.Parse(doc.Root.Element("TaxAmount").Value);
        // Внешняя система возвращает НДС отдельно — домен ожидает итог
        return new TaxCalculationResult(
            total: request.Amount + taxAmount, 
            tax: taxAmount
        );
    }
}

// 2. Адаптер + Защитный прокси
public class ResilientLegacyTaxAdapter : ITaxCalculator
{
    private readonly AccountingSystem _legacy;
    private readonly TaxRequestTranslator _translator;
    private readonly ILogger _logger;

    public ResilientLegacyTaxAdapter(
        AccountingSystem legacy,
        TaxRequestTranslator translator,
        ILogger<ResilientLegacyTaxAdapter> logger)
    {
        _legacy = legacy;
        _translator = translator;
        _logger = logger;
    }

    public async Task<TaxCalculationResult> Calculate(TaxCalculationRequest request)
    {
        // Circuit Breaker (через Polly)
        return await Policy
            .Handle<Exception>()
            .CircuitBreakerAsync(
                exceptionsAllowedBeforeBreaking: 3,
                durationOfBreak: TimeSpan.FromMinutes(1))
            .ExecuteAsync(async () => 
            {
                // Retry (тоже через Polly)
                return await Policy
                    .Handle<TimeoutException>()
                    .WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(Math.Pow(2, i)))
                    .ExecuteAsync(async () => 
                    {
                        try
                        {
                            var xmlInput = _translator.ToLegacyXml(request);
                            var xmlOutput = await Task.Run(() => 
                                _legacy.CalcTax(xmlInput), 
                                CancellationToken.None);
                            return _translator.FromLegacyXml(xmlOutput);
                        }
                        catch (Exception ex)
                        {
                            _logger.LogWarning(ex, "Legacy tax calculation failed");
                            throw;
                        }
                    });
            });
    }
}

// 3. Фасад для клиента
public class TaxCalculationService
{
    private readonly ITaxCalculator _calculator;
    public TaxCalculationService(ITaxCalculator calculator) => _calculator = calculator;

    // Доменный метод — не знает о legacy
    public async Task<OrderWithTaxes> ApplyTaxesToOrder(Order order)
    {
        var taxRequest = new TaxCalculationRequest(
            amount: order.Total,
            taxRate: order.Country.TaxRate);

        var result = await _calculator.Calculate(taxRequest);
        
        return order.WithTaxes(
            totalInclTax: result.Total,
            taxAmount: result.Tax);
    }
}