rails
The Gateway Pattern
Use gateway classes to organize external API integrations and establish a clearer separation of concerns.
The problem
When building a Rails app, chances are you’ll have to integrate with one or more external APIs: processing payments, sending push notifications, and firing analytics events are just a few examples.
At first, these integrations might be one-liners sprinkled into controllers or model callbacks. But as your app gets more complex, the code that “glues” your app with various external APIs can become significant.
Furthermore, if you’re not careful, the lines between your app and an external API can start to blur in confusing ways. For example, if your app’s concept of a “subscription” is subtly different from what your payment processor defines as a “subscription”, mixing these mismatched concepts within your model or controller code can make things hard to follow.
Solution: the gateway pattern
Simply put, a gateway is a class that wraps an external API. Its job is to translate the needs of the application into the external API call(s) needed to accomplish those tasks.
In describing this pattern, Martin Fowler writes:
A key purpose of the gateway is to translate a foreign vocabulary which would otherwise complicate the host code.
Naming things and establishing clean domain models is hard enough already; you probably don’t want a third-party’s terminology complicating things further. The job of a gateway is to keep external API-specific jargon at arm’s length.
For example, a hypothetical PaymentGateway
class could take care of things like:
- Expose methods for high-level domain concepts (e.g.
cancel_subscription
) and translate them into what may require several low-level API calls behind the scenes. - Map between your Active Record models and the external API’s request/response objects.
- Convert API-specific errors into exception classes that make sense for the domain your application; e.g.
Faraday::Error
→SubscriptionInactiveError
.
Gateway by another name?
It’s worth noting that the gateway pattern is often applied without using the term gateway. The term service is also popular. A PaymentService
might encapsulate the logic for dealing with an external payment processor, for example.
I prefer the term gateway because service is overloaded in the developer community and can lead to confusion. Some Rails developers use service objects to refer to form objects, command objects, or anything with a call
method; in the Java community, services are a very specific layer of a stack that includes repositories, models, and data-transfer objects (DTOs).
Where service is vague, the term gateway more clearly states its purpose: it defines a boundary between the app and an external system.
File organization
Gateway classes don’t quite fit the standard Rails categorization of models, views, controllers, and helpers. Where should they go?
Place gateway classes in app/gateways
I recommend defining gateway classes in an app/gateways
folder. Rails auto-loads all directories beneath app
, so this works without any extra configuration.
app/gateways/
payment_gateway.rb
push_notification_gateway.rb
Place purely external code in app/lib
Each gateway will need some code to make actual HTTP calls to the external APIs. Sometimes this code is already built and packaged in an official gem, like stripe
or twilio-ruby
. In other cases, you might have to build your own API client, and write classes that represent the data you get back from the API. This API-specific code is unrelated to the rest your app.
Rails ships with a lib
directory for the purpose of organizing this type of code, but it is not auto-loaded, which causes a lot of friction. For this reason I recommend using app/lib
, which is auto-loaded by default.
app/lib/
acme_payments/
api_client.rb
subscription.rb
app/lib/acme_payments
.
Combining the gateway and registry patterns
Now that your gateways are cleanly separated from the rest of your app in terms of file structure, how do you actually call them? For example, let’s say that you need to inform the payment gateway when a user cancels their subscription. It would be nice to write something like this without having to worry about how the gateway is constructed:
payment_gateway.cancel_subscription(user)
payment_gateway
come from?
This is where the registry pattern comes in. Consider using a registry named Gateways
to provide easy global access to singleton instances of your gateways:
module Gateways
def self.payment
@payment ||= PaymentGateway.new
end
def self.push_notification
@push_notification ||= PushNotificationGateway.new
end
end
This registry allows you to write e.g.
Gateways.payment
to reference the payment gateway singleton from anywhere in the app.
A nice consequence of this pattern is that gateways can be easily stubbed for testing. That way you can write unit tests without triggering external API calls, for example.
expect(Gateways).to receive(:payment).and_return(payment_gateway_stub)
Check out my registry pattern article for a closer look at this approach.
Conclusion
As with any abstraction, the gateway pattern adds some overhead to the architecture of a Rails code base. It’s important to identify a clear need before reaching for a solution, and adding new directories under app/
is a decision that shouldn’t be taken lightly.
Generally, I recommend the gateway pattern when these conditions are met:
- Your app needs to interact with one or more external APIs
- API interactions happen at multiple locations throughout your code base
- A non-trivial amount of code is needed to translate between your Rails models and the concepts of external API; or the external API doesn’t have an official gem and requires you to write a custom-built client
Further reading
- The Registry Pattern
- Advanced techniques for calling HTTP APIs in Ruby
- Gateway (Martin Fowler)
- ServiceLocator (Martin Fowler)
- Whatever you do, don’t autoload Rails
lib/
(Ben Sheldon)
You just read
The Gateway Pattern
Use gateway classes to organize external API integrations and establish a clearer separation of concerns.
Share this post? Copy link
About the author
Hi! I’m a Ruby and CSS enthusiast, regular open source contributor, software engineer, and occasional blogger writing from the San Francisco Bay Area. Thanks for stopping by! —Matt