API Client Design Patterns I Learned
Over the past couple of years as I've been making API clients to integrate with various services for my own needs, I settled on a design pattern that was inspired from the AWS SDK clients. Every operation is its own self contained unit that has an object describing the request. Depending on the complexity of the request there may be some helper methods for common operations. For example, using the Geocod.io client:
var response = await geocodio.GetGeocodeAsync(
"1600 Pennsylvania Ave NW, Washington, DC 20500"
);
...and:
var response = await geocodio.GetGeocodeAsync(new GeocodeRequest {
Address = "1600 Pennsylvania Ave NW, Washington, DC 20500"
});
...do the same request. In fact the first one calls into the second one. This way I can perform a quick simple call or if I need to I can build out a more complex request and pass it through.
In the Kraken.io client, I would probably use custom requests more often depending on my optimization needs because there's so many options that can be configured on how the image should be optimized. Creating a helper method for all those options is simply unpractical and a waste of time.
The same pattern can be applied to the client constructor as well, for example:
public AbcClient(
string key);
...and:
public AbcClient(new AbcClientOptions {
Key = "abc"
});
...would create the same instance. I haven't had to use constructor overloads like this yet because the APIs I've integrated with weren't complex enough to warrant it, but the possibility remains. I've yet to find an API where this pattern is not applicable.
Granted, people that spend their time writing API clients, or software really, probably know this already and this post would seem silly to them, but maybe you don't and I hope this helps you settle on a pattern for the API client you might be developing.