Writing Clients in Go: Redundancy and the
Over the past few weeks, I’ve built a few clients in Go for accessing various internal and external APIs. I’ve noticed a few patterns emerging from the natural maturity of these clients, some of which I’ve also seen in open source clients from major providers, like Stripe and AWS. This is the first part in a series about various patterns with client written in Go, and is probably applicable to many other languages.
Redundancy and the
The first pattern is to offer an interface that represents the base “client”. This interface is concretely implemented by other “real” clients. The interface proves useful for allowing multiple implementation of the client.
For example, if you’re going to use a location service, like Google Maps, Here, Bing Maps, etc., you’ll probably want to opt into using something near intersection of “cheap” and “reliable” in the matrix.
Google Maps is not cheap, but Bing Maps is. So you write a client for Bing Maps that returns to us the
DriveTime() between a list of locations at a time and day in the future. You wrap those data points into a
Which implements the location client interface:
But the Bing Maps only provides so many 9’s of uptime (for example) in the Freemium SLA, and it happens to be less than Google Map’s SLA. So you build another
Client that implements
locationiface for Google Maps, which you use as a backup.
Wouldn’t it be nice if you could easily call Google Maps, only if Bing Maps fails?
So, you build a third client. Let’s call it the
Client, that implements
locationiface and provides the desired redundancy:
From your application, you can make a single call to the
client.DriveTime method from of a
location.Client, which you constructed out of
Similarity to the AWS Go SDK⌗
If you’re familiar with the AWS Go SDK, you may recognize this naming scheme and pattern. AWS provides
*iface packages for many of their popular services, like DynamoDB.
These “interface packages” also commonly used to mock out a client or service.
iface package can be useful for enabling a “wrapper” client, which can provide redundancy while keeping thing simple when writing clients that use (possibly unreliable) external APIs.
In the next part, I’ll discuss breaking clients into their own packages based not on the external endpoint, but on their function.