Итак, я создаю клиент для третьего API и хочу иметь возможность Get()
и GetMany()
для каждого типа, который может предоставить этот контроллер.
Поэтому я создал этот адаптер, который предоставляет для них методы общего типа.
public interface IClientAdapter<T>
{
public Task<T?> Get(string predicates = "");
public Task<List<T>?> GetMany(string predicates = "");
}
И это основной интерфейс реального клиента.
public interface IBasicSpaceDataClient
{
public IClientAdapter<Announcement> Announcements { get; }
public IClientAdapter<BoxScore> BoxScores { get; }
public IClientAdapter<Conjunction> Conjunctions { get; }
public IClientAdapter<Decay> Decays { get; }
public IClientAdapter<GeneralPerturbation> GeneralPerturbations { get; }
public IClientAdapter<GeneralPerturbation> GeneralPerturbationHistory { get; }
public IClientAdapter<LaunchSite> LaunchSites { get; }
public IClientAdapter<SatCatChange> SatCatChanges { get; }
public IClientAdapter<SatCatEntry> SatCatEntries { get; }
public IClientAdapter<SatCatEntry> SatCatDebuts { get; }
public IClientAdapter<TrackingAndImpactPrediction> TrackingAndImpactPredictions { get; }
}
Это означает, что вы можете сделать:
List<Announcement> announcements = await myClient.Announcements.GetMany();
Что я думаю, довольно приятно.
С точки зрения фактической реализации адаптер выглядит так:
internal class HttpClientAdapter<T>: IClientAdapter<T>
{
private readonly HttpClient _client;
private readonly string _endpoint;
public HttpClientAdapter(HttpClient client, string endpoint)
{
_client = client;
_endpoint = endpoint;
}
public async Task<T?> Get(string predicates)
{
T[]? arrayOfOne = await _client.GetFromJsonAsync<T[]>($"{_endpoint}/limit/1/{predicates}");
return arrayOfOne is null ? default : arrayOfOne.Single();
}
public async Task<List<T>?> GetMany(string predicates) => await _client.GetFromJsonAsync<List<T>>($"{_endpoint}/{predicates}");
}
И все это работает достаточно хорошо.
То, в чем я не уверен, закрадывается в конструктор конкретного клиента BasicSpaceData
.
public BasicSpaceDataClient(HttpClient client) {
Announcements = new HttpClientAdapter<Announcement>(client, $"{BasicSpaceDataEndpoint}announcement");
BoxScores = new HttpClientAdapter<BoxScore>(client, $"{BasicSpaceDataEndpoint}boxscore");
Conjunctions = new HttpClientAdapter<Conjunction>(client, $"{BasicSpaceDataEndpoint}cdm_public");
Decays = new HttpClientAdapter<Decay>(client, $"{BasicSpaceDataEndpoint}decay");
GeneralPerturbations = new HttpClientAdapter<GeneralPerturbation>(client, $"{BasicSpaceDataEndpoint}gp");
GeneralPerturbationHistory = new HttpClientAdapter<GeneralPerturbation>(client, $"{BasicSpaceDataEndpoint}gp_history");
LaunchSites = new HttpClientAdapter<LaunchSite>(client, $"{BasicSpaceDataEndpoint}launch_site");
SatCatChanges = new HttpClientAdapter<SatCatChange>(client, $"{BasicSpaceDataEndpoint}satcat_change");
SatCatEntries = new HttpClientAdapter<SatCatEntry>(client, $"{BasicSpaceDataEndpoint}satcat");
SatCatDebuts = new HttpClientAdapter<SatCatEntry>(client, $"{BasicSpaceDataEndpoint}satcat_debut");
TrackingAndImpactPredictions = new HttpClientAdapter<TrackingAndImpactPrediction>(client, $"{BasicSpaceDataEndpoint}tip");
}
В настоящее время все адаптеры совместно используют один клиент (что, я думаю, имеет смысл, учитывая, что все они указывают на один контроллер на стороне сервера), и все они обновляются в конструкторе клиента.
Это вторая часть, в которой я не уверен. С одной стороны, это очень простые объекты, и вся функциональность реализуется за счет внедрения HttpClient
. С другой стороны, такого рода вещи всегда заставляют меня задуматься о том, не лучше ли им сделать инъекцию. Обычно я выбираю последнее, но в этом случае мне интересно, есть ли в этом какая-то польза, и все, что это может сделать, это увеличить сложность моей настройки DI.
Что думают люди по этому поводу?
Как только одна из конечных точек будет отличаться каким-либо образом, вы потеряете свои сбережения в плане LoC
Если дать класс клиента для некоторого API, я автоматически предположу, что все методы являются конечными точками того же API. Однако если мне придется инжектировать каждый клиент ресурса... Возникает вопрос, указывают ли они все на разные API. Если я вызываю AddAddress("Glasgow"), могу ли я позже вызвать GetCity("Glasgow")?
Итак. Нет, я бы не стал вводить клиентов ресурса. Плюс! Если бы не было много ресурсов с точно таким же шаблоном конечной точки по какой-то причине, я бы имел клиентов ресурсов с жестко закодированными методами, а не использовал дженерик.
Меня беспокоит то, что, сделав этот общий (в обоих смыслах) подход, я столкнусь с различными конечными точками ресурсов или требованиями, которые не будут соответствовать шаблону, и буду склоняться к дальнейшему усложнению общего случая, чтобы справиться с ними, вместо того чтобы отказаться от него и сделать специальные случаи.
Опасность заключается в том, что в итоге получится слишком сложное решение с большим количеством LoC, чем простое решение с жестким кодированием каждой конечной точки.