Apprendre à encapsuler les interactions avec un service externe
Le Web est couvert de services exposant une API pour les applications modernes avec lesquelles interagir, allant des données météorologiques à la géolocalisation en passant par les calendriers et bien plus encore. C’est vraiment puissant. Importer un SDK ou effectuer un simple appel REST dans votre application peut ne prendre que quelques minutes ! Au fur et à mesure que les applications mûrissent et que le nombre de ces dépendances augmente, la diffusion de ces appels directs dans votre base de code devient confuse et les nouvelles fonctionnalités prennent plus de temps à mettre en œuvre.
Je veux parler de la façon dont j’utilise un modèle de conception inventé un passerelle par Martin Fowler. L’idée simple est de créer une classe dans votre application pour encapsuler toutes les interactions avec un service externe. J’utilise à la fois les termes « externe » et « service » de manière libérale. Ce modèle fonctionne pour une API REST d’un service cloud, mais fonctionne également bien lors de l’appel d’un script perl local pour écrire quelque chose sur le système local. Certains des avantages incluent:
- Créer un comportement cohérent via votre base de code, comme les exceptions
- Amélioration de la lisibilité du code
- Simplification des demandes et des réponses compliquées résultant en une plus grande réutilisabilité
- Découverte facile des fonctionnalités utilisées à partir d’un SDK riche
- Rendre la gestion des connexions et des identifiants plus robuste
Vous réussirez avec ce modèle si vous absorbez ce concept :
Les Gateway
La classe que vous créez expose l’interface minimale la plus simple et la plus absolue nécessaire à votre application.
La plupart des applications n’utiliseront jamais qu’un très petit pourcentage des capacités qu’un service a à offrir. La création d’une passerelle nous permet de rester concentrés sur les fonctionnalités dont nous avons réellement besoin et d’ignorer le reste.
Voyons comment cela pourrait fonctionner avec PyGithubName. Nous allons commencer par importer le SDK tiers puis gérer la configuration de la connexion dans le constructeur de notre Gateway. Cela facilite la mise à jour de la façon dont nous nous connectons ultérieurement si nous devons passer du chargement du jeton d’un fichier à une variable d’environnement par exemple.
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)
Maintenant, nous voulons une méthode simple pour obtenir une liste des dépôts :
def get_repos():
""" Get a list of the names of available repos
"""
return [repo.name for repo in this_connection.get_user().get_repos()]
Dans cette seule ligne, nous sommes passés d’un PaginatedList
d’objets du référentiel à une simple liste de chaînes. Comme c’est tout ce dont j’ai besoin dans mon application, il est maintenant facilement réutilisable sans avoir à revoir la structure de l’une ou l’autre de ces classes complexes. Et bien sûr, GithubGateway().get_repos()
est plus lisible.
Nous devons également vérifier s’il y a des demandes d’extraction en attente. Ici, nous allons tous les deux envelopper la demande et la réponse. J’aime généralement laisser les exceptions remonter jusqu’à l’endroit où elles seront gérées, mais dans ce cas, nous aimerions masquer les détails d’implémentation du lancement GithubException.UnknownObjectException
dans le cas où nous voulons apporter des modifications à l’implémentation ultérieurement.
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)
Il n’y a aucune raison d’ajouter d’autres paramètres à cette méthode pour le moment. S’il est nécessaire à l’avenir de faire de la branche un paramètre, par exemple, c’est aussi simple que d’en faire un paramètre avec ‘main’ par défaut. La classe fait partie de l’application, il n’y a donc aucune raison de la compliquer en essayant de tenir compte des futures fonctionnalités dont nous n’aurons probablement jamais besoin.
C’est aussi l’occasion de créer un RepositoryName
classe en tant qu’objet de domaine et applique le des règles nous connaissons les noms de dépôt GitHub :
- Longueur max : 100 points de code
- Tous les points de code doivent être soit un trait d’union (
-
), un trait de soulignement (_
), une période (.
), ou un point de code alphanumérique ASCII
Vous trouverez les étapes pour créer ce type de classe dans Rendre les chaînes intelligentes en Python.
Nous avons semé une passerelle ici et c’est peut-être tout ce dont nous avons besoin, mais nous avons également de la place pour grandir. En pensant aux services avec lesquels vous interagissez et en créant une classe simple pour chacun, vous pouvez fournir une interface adaptée à votre domaine et à vos cas d’utilisation.