Adapters 101

Sometimes people get confused as to what is the roles of adapters, how to use them,
how to test them and how to configure them. Misunderstanging often comes from lack
of examples so let’s see some of them.

Our example will be about sending apple push notifications (APNS). Let’s say in our
system we are sending push notifications with text (alert) only
(no sound, no badge, etc). Very simple and basic usecase. One more thing that we
obviously need as well is device token. Let’s have a simple interface for sending
push notifications.

defnotify(device_token,text)end

That’s the interface that every one of our adapters will have to follow. So let’s
write our first implementation using the apns gem.

We’ve protected ourselves from the dependency on apns gem. We are still using it
but no part of our code is calling it directly. We are free to change it later
(which we will do)

We’ve isolated our interface from the implementation as Clean Code architecture
teaches us. Of course in Ruby we don’t have interfaces so it is kind-of virtual
but we can make it a bit more explicit, which I will show you how, later.

We designed API that we like and which is suitable for our app. Gems and 3rd party
services often offer your a lot of features which you might not be even using.
So here we explicitly state that we only use device_token and text. If it ever
comes to dropping the old library or migrating to new solution, you are coverd.
It’s simpler process when the cooperation can be easily seen in one place
(adapter). Evaluating and estimating such task is faster when you know exactly
what features you are using and what not.

Adapters in real life

As you can imagine looking at the images, the situation is always the same. We’ve got to parts with
incompatible interfaces and adapter mediating between them.

Adapters and architecture

Part of your app (probably a service) that we call client
is relying on some kind of interface for its proper behavior.
Of course ruby does not have explicit interfaces so what I mean is a
compatibility in a duck-typing way. Implicit interface defined by how we
call our methods (what parameters they take and what they return). There is
a component, an already existing one (adaptee) that can do the job our client wants but
does not expose the interface that we would like to use. The mediator between
these two is our adapter.

The interface can be fulfilled by possibily many adapters. They might be wrapping
another API or gem which we don’t want our app to interact directly with.

Multiple Adapters

Let’s move further with our task.

We don’t wanna be sending any push notifications from our development environment and
from our test environment. What are our options? I don’t like putting code such as
if Rails.env.test? || Rails.env.production? into my codebase. It makes testing
as well as playing with the application in development mode harder. For such usecases new
adapter is handy.

I like this more then using doubles and expectations because of its simplicity.
But using mocking techniques here would be apropriate as well. In that case
however I would recommend using Verifying doubles
from Rspec or to go with bogus. I recommend watching great video about
possible problems that mocks and doubles introduce from the author of bogus and
solutions for them. Integration tests are bogus.

Injecting and configuring adapters

Ok, so we have two adapters, how do we provide them to those who need these adapters to work?
Well, I’m gonna show you an example and not talk much about it because it’s going to be a topic
of another blogpos.

Did you notice that HoneyBadger is not hidden behind adapter? Bad code, bad code… ;)

What do we have now?

The result

We separated our interface from the implementations. Of course our interface is
not defined (again, Ruby) but we can describe it later using tests. App with the
interface it dependend is one component. Every implementation can be a separate
component.

Our goal here was to get closer to
Clean Architecture .
Use Cases (Interactors, Service Objects) are no longer bothered with implementation details. Instead they relay
on the interface and accept any implementation that is consistent with it.

The part of application which responsibility is to put everything in motion is called
Main by Uncle Bob. We put all the puzzles together by using Injectors and
Rails configuration. They define how to construct the working objects.

Changing underlying gem

In reality I no longer use apns gem because of its global configuration. I
prefer grocer because I can more easily and safely use it to send push
notifications to 2 separate mobile apps or even same iOS app but built with
either production or development APNS certificate.

So let’s say that our project evolved and now we need to be able to send push
notifications to 2 separate mobile apps. First we can refactor the interface of
our adapter to:

defnotify(device_token,text,app_name)end

Then we can change the implementation of our Sync adapter to use grocer gem
instead (we need some tweeks to the other implementations as well).
In simplest version it can be:

However every new grocer instance is using new conncetion to Apple push
notifications service. But, the recommended way is to reuse the connection.
This can be especially usefull if you are using sidekiq. In such case every
thread can have its own connection to apple for every app that you need
to support. This makes sending the notifications very fast.

In this implementation we kill the grocer instance when exception happens (might happen
because of problems with delivery, connection that was unused for a long time, etc).
We also reraise the exception so that higher layer (probably sidekiq or resque) know
that the task failed (and can schedule it again).

Adapters configuration

The downside of that is that the instance of adapter is global. Which means you might
need to take care of it being thread-safe (if you use threads). And you must
take great care of its state. So calling it multiple times between requests is
ok. The alternative is to use proc as factory for creating instances of your adapter.

If your adapter itself needs some dependencies consider using factories or injectors
for fully building it. From my experience adapters usually can be constructed quite
simply. And they are building blocks for other, more complicated structures like
service objects.

Testing adapters

I like to verify the interface of my adapters using shared examples in rspec.

shared_examples_for:apns_adapterdospecify"#notify"doexpect(adapter.method(:notify).arity).toeq(2)end# another way without even constructing instancespecify"#notify"doexpect(described_class.instance_method(:notify).arity).toeq(2)endend

For the rest of the test you must write something specific to the adapter implementation.
Adapters doing http request can either stub http communication
with webmock
or vcr. Alternatively, you can just use mocks and expectations to check,
whether the gem that you use for communication is being use correctly. However,
if the logic is not complicated the test are quickly becoming typo test,
so they might even not be worth writing.

In many cases I don’t think you should test Fake adapter because this is what we use for
testing. And testing the code intended for testing might be too much.

Dealing with exceptions

Because we don’t want our app to be bothered with adapter implementation
(our clients don’t care about anything except for the interface) our
adapters need to throw the same exceptions. Because what exceptions are raised
is part of the interface. This example does not suite us well to discuss it
here because we use our adapters in fire and forget mode. So we will have
to switch for a moment to something else.

Imagine that we are using some kind of geolocation service which based on
user provided address (not a specific format, just String from one text input)
can tell us the longitude and latitude coordinates of the location. We are in
the middle of switching to another provided which seems to provide better data
for the places that our customers talk about. Or is simply cheaper. So we have
two adapters. Both of them communicate via HTTP with APIs exposed by our
providers. But both of them use separate gems for that. As you can easily imagine
when anything goes wrong, gems are throwing their own custom exceptions. We need
to catch them and throw exceptions which our clients/services except to catch.

But as I said I don’t like this approach. The problem is that if you want to
communicate something domain specific via the exception you can’t relay on 3rd
party exceptions. If it was adapter responsibility to provide in exception
information whether service should retry later or give up, then you need custom
exception to communicate it.

Adapters ain’t easy

There are few problems with adapters. Their interface tends to be
lowest common denominator between features supported by implementations.
That was the reason which sparkled big discussion about queue interface for
Rails which at that time was removed from it. If one technology limits you so
you schedule background job only with JSON compatibile attributes you are
limited to just that. If another technology let’s you use Hashes with every
Ruby primitive and yet another would even allow you to pass whatever ruby object
you wish then the interface is still whatever JSON allows you to do. No only
you won’t be able to easily pass instance of your custom class as paramter for
scheduled job. You won’t even be able to use Date class because there is no
such type in JSON. Lowest Common Denominator…

You won’t easily extract Async adapter if you care about the result. I think that’s
obvious. You can’t easily substitute adapter which can return result with such
that cannot. Async is architectural decision here. And rest of the code must be
written in a way that reflects it. Thus expecting to get the result somehow later.

Getting the right level of abstraction for adapter might not be easy. When you cover
api or a gem, it’s not that hard. But once you start doing things like
NotificationAdapter which will let you send notification to user without bothering
the client whether it is a push for iOS, Android, Email or SMS, you might find yourself in
trouble. The closer the adapter is to the domain of adaptee, the easier it is to
write it. The closer it is to the domain of the client, of your app, the harder it
is, the more it will know about your usecases. And the more complicated and
unique for the app, such adapter will be. You will often stop for a moment to reflect
whether given functionality is the responsibility of the client, adapter or maybe
yet another object.

Summary

Adapters are puzzles that we put between our domain and existing solutions such
as gems, libraries, APIs. Use them wisely to decouple core of your app from 3rd party code
for whatever reason you have. Speed, Readability, Testability, Isolation,
Interchangeability.