Оволодіння шаблоном Фасад у C# .NET 8: Спрощення складних сценаріїв

У сучасному програмному забезпеченні складність виникає через інтеграцію кількох підсистем, таких як сторонні API, розподілені сервіси або застарілі системи. Пряме керування цими системами може призвести до незручного, схильного до помилок коду. Шаблон фасаду (Facade Pattern) допомагає впоратися з цією складністю, пропонуючи уніфікований та спрощений інтерфейс.

Ця стаття детальніше розглядає Шаблон фасаду (Facade Pattern) у C# .NET 8, демонструючи його потужність через розширені сценарії та покращення з використанням сучасних практик, таких як принципи SOLID, стійкість та впровадження залежностей (dependency injection).

Що таке Шаблон фасаду (Facade Pattern)?

Шаблон фасаду (Facade Pattern) — це структурний шаблон проектування, який надає високорівневий інтерфейс для приховування складнощів підсистем. Він особливо корисний для:

  • Спрощення використання: Клієнти взаємодіють з єдиним, злагодженим API.
  • Інкапсуляції: Внутрішні деталі підсистем приховані.
  • Підтримуваності: Зміни в підсистемах не впливають на клієнта.

Розширений сценарій: Єдина система комунікацій

Уявіть, що ви створюєте додаток, який інтегрується з:

  1. Службою сповіщень (наприклад, для надсилання SMS чи електронних листів).
  2. Платіжним шлюзом (наприклад, Stripe або PayPal).
  3. Системою виявлення шахрайства для перевірки транзакцій.

Пряма інтеграція з кожною службою створювала б тісно зв'язаний, складний у підтримці код. Натомість ми можемо використати фасад для керування цими взаємодіями.

Реалізація

1. Визначення підсистем

Кожна підсистема відповідає за конкретну задачу:

public interface INotificationService  
{  
 Task SendNotificationAsync(string message, string recipient);  
}  

public class NotificationService : INotificationService  
{  
 public async Task SendNotificationAsync(string message, string recipient)  
 {  
 // Симуляція надсилання сповіщення  
 await Task.Delay(500);  
 Console.WriteLine($"Сповіщення надіслано {recipient}: {message}");  
 }  
}  

public interface IPaymentGateway  
{  
 Task ProcessPaymentAsync(decimal amount, string cardNumber);  
}  

public class PaymentGateway : IPaymentGateway  
{  
 public async Task ProcessPaymentAsync(decimal amount, string cardNumber)  
 {  
 // Симуляція обробки платежу  
 await Task.Delay(1000);  
 Console.WriteLine($"Платіж на суму {amount:C} оброблено для картки: {cardNumber}");  
 return true;  
 }  
}  

public interface IFraudDetectionService  
{  
 Task ValidateTransactionAsync(decimal amount, string cardNumber);  
}  

public class FraudDetectionService : IFraudDetectionService  
{  
 public async Task ValidateTransactionAsync(decimal amount, string cardNumber)  
 {  
 // Симуляція виявлення шахрайства  
 await Task.Delay(700);  
 Console.WriteLine($"Транзакція підтверджена для картки: {cardNumber}");  
 return true;  
 }  
}

2.

Створення фасаду

Фасад спрощує оркестрацію цих служб:

public class UnifiedPaymentFacade  
{  
 private readonly INotificationService _notificationService;  
 private readonly IPaymentGateway _paymentGateway;  
 private readonly IFraudDetectionService _fraudDetectionService;  

 public UnifiedPaymentFacade(  
 INotificationService notificationService,  
 IPaymentGateway paymentGateway,  
 IFraudDetectionService fraudDetectionService)  
 {  
 _notificationService = notificationService ?? throw new ArgumentNullException(nameof(notificationService));  
 _paymentGateway = paymentGateway ?? throw new ArgumentNullException(nameof(paymentGateway));  
 _fraudDetectionService = fraudDetectionService ?? throw new ArgumentNullException(nameof(fraudDetectionService));  
 }  

 public async Task ExecuteTransactionAsync(decimal amount, string cardNumber, string recipient)  
 {  
 Console.WriteLine("Розпочато транзакцію...");  

 // Крок 1: Перевірка транзакції  
 if (!await _fraudDetectionService.ValidateTransactionAsync(amount, cardNumber))  
 {  
 Console.WriteLine("Транзакція позначена як шахрайська.");  
 return false;  
 }  

 // Крок 2: Обробка платежу  
 if (!await _paymentGateway.ProcessPaymentAsync(amount, cardNumber))  
 {  
 Console.WriteLine("Не вдалося обробити платіж.");  
 return false;  
 }  

 // Крок 3: Надсилання сповіщення  
 await _notificationService.SendNotificationAsync($"Ваш платіж на суму {amount:C} було успішно оброблено.", recipient);  

 Console.WriteLine("Транзакція завершена успішно.");  
 return true;  
 }  
}

Покращення та розширені практики

1. Додавання стійкості за допомогою Polly

Інтеграція механізмів повторних спроб для зовнішніх викликів, щоб елегантно обробляти тимчасові помилки.

using Polly;  

public class UnifiedPaymentFacade  
{  
 private readonly INotificationService _notificationService;  
 private readonly IPaymentGateway _paymentGateway;  
 private readonly IFraudDetectionService _fraudDetectionService;  
 private readonly IAsyncPolicy _retryPolicy;  

 public UnifiedPaymentFacade(  
 INotificationService notificationService,  
 IPaymentGateway paymentGateway,  
 IFraudDetectionService fraudDetectionService)  
 {  
 _notificationService = notificationService ?? throw new ArgumentNullException(nameof(notificationService));  
 _paymentGateway = paymentGateway ?? throw new ArgumentNullException(nameof(paymentGateway));  
 _fraudDetectionService = fraudDetectionService ?? throw new ArgumentNullException(nameof(fraudDetectionService));  

 _retryPolicy = Policy  
 .Handle()  
 .RetryAsync(3, (exception, retryCount) =>  
 {  
 Console.WriteLine($"Повторна спроба {retryCount} через: {exception.Message}");  
 });  
 }  

 public async Task ExecuteTransactionAsync(decimal amount, string cardNumber, string recipient)  
 {  
 return await _retryPolicy.ExecuteAsync(async () =>  
 {  
 Console.WriteLine("Розпочато транзакцію...");  

 // Крок 1: Перевірка транзакції  
 if (!await _fraudDetectionService.ValidateTransactionAsync(amount, cardNumber))  
 {  
 Console.WriteLine("Транзакція позначена як шахрайська.");  
 return false;  
 }  

 // Крок 2: Обробка платежу  
 if (!await _paymentGateway.ProcessPaymentAsync(amount, cardNumber))  
 {  
 Console.WriteLine("Не вдалося обробити платіж.");  
 return false;  
 }  

 // Крок 3: Надсилання сповіщення  
 await _notificationService.SendNotificationAsync($"Ваш платіж на суму {amount:C} було успішно оброблено.", recipient);  

 Console.WriteLine("Транзакція завершена успішно.");  
 return true;  
 });  
 }  
}

2.

Структуроване логування та спостережуваність

Додайте логування для покращення спостережуваності, використовуючи ILogger.

public class UnifiedPaymentFacade  
{  
 private readonly ILogger _logger;  
 private readonly INotificationService _notificationService;  
 private readonly IPaymentGateway _paymentGateway;  
 private readonly IFraudDetectionService _fraudDetectionService;  

 public UnifiedPaymentFacade(  
 INotificationService notificationService,  
 IPaymentGateway paymentGateway,  
 IFraudDetectionService fraudDetectionService,  
 ILogger logger)  
 {  
 _notificationService = notificationService ?? throw new ArgumentNullException(nameof(notificationService));  
 _paymentGateway = paymentGateway ?? throw new ArgumentNullException(nameof(paymentGateway));  
 _fraudDetectionService = fraudDetectionService ?? throw new ArgumentNullException(nameof(fraudDetectionService));  
 _logger = logger ?? throw new ArgumentNullException(nameof(logger));  
 }  

 public async Task ExecuteTransactionAsync(decimal amount, string cardNumber, string recipient)  
 {  
 _logger.LogInformation("Розпочато транзакцію...");  

 try  
 {  
 if (!await _fraudDetectionService.ValidateTransactionAsync(amount, cardNumber))  
 {  
 _logger.LogWarning("Транзакція позначена як шахрайська.");  
 return false;  
 }  

 if (!await _paymentGateway.ProcessPaymentAsync(amount, cardNumber))  
 {  
 _logger.LogError("Не вдалося обробити платіж.");  
 return false;  
 }  

 await _notificationService.SendNotificationAsync($"Ваш платіж на суму {amount:C} було успішно оброблено.", recipient);  

 _logger.LogInformation("Транзакція завершена успішно.");  
 return true;  
 }  
 catch (Exception ex)  
 {  
 _logger.LogError($"Транзакція не вдалася: {ex.Message}");  
 throw;  
 }  
 }  
}

3. Юніт-тестування з мокінгом

Використовуйте Moq для тестування фасаду без залежності від реальних реалізацій.

public class UnifiedPaymentFacadeTests  
{  
 [Fact]  
 public async Task ExecuteTransactionAsync_ShouldCompleteSuccessfully()  
 {  
 // Підготовка  
 var notificationMock = new Mock<INotificationService>();  
 var paymentMock = new Mock<IPaymentGateway>();  
 var fraudMock = new Mock<IFraudDetectionService>();  
 var loggerMock = new Mock<ILogger>();  

 fraudMock.Setup(f => f.ValidateTransactionAsync(It.IsAny<decimal>(), It.IsAny<string>())).ReturnsAsync(true);  
 paymentMock.Setup(p => p.ProcessPaymentAsync(It.IsAny<decimal>(), It.IsAny<string>())).ReturnsAsync(true);  
 notificationMock.Setup(n => n.SendNotificationAsync(It.IsAny<string>(), It.IsAny<string>())).Returns(Task.CompletedTask);  

 var facade = new UnifiedPaymentFacade(notificationMock.Object, paymentMock.Object, fraudMock.Object, loggerMock.Object);  

 // Дія  
 var result = await facade.ExecuteTransactionAsync(100.00m, "1234-5678-9876-5432", "[email protected]");  

 // Перевірка  
 Assert.True(result);  
 fraudMock.Verify(f => f.ValidateTransactionAsync(It.IsAny<decimal>(), It.IsAny<string>()), Times.Once);  
 paymentMock.Verify(p => p.ProcessPaymentAsync(It.IsAny<decimal>(), It.IsAny<string>()), Times.Once);  
 notificationMock.Verify(n => n.SendNotificationAsync(It.IsAny<string>(), It.IsAny<string>()), Times.Once);  
 }  
}

Висновок

Шаблон фасаду є потужним інструментом для управління складністю, особливо в сценаріях, що включають кілька підсистем. Інтегруючи стійкість, структуроване логування та тестування, ви можете створювати надійні та масштабовані рішення.

Використовуйте цей шаблон для створення систем, що легко обслуговуються, спрощуючи складність без втрати функціональності.

Поділіться, як ви використовували шаблон фасаду у своїх проектах! 🚀

Перекладено з: Mastering the Facade Pattern in C# .NET 8: Simplifying Advanced Scenarios

Leave a Reply

Your email address will not be published. Required fields are marked *