Gateway
Gateway

Interesting software rarely lives in isolation. The software a team
writes usually has to interact with external systems, these may be
libraries, remote calls to external services, interactions with a database,
or with with files. Usually there will be some form of API for that external
system, but that API will often seem awkward from the context of our
software. The API may use different types, require strange arguments,
combine fields in ways that don’t make sense in our context. Dealing with
such an API can result in jarring mismatches whenever its used.

A gateway acts as a single point to confront this foreigner. Any code
within our system interacts with the interface of the gateway, which is
designed to work in the terms that our system uses. The gateway then
translates this convenient API into the API offered by the foreigner.

While this pattern is widely used (but should be more prevalent), the
name “gateway” has not caught on. So while you should expect to see this
pattern frequently, there is no widely-used name for it.

How it Works

A gateway is typically a simple wrapper. We look at what our code needs
to do with the external system and construct an interface that supports that
clearly and directly. We then implement the gateway to translate that
interaction to the terms of the external system. This will usually involve
translating a familiar function call into what’s required by the foreign
API, adjusting parameters as needed to make it work. When we get the
results, we then transform those into a form that’s easily consumable in our
code. As our code grows, making new demands on the external system, we
enhance the gateway to continue to support its different needs.

Gateways should only include logic that supports this translation between
domestic and foreign concepts. Any logic that builds on that should be in
clients of the gateway.

It’s often useful to add a connection object to the basic structure of
the gateway. The connection is a simple wrapper around the call to the
foreign coded. The gateway translates its parameters into the foreign
signature, and calls the connection with that signature. The connection then
just calls the foreign API and returns its result. The gateway finishes by
translating that result to a more digestible form. The connection can be
useful in two ways. Firstly it can encapsulate any awkward parts of the call
to the foreign code, such as the manipulations needed for a REST API call.
Secondly it acts as a good point for inserting a Test Double.

When to Use It

I use a gateway whenever I access some external software and there is any
awkwardness in that external element. Rather than let the awkwardness spread
through my code, I contain to a single place in the gateway.

Using a gateway can make a system much easier to test by allowing the
test harness to stub out the gateway’s connection object. This is
particularly important for gateways that access remote services, as it can
remove the need for a slow remote call. It’s essential for external systems
that need to supply canned data for testing but aren’t designed to do so. I
would use a gateway here, even if the external API is otherwise okay to use
(in which case the gateway would only be the connection object).

Another virtue of a gateway is that it makes much easier to swap out an
external system for another, should that happen. Similarly should an
external system change its API or returned data, a gateway makes it much
easier to adjust our code since any change is confined to a single place.
But although this benefit is handy, its hardly ever a reason to use a
gateway, since just encapsulating the foreign API is justification
enough.

A key purpose of the gateway is to translate a foreign vocabulary which
would otherwise complicate the host code. But before doing that, we do need
to consider whether we should just use the foreign vocabulary. I have come
across situations where a team has translated a widely-understood foreign
vocabulary into a particular one for their code base because “they didn’t
like the names”. There’s no general rule I can state for this decision, a
team has to exercise its judgment on whether they should adopt the external
vocabulary or develop their own. (In Domain Driven Design
patterns this is the choice between Conformist and Anticorruption
Layer.)

A particular example of this is where we are building on platform and
considering whether we wish to isolate ourselves from the underlying
platform. In many cases the platform’s facilities are so pervasive that it’s
not worth going through the effort of wrapping it. I won’t consider wrapping
a language’s collection classes, for example. In that situation I just
accept that their vocabulary is part of the vocabulary of my software.

Further Reading

I originally described this
pattern in P of EAA.
At that time I struggled whether to coin a new pattern name as opposed to
referring to the existing Gang of Four patterns: Facade, Adapter, and
Mediator. In the end I decided that there was enough of a difference that it
was worth a new name.

While Facade simplifies a more complex API, it’s usually done by the
writer of the service for general use. A gateway is written by the client
for its particular use.

Adapter is the closest GoF pattern to the gateway as it alters an class’s
interface to match another. But the adapter is defined in the context of
both interfaces already being present, while with a gateway I’m defining the
gateway’s interface as I wrap the foreign element. That distinction led me
to treat gateway as a separate pattern. Over time people have used “adapter”
much more loosely, so it’s not unusual to see gateways called adapters.

Mediator separates multiple objects so they don’t need to know about each
other, they just know about the mediator. With a gateway there’s usually
only one resource that’s being encapsulated behind the gateway and that
resource will not know about the gateway.

The notion of a gateway fits well with that of the Bounded Contexts of Domain Driven Design. I use a gateway when I’m dealing with something
in a different context, the gateway handles the translation between the
foreign context and my own. The gateway is a way to implement an
Anticorruption Layer. Consequently some teams will use that term, naming
their gateways with the sortof-abbreviation “ACL”.

A common use of the term “gateway” is the API gateway.
According the principles I’ve outlined above, this is really more of a
facade, since it’s build by the service provider for general client usage.

Example: Simple Function (TypeScript)

Consider an imaginary hospital application that monitors a range of
treatment programs. Many of these treatment programs need to book a patient
to have time with a bone fusion machine. To do this, the application needs
to interact with the hospital’s equipment booking service. The application
interacts with the service via a library which exposes a function to list
available booking slots for some equipment.

equipmentBookingService.ts…

  export function listAvailableSlots(equipmentCode: string, duration: number, isEmergency: boolean) : Slot[]

Since our application only uses bone fusion machines, and never in an
emergency, it makes sense to simplify this function call. A simple
gateway here can be a function, named in a way that makes sense for the
current application.

boneFusionGateway.ts…

  export function listBoneFusionSlots(length: Duration) 
    return ebs.listAvailableSlots("BFSN", length.toMinutes(), false)
      .map(convertSlot)
  

This gateway function is doing several useful things. Firstly its name
ties it to the particular usage within the application, allowing many
callers to contain code that is clearer to read.

The gateway function encapsulates the equipment booking service’s
equipment code. Only this function needs to know that to get a bone fusion
machine, you need code “BFSN”.

The gateway function does conversion from the types used within the
application to the types used by the API. In this case the application uses
js-joda to handle time –
a common and wise choice to simplify any kind of date/time work in
JavaScript. The API however, uses an integer number of minutes. The gateway
function allows callers to work with the conventions in the application,
without concerning themselves about how to convert to the conventions of the
external API.

All requests from the application are non-urgent, hence the
gateway doesn’t expose a parameter that’s always going to be the same value

Finally, the return values from the API are converted from the context of
the equipment booking service with a conversion function.

The equipment booking service returns slot objects that look like this

equipmentBookingService.ts…

  export interface Slot 
    duration: number,
    equipmentCode: string,
    date: string,
    time: string,
    equipmentID: string,
    emergencyOnly: boolean,
  

but the calling application finds it more useful to have slots like this

treatmentPlanningAppointment.ts…

  export interface Slot 
    date: LocalDate,
    time: LocalTime,
    duration: Duration,
    model: EquipmentModel
  

so this code performs the conversion

boneFusionGateway.ts…

  function convertSlot(slot:ebs.Slot) : Slot 
    return 
      date: LocalDate.parse(slot.date),
      time: LocalTime.parse(slot.time),
      duration: Duration.ofMinutes(slot.duration),
      model: modelFor(slot.equipmentID),
    
  

The conversion leaves out fields that are meaningless to the
treatment planning application. It converts from the date and time strings
to js-joda. The treatment planning users don’t care about the equipmentID
codes, but they do care about what model of equipment is available in the
slot. So convertSlot looks up the equipment model from its local
store and enriches the slot data with a model record.

By doing this, the treatment planning application doesn’t have to deal
with the language of the equipment booking service. It can pretend that the
equipment booking service works seamlessly in the world of treatment
planning.

Example: Using a replaceable connection (TypeScript)

Gateways are the path to foreign code, and often foreign code is the
route to important data that resides in other places. Such foreign data can
complicate testing. We don’t want to be booking equipment slots every time
the developers of the treatment application run our tests. Even if the
service provides a test instance, the slow speed of remote calls often
undermines the usability of a fast test suite. This is when it makes sense
to use a Test Double.

A gateway is a natural point to insert such a test double, but there are
couple of different ways to do it, since its worth having a bit more
structure to a remote gateway. When working with a remote service, the
gateway fulfills two responsibilities. As with local gateways, it does the
translation from the vocabulary of the remote service into that of the host
application. But with a remote service, it also has the responsibility of
encapsulating the remoteness of that remote service, such as the details of
how the remote call is done. That second responsibility implies that a remote
gateway should contain a separate element to handle that, which I call the
connection.

In this situation listAvailableSlots may be a remote call to
some URL which can be supplied from configuration.

equipmentBookingService.ts…

  export async function listAvailableSlots(equipmentCode: string, duration: number, isEmergency: boolean) : Promise<Slot[]>
  
    const url = new URL(config['equipmentServiceRootUrl'] + '/availableSlots')
    const params = url.searchParams;
    params.set('duration', duration.toString())
    params.set('isEmergency', isEmergency.toString())
    params.set('equipmentCode', equipmentCode)
    const response = await fetch(url)
    const data = await response.json()
    return data
  

Having the root URL in configuration allows us to test the system against
a test instance or a stub service by supplying a different root URL. This is
great, but by manipulating the gateway we can avoid a remote call at all,
which can be a significant speedup for the tests.

The connection also takes care of hassles using the machinery for
invoking the remote call, in this case JavaScript’s fetch API. The outer
gateway handles converting the gateway’s interface to the remote signature in
terms of the remote API, while the connection takes that signature and
expresses it as an HTTP get. Breaking those two tasks apart keeps each one simple.

I then add this connection to the gateway class on construction. The
public function then uses this passed in connection.

class BoneFusionGateway…

  private readonly conn: Connection
  constructor(conn:Connection) 
    this.conn = conn
  

  async listSlots(length: Duration) : Promise<Slot[]> 
    const slots = await this.conn("BFSN", length.toMinutes(), false)
    return slots.map(convertSlot)
  

Often gateways support several public functions on the same underlying
connection. So if our treatment application later needed to reserve a blood
filter machine, we could add another function to the gateway that would use
the same connection function with a different equipment code. Gateways may
also combine data from multiple connections into a single public
function.

When a service call like this requires some configuration, it’s usually
wise to do it separately from the code that uses it. We want our
treatment planning appointment code to be able to simply use the gateway
without having to know about how it should be configured. A simple and
useful way to do this is to use a service locator.

class ServiceLocator…

  boneFusionGateway: BoneFusionGateway

serviceLocator.ts…

  export let theServiceLocator: ServiceLocator

configuration (usually run at application startup)

  theServiceLocator.boneFusionGateway = new BoneFusionGateway(listAvailableSlots)

application code using the gateway

  const slots =  await theServiceLocator.boneFusionGateway.listSlots(Duration.ofHours(2))

Given this kind of setup, I can then write a test with a stub for the
connection like this

it('stubbing the connection', async function() 
  const input: ebs.Slot[] = [
    duration:  120, equipmentCode: "BFSN", equipmentID: "BF-018",
     date: "2020-05-01", time: "13:00", emergencyOnly: false,
    duration: 180, equipmentCode: "BFSN", equipmentID: "BF-018",
     date: "2020-05-02", time: "08:00", emergencyOnly: false,
    duration: 150, equipmentCode: "BFSN", equipmentID: "BF-019",
     date: "2020-04-06", time: "10:00", emergencyOnly: false,
   
  ]
  theServiceLocator.boneFusionGateway = new BoneFusionGateway(async () => input)
  const expected: Slot[] = [
    duration: Duration.ofHours(2), date: LocalDate.of(2020, 5,1), time: LocalTime.of(13,0),
     model: new EquipmentModel("Marrowvate D12"),
    duration: Duration.ofHours(3), date: LocalDate.of(2020, 5,2), time: LocalTime.of(8,0),
     model: new EquipmentModel("Marrowvate D12"),
  ]
  expect(await suitableSlots()).toStrictEqual(expected)
);

Stubbing in this way allows me to write tests without having to do a
remote call at all.

Depending on the complexity of the translation that the gateway is doing,
however, I might prefer to write my test data in the language of the
application rather than the language of the remote service. I can do that
with a test like this that checks that suitableSlots removes
slots with the wrong kind of equipment model.

it('stubbing the gateway', async function() 
  const stubGateway = new StubBoneFusionGateway()
  theServiceLocator.boneFusionGateway = stubGateway
  stubGateway.listSlotsData = [
    duration: Duration.ofHours(2), date: LocalDate.of(2020, 5,1), time: LocalTime.of(12,0),
     model: new EquipmentModel("Marrowvate D10"), // not suitable
    duration: Duration.ofHours(2), date: LocalDate.of(2020, 5,1), time: LocalTime.of(13,0),
     model: new EquipmentModel("Marrowvate D12"),
    duration: Duration.ofHours(3), date: LocalDate.of(2020, 5,2), time: LocalTime.of(8,0),
     model: new EquipmentModel("Marrowvate D12"),
  ]
  const expected: Slot[] = [
    duration: Duration.ofHours(2), date: LocalDate.of(2020, 5,1), time: LocalTime.of(13,0),
     model: new EquipmentModel("Marrowvate D12"),
    duration: Duration.ofHours(3), date: LocalDate.of(2020, 5,2), time: LocalTime.of(8,0),
     model: new EquipmentModel("Marrowvate D12"),
  ]
  expect(await suitableSlots()).toStrictEqual(expected)   
);

class StubBoneFusionGateway extends BoneFusionGateway   
  listSlotsData: Slot[] = []

  async listSlots(length: Duration) : Promise<Slot[]> 
    return this.listSlotsData
  
  
  constructor() 
    super (async () => []) //connection not used, but needed for type check
  

Stubbing the gateway can make it clearer what the application logic inside
suitableSlots is supposed to do – in this case filter out the
Marrowvate D10. But when I do this, I’m not testing the translation logic
inside the gateway, so I need at least some tests that stub at the connection
level. And if the remote system data isn’t too hard to follow, I might be able to
get away with only stubbing the connection. But often it’s useful
to be able stub at both points depending on the test I’m writing.

My programming platform may support some form of stubbing for remote calls
directly. For example the JavaScript testing environment Jest allows me to stub all sorts of function calls
with its mock functions. What’s available to me depends on the platform
I’m using, but as you can see, it’s not difficult to design gateways to
have these hooks without any additional tools.

When stubbing a remote service like this, it’s wise to use Contract Tests to ensure my assumptions about the remote service stay
in sync with any changes that service makes.

Example: Refactoring code accessing YouTube to introduce a Gateway (Ruby)

A few years ago I
wrote an article with some code that accesses YouTube’s API to display
some information about videos. I show how the code tangles up different
concerns and refactor the code to clearly separate them – introducing a
gateway in the process. It provides a step-by-step explanation of how we can
introduce a gateway into an existing code base.

Acknowledgements

(Chris) Chakrit
Likitkhajorn, Cam Jackson, Deepti Mittal, Jason Smith, Karthik Krishnan, Marcelo de Moraes Leite, Matthew
Harward, and Pavlo Kerestey

discussed drafts of this post on our internal mailing list.

Leave a Reply