So I'm building a client for a third API and I want to be able to Get()
and GetMany()
for each type this controller can provide.
So I've built this adapter, that provides generically typed methods for these.
public interface IClientAdapter<T>
{
public Task<T?> Get(string predicates = "");
public Task<List<T>?> GetMany(string predicates = "");
}
And this is the primary interface of the actual client.
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; }
}
This means that you can do:
List<Announcement> announcements = await myClient.Announcements.GetMany();
Which I think is quite nice.
In terms of actual implementation, the adapter looks like this:
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}");
}
And all of this works quite well.
The thing I'm not sure about creeps in in the BasicSpaceData
concrete client constructor.
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");
}
Currently, all adapters share one client (which I think makes sense given they're all pointing at one controller server-side) and they're all newed up in the client constructor.
It's that second part I'm not so sure about. On the one hand, they're very simple objects and all the functionality comes through the injected HttpClient
. On the other hand, this sort of thing always makes me think about whether they'd be better off injected. Usually I'd opt with the latter, but in this case, I'm wondering if there's actually any benefit to this and all it would do would be increase the complexity of my DI setup.
What are people's thoughts on this?