Design Pattern: Gateways in Python for Nice Services and Not-Nice Services
Learn how to encapsulate interactions with an external service

The web is covered with services exposing an API for modern applications to interact with ranging from weather data to geolocation to calendars and so much more. It’s truly powerful. Importing an SDK or making a simple REST call in your app might only take minutes to implement! As applications mature and the number of these dependencies increase, spreading these direct calls across your code base becomes confusing and new features take longer to implement.
I want to talk about how I use a design pattern coined a Gateway by Martin Fowler. The simple idea is to create a class in your application to encapsulate all interactions with an external service. I’m using both the terms “external” and “service” liberally. This pattern works for a REST API of a cloud service, but works equally well when calling a local perl script to write something to the local system. Some of the advantages include:
- Creating consistent behavior through your code base such as exceptions
- Improving readability of code
- Simplifying complicated requests and responses resulting in higher reusability
- Easy discovery of what functionality is in use from a rich SDK
- Making management of connections and credentials more robust
You will be successful with this pattern if you absorb this one concept:
The Gateway
class you create exposes the simplest and absolute bare minimum interface needed for your application.
Most applications will only ever use a very small percentage of the capabilities a service has to offer. Creating a Gateway allows us to stay focused on the features we actually need and ignore the rest.
Let’s look at how this might work with PyGithub. We’ll start by importing the third-party SDK and then handle configuration of the connection in the constructor of our Gateway. This makes it easy to update how we connect later if we need to change from loading the token from a file to an environment variable for instance.
from github import Github
Class GithubGateway:
def __init__(access_token=None):
if access_token is None:
access_token = this._load_access_token()
this._connection = Github(access_token)
Now, we want a simple method to get a list of the repos:
def get_repos():
""" Get a list of the names of available repos
"""
return [repo.name for repo in this_connection.get_user().get_repos()]
In this one line, we’ve gone from a PaginatedList
of repository objects to a simple list of strings. Since that’s all I need in my application, it’s now easily reusable without having to look at the structure of either of these complex classes again. And of course, GithubGateway().get_repos()
is more readable.
We also need to check if there’s outstanding pull requests. Here we’re both going to wrap the request and the response. I generally like to let exceptions bubble up to where they will be handled, but in this case we’d like to hide the implementation details of throwing GithubException.UnknownObjectException
in the case we want to make changes to the implementation later.
def has_open_pull_requests(repo_name):
""" Return True if there are open pull requests against the ‘main’ branch, False if not
"""
try:
return this._connection.get_repo(repo_name).get_pulls(state='open', sort='created', base='main').totalCount > 0
except Exception as e:
raise GithubGatewayException(e)
There’s no reason to add any more parameters to this method right now. If there’s a need in the future to make the branch a parameter, for example, it’s as simple as making it a parameter with a default of ‘main.’ The class is part of the application so there’s no reason to complicate it by trying to account for future functionality we likely won’t ever need.
This is also an opportunity to create a RepositoryName
class as a domain object and apply the rules we know about GitHub repo names:
- Max length: 100 code points
- All code points must be either a hyphen (
-
), an underscore (_
), a period (.
), or an ASCII alphanumeric code point
You’ll find the steps to create this type of class in Making Strings Smart in Python.
We’ve seeded a gateway here and it may be all we ever need, but we also have room to grow. By thinking about the services you interact with and creating a simple class for each, you can provide an interface custom fit to your domain and use cases.