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.

Diagram with “your domain” on the left, “their domain” on the right, with “gateway” mediating between the two
A gateway formalizes the boundary between your app’s domain and that of the external API, translating between the two.

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 lib or 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. Before Rails 7.1, this directory was not auto-loaded by default, which caused a lot of friction, but for Rails 7.1 and newer, this is no longer a problem.

config.autoload_lib(ignore: %w[assets tasks])
config/application.rb
This is the default configuration starting in Rails 7.1. With this setting, Rails will automatically load the contents of lib, and in development mode it will be auto-refreshed.

For Rails 7.0 and older, using app/lib is a good alternative.

lib/
  acme_payments/
    api_client.rb
    subscription.rb
Let’s say you’re using the “Acme Payments” API, and this hypothetical API doesn’t have an official Ruby gem. You’ll need to build your own API client. A good place for this would be under app/lib/acme_payments (Rails 7) or lib/acme_payments (Rails 7.1+).

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)
But where does 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
app/gateways/gateways.rb
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)
Stubbing the payment gateway in RSpec.

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

Share this? Copy link

Feedback? Email me!

Hi! 👋 I’m Matt Brictson, a software engineer in San Francisco. This site is my excuse to practice UI design, fuss over CSS, and share my interest in open source. I blog about Rails, design patterns, and other development topics.

Recent articles

RSS
View all posts →

Open source projects

mattbrictson/nextgen

Generate your next Rails app interactively! This template includes production-ready recommendations for testing, security, developer productivity, and modern frontends. Plus optional Vite support! ⚡️

90
Updated 14 days ago

mattbrictson/tomo

A friendly CLI for deploying Rails apps ✨

374
Updated 14 days ago

More on GitHub →