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.
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.
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.
Gateway classes don’t quite fit the standard Rails categorization of models, views, controllers, and helpers. Where should they go?
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
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
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.
For Rails 7.0 and older, using
app/lib is a good alternative.
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:
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:
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.
Check out my registry pattern article for a closer look at this approach.
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