Заглушка сервісу (шаблон проєктування)
Заглушка сервісу (англ. Service Stub) — шаблон проєктування, який дозволяє позбутись залежностей від зовнішніх компонентів.
Опис
Часто система залежить від зовнішніх сервісів. Зовнішні сервіси можуть створювати неочікувані проблеми, їх не можна контролювати та підтримувати. Ненадійність зовнішніх ресурсів викликає проблеми в самій аплікації. При цьому розробка уповільнюється також через неможливість тестувати логіку аплікації.
Рішенням буде робити залежність не на зовнішній сервіс, а на абстракцію. А при потребі (у разі несправності сервісу, чи тестуванні) використовувати заглушку, цього сервісу, яка виконується локально.
Реалізація
Нехай, наша аплікація містить операції із конвертуванням грошей. Тоді нам необхідний сервіс, який повертатиме курс валют на цю мить. Представимо таку залежність у вигляді абстракцію.
public interface ICurrencyExchange
{
Task<Money> Convert(Money originalAmount, Currency destinationCurrency);
}
Тоді реалізуємо інтерфейси використовуючи необхідний сервіс.
public sealed class CurrencyExchangeService : ICurrencyExchange
{
private const Uri _exchangeUrl = new Uri("https://api.exchangeratesapi.io/latest?base=USD");
private readonly IHttpClient _httpClient;
public CurrencyExchangeService(IHttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<Money> Convert(Money originalAmount, Currency destinationCurrency)
{
var response = await _httpClient.GetAsync(_exchangeUrl);
var usdRates = await response.ReadAsAsync<CurrencyMap>();
decimal usdAmount = usdRates[originalAmount.Currency] / originalAmount.Amount;
decimal destinationAmount = usdRates[destinationCurrency] / usdAmount;
return new Money(destinationAmount, destinationCurrency);
}
}
Та щоб уникнути залежності на зовнішній ресурс, напишемо заглушку, яку будемо використовувати при тестах.
public sealed class CurrencyExchangeStub : ICurrencyExchange
{
private readonly Dictionary<Currency, decimal> _usdRates = new Dictionary<Currency, decimal>
{
[Currency.Dollar] = 1m,
[Currency.Euro] = 0.89021m,
};
public Task<Money> Convert(Money originalAmount, Currency destinationCurrency)
{
decimal usdAmount = this._usdRates[originalAmount.Currency] / originalAmount.Amount;
decimal destinationAmount = this._usdRates[destinationCurrency] / usdAmount;
var money = new Money(destinationAmount, destinationCurrency)
return Task.FromResult(money);
}
}
Зв'язок з іншими патернами
- Макет об'єкта та заглушка сервісу часто плутають. Та варто розуміти, що макет об'єкта використовуються для імітації поведінки однієї чи декількох функцій та залежно від умов ця імітація може відрізнятись в той час, як заглушка сервісу замінює цілий сервіс та його реалізація залишається незмінною.