J’ai démarré il y a un peu plus d'un an une mission chez Euler Hermes, leader mondial de l’assurance du crédit fournisseur. Pour remettre un peu de contexte à cette mission : cette société bien connue des professionnels permet d’assurer le risque que prend une société lorsqu’un de ses acheteurs lui passe un contrat sur des biens ou des services. Finalement si l’acheteur fait un défaut de paiement, Euler Hermes réglera son client et se chargera du recouvrement par la suite.
Cette société séculaire a su évoluer au fil des années pour commencer une migration complète sur AWS. Si vous souhaitez plus de détails, vous pouvez visionner la keynote du CIO d’Euler Hermes lors du dernier AWS Summit Paris: https://www.youtube.com/watch?v=aMKz4ZjmzEw
Au sein de la société, une équipe est en charge d’apporter les guidelines AWS afin que l’on respecte les 5 grands piliers AWS (pour rappel : performance, sécurité, finops, fiabilité et pour finir excellence opérationnelle). Dans ce dernier pilier, il est question de fluidifier, par l’organisation et des processus, le développement et le déploiement des applications.
C’est sur ce point en particulier que nous avons rencontré un problème au sein de l’équipe. Nous avions besoin d’ouvrir nos API REST à des consommateurs externes: pour ce faire nous avons décidé de déployer notre API sous API Gateway. Grâce à un simple swagger, l’affaire était dans le sac.
Habitués à communiquer les contrats d’interfaces à d’autres équipes, nous avons utilisé la librairie Springfox( http://springfox.github.io/springfox/ ) permettant de générer un swagger à partir d’annotations mises en place dans le code.
Premier problème avec cette solution : il me faudrait lancer le serveur au moment du build, le requêter ensuite sur l’URL qui sert la génération du swagger.
C’est faisable mais ça commence à se complexifier et ça me dérange de complexifier mon projet. Qui plus est mon application a des dépendances sur des services extérieurs, inaccessibles depuis ma chaîne de build.
Second problème, AWS requiert des parties spécifiques permettant de décrire le lien entre le endpoint sur la gateway et le composant AWS suivant. Bien qu’ il existe une annotation @Extension permettant de définir des éléments custom, cette annotation contient néanmoins une liste d’@ExtensionProperty, qui n’est autre qu’un couple de clefs/valeurs sous forme de String.
Bien sûr la structure du json pour l’ApiGateway est un peu plus complexe, il va falloir s’adpter au format suivant :
x-amazon-apigateway-integration:
passthroughBehavior: "when_no_match"
connectionId: "${vpcConnectionId}"
responses:
default:
statusCode: "404"
type: "http_proxy"
httpMethod: "GET"
uri: "https://${nginx_uri}/search/v1/company"
connectionType: "VPC_LINK"
timeoutInMillis: 29000
Je me renseigne à droite et à gauche: le retour est toujours le même : “On le génère et le maintient de façon manuelle ce fichier...”.
Non, je n’ose pas y croire. De l’automatisation, que diable!!!
Du coup j’analyse mes problèmes :
- Générer le swagger au moment du build (et qu’il devienne un artefact comme un autre)
- Pouvoir y insérer des parties spécifiques à AWS
- Prendre en compte le versioning
Pour la suite de l’article, j’ai réalisé un repository Github, avec les différentes étapes taggées. La version initiale est à l’URL :
https://github.com/hcapitaine/demoSwaggerAWS/tree/initialProject
Concernant le versionning, je trouve une solution à mon problème :
https://github.com/kongchen/swagger-maven-plugin
Ce génial plugin me permet de générer un swagger directement à partir de mes controllers Spring sans forcément avoir besoin de décrire toutes les méthodes avec des annotations Swagger.
En plus, comme je peux restreindre le scope du scan des classes, cela me permettra d’éviter que le plugin ne scanne les controllers de Spring, comme les actuators.
https://github.com/hcapitaine/demoSwaggerAWS/blob/swaggerV1/pom.xml
Concernant le second point, ce plugin a vraiment tout prévu. Je vois dans la documentation qu’il a dans sa définition une balise swaggerExtensions qui permet d’avoir ses propres implémentations d’Extension. C’est décidé je crée ma propre implémentation.
Je crée une classe AmazonVendorExtension qui permettra soit de faire un lien vers un fichier json qui contiendra les définitions propres à AWS, soit directement la définition sous forme de string qui contiendra du json pour un parsing plus aisé.
Dans un second temps, je crée une implémentation AbstractSwaggerExtension qui permet de scanner chaque endpoint. En voyant le contenu spécifique pour AWS, je me dis qu’une petite optimisation permettra d’éviter de répéter une bonne partie du json commun à tous les endpoints d’une même ressource. Au final l’extension, permet de regarder si la classe porte une annotation AmazonVendorExtension, de parser le contenu avec Jackson et de le merger avec le contenu de l’annotation propre au endpoint.
Le résultat est visible, ici :
https://github.com/hcapitaine/demoSwaggerAWS/tree/swaggerV1AWS
Il me reste à gérer le versionning mais c’est une tâche qui va être assez facile. En ajoutant une seconde phase d’exécution du plugin mais en scannant que les packages v2 de l’application, j’obtiens rapidement un second swagger file avec tout ce qui va bien pour AWS.
Voici le résultat final :
https://github.com/hcapitaine/demoSwaggerAWS/tree/swaggerV2AWS