Flexible Design with Adapters
      March 17, 2018
      
   
      elixir
      
      patterns
        
    
  With Phoenix 1.3 well behind us now, Elixir developers understand how to approach most problems “The Elixir Way”:
- Create a well-named context module for the problem, e.g. “Payment”
 - Put functions in it, which don’t expose how data is persisted
 - Profit
 
This is great, but it can be taken even further. A Payment
module is a great example, in fact. Many developers1 would scan the
landscape for the best payment gateway out there2, find its companion
library in Hex, and build their whole Payment module around its API. They
would use the gateway library’s structs throughout their application, and
name fields in the database using gateway-specific terms.
This gets the job done, but it can be shortsighted. Eventually, the stakeholders always want to do crazy things like:
- Switch payment gateways (“Authorize.net gave us a better deal!”)
 - Route some payments through gateway A and others through gateway B
 
This kind of change is very common whenever the third party service you’re integrating is generic and commoditized, i.e. offered by more than one vendor. For example:
- File uploading and storage
 - Image manipulation
 - Payment processing
 - SMS delivery
 - Email delivery
 - Content-delivery networks
 - Analytics
 - Performance & error monitoring
 
If your app isn’t designed to anticipate this kind of change, you’ll be caught flatfooted whenever you need to change vendors for any of these services.
Introducing Adapters
In Elixir, the best way to insulate your app from vendor changes is to
isolate your vendor-specific code into Adapter modules.
Before you get to that though, you’d first define app-specific structs for
your problem space. For example, you might create a MyApp.Payment.Charge
struct to represent credit card charges.
You’d use these structs exclusively throughout your app, never depending on vendor-specific structs or types.
%MyApp.Payment.Charge{
  gateway_id: "[id of charge on gateway]",
  amount_cents: 1_000,
  currency: "usd",
  # ...
}
Next, you’d identify which features you need from your vendor, and codify
those into an Adapter behaviour. In this case, Gateway seems to be a good
name.
defmodule MyApp.Payment.Gateway do
  alias MyApp.Payment.{Charge, GatewayError}
  @callback create_charge(Charge.t) :: {:ok, Charge.t} | {:error, GatewayError.t}
end
You’d then implement that behaviour for your chosen vendor.
defmodule MyApp.Payment.StripeGateway do
  @behaviour MyApp.Payment.Gateway
  @impl true
  def create_charge(charge) do
    # ...
  end
end
Inside your context, when you need to make a call to your vendor, you fetch
an Adapter module from configuration and call it:
defmodule MyApp.Payment do
  def create_charge(attrs \\ %{}) do
    %MyApp.Payment.Charge{}
    |> struct(attrs)
    |> gateway().create_charge()
  end
  defp gateway do
    :my_app
    |> Application.get_env(__MODULE__, [])
    |> Keyword.fetch!(:gateway)
  end
end
Finally, you’d configure your context to use the desired Adapter.
config :my_app, MyApp.Payment,
  gateway: MyApp.Payment.StripeGateway
Congratulations, you now have an adapter-based design!
Benefits
Now that your vendor is isolated behind a behaviour, you can much more easily support multiple vendors. Beyond that, you also get a major benefit in your tests.
You can now easily implement a Test implementation of your Adapter which
you only use in the :test environment. This allows you to thoroughly test
all the possible error states, without having to make live API calls. This is
particularly nice when your vendor doesn’t provide a good “test” version of
their API.
Examples
In conclusion, here are some stand-out libraries on Hex which implement the pattern if you want to see it in action: