Les marchés publics, comment assurer leur cohérence

  • Votre système comprend-il de nombreux services interconnectés?
  • mettre à jour manuellement le code de service lors du changement de l'API publique?
  • les changements dans vos services sapent souvent le travail des autres, et les autres développeurs vous détestent pour cela?

Si vous avez répondu oui au moins une fois, alors bienvenue!

termes


Marchés publics, spécifications - interfaces publiques à travers lesquelles vous pouvez interagir avec le service. Dans le texte, ils signifient la même chose.

Quel est le sujet de cet article


Apprenez à réduire le temps consacré au développement de services Web à l'aide d'outils de description unifiée des contrats et de génération automatique de code.

Une bonne utilisation des techniques et outils décrits ci-dessous vous permettra de déployer rapidement de nouvelles fonctionnalités et de ne pas casser les anciennes.

À quoi ressemble le problème


Il existe un système composé de plusieurs services. Ces services sont attribués à différentes équipes.



Les services aux consommateurs dépendent du fournisseur de services.
Le système évolue et un jour le prestataire de services modifie ses marchés publics.



Si les services aux consommateurs ne sont pas prêts à changer, le système cesse de fonctionner pleinement.



Comment résoudre ce problème


L'équipe du service fournisseur réparera tout


Cela peut être fait si l'équipe du fournisseur est propriétaire du domaine des autres services et a accès à ses référentiels git. Cela ne fonctionnera que dans les petits projets lorsqu'il y a peu de services dépendants. C'est l'option la moins chère. Si possible, vous devez l'utiliser.

Mettre à jour le code de votre service auprès de l'équipe consommateur


Pourquoi les autres se cassent, mais réparons-nous?

Cependant, la question principale est de savoir comment réparer votre service, à quoi ressemble le contrat maintenant? Vous devez apprendre le nouveau code de service du fournisseur ou contacter leur équipe. Nous passons du temps à étudier le code et à interagir avec une autre équipe.

Pensez à quoi faire pour éviter que le problème n'apparaisse


L'option la plus raisonnable à long terme. Considérez-le dans la section suivante.

Comment prévenir la manifestation d'un problème


Le cycle de vie du développement logiciel peut être représenté en trois étapes: conception, implémentation et test.

Chacune des étapes doit être développée comme suit:

  1. au stade de la conception, définir de manière déclarative les contrats;
  2. lors de la mise en œuvre, nous générons le code serveur et client sous contrat;
  3. lors des tests, nous vérifions les contrats et essayons de prendre en compte les besoins des clients (CDC).

Chacune des étapes est expliquée plus loin comme un exemple de notre problème.

A quoi ressemble le problème avec nous




Voilà à quoi ressemble notre écosystème.
Les cercles sont des services et les flèches sont des canaux de communication entre eux.

Frontend est une application cliente basée sur le Web.

La plupart des flèches mènent au service de stockage. Il stocke des documents. C'est le service le plus important. Après tout, notre produit est un système de gestion électronique de documents.

Si ce service modifie ses contrats, le système cessera immédiatement de fonctionner.



Les sources de notre système sont principalement écrites en c #, mais il existe également des services en Go et en Python. Dans ce contexte, peu importe ce que font les autres services de la figure.



Chaque service a sa propre implémentation client pour travailler avec le service de stockage. Lors de la modification des contrats, vous devez mettre à jour manuellement le code dans chaque projet.

Je voudrais m'éloigner de la mise à jour manuelle vers automatique. Cela aidera à augmenter la vitesse à laquelle le code client change et à réduire les erreurs. Les erreurs sont des fautes de frappe dans l'URL, des erreurs dues à la négligence, etc.

Cependant, cette approche ne corrige pas les erreurs dans la logique métier du client. Vous ne pouvez l'ajuster que manuellement.

Du problème à la tâche


Dans notre cas, il est nécessaire d'implémenter la génération automatique de code client.
Ce faisant, les éléments suivants doivent être pris en considération:

  • côté serveur - les contrôleurs sont déjà écrits;
  • le navigateur est client du service;
  • Les services communiquent via HTTP.
  • la génération doit être réglée. Par exemple, pour prendre en charge JWT.

Des questions


Au cours de la résolution du problème, des questions se sont posées:

  • quel outil choisir;
  • comment obtenir des contrats;
  • où placer les contrats;
  • où placer le code client;
  • à quel moment faire la génération.

Voici les réponses à ces questions.

Quel outil choisir


Les outils pour travailler avec les contrats sont présentés dans deux directions - RPC et REST.



RPC peut être compris comme un simple appel distant, tandis que REST requiert des conditions supplémentaires pour les verbes HTTP et les URL.

Les différences d'appels RPC et REST sont présentées ici.
RPC - Appel de procédure à distanceREST Representational State Transfer
, HTTP- URL
Restaurant:8080/Orders/PlaceOrderPOSTRestaurant:8080/Orders
Restaurant:8080/Orders/GetOrder?OrderNumber=1GETRestaurant:8080/Orders/1
Restaurant:8080/Orders/UpdateOrderPUTRestaurant:8080/Orders/1


Outils


Le tableau présente une comparaison des outils pour travailler avec REST et RPC.
PropriétésOpenapiWsdlÉpargnegRPC
Un typeDU REPOSRpc
Plate-formeNe dépend pas
LangueNe dépend pas
Séquence de développement *code d'abord, spécification d'abordcode d'abord, spécification d'abordspec firstcode d'abord, spécification d'abord
Protocole de transportHTTP / 1.1tout (REST nécessite HTTP)posséderHTTP / 2
Vuespécificationcadre
CommentaireSeuil d'entrée bas, beaucoup de documentationRedondance XML, SOAP, etc.Seuil d'entrée élevé, peu de documentationSeuil d'entrée moyen, meilleure documentation
Code d'abord - nous écrivons d'abord la partie serveur, puis nous obtenons des contrats dessus. C'est pratique lorsque le côté serveur est déjà écrit. Pas besoin de décrire manuellement les contrats.

Spec first - nous définissons d'abord les contrats, puis nous obtenons la partie client et la partie serveur. C'est pratique au début du développement quand il n'y a pas encore de code. La

sortie

WSDL ne convient pas en raison de sa redondance.

Apache Thrift est trop exotique et difficile à apprendre.

GRPC nécessite Net Core 3.0 et Net Standard 2.1. Au moment de l'analyse, le Core 2.2 net et le Standard 2.0 net étaient utilisés. Il n'y a pas de support GRPC dans le navigateur prêt à l'emploi, une solution supplémentaire est requise. GRPC utilise Protobuf et la sérialisation binaire HTTP / 2. Pour cette raison, la gamme d'utilitaires pour tester les API telles que Postman, etc., se réduit. Les tests de charge via certains JMeter peuvent nécessiter des efforts supplémentaires. Ne convient pas, le passage à GRPC nécessite beaucoup de ressources.

OpenAPI ne nécessite pas de mises à jour supplémentaires. Il captive une abondance d'outils qui prennent en charge le travail avec REST et cette spécification. Nous le sélectionnons.

Outils pour travailler avec OpenAPI


Le tableau présente une comparaison des outils pour travailler avec OpenAPI.
OutilsswashbuckleNSwagOpenapitools
Versions de spécifications prises en chargePeut générer des spécifications au format OpenApi v2, v3
Prise en charge du code en premieril y ail y aNon
Langues de serveur prises en chargeNonC #Beaucoup de
Langues client prises en chargeNonC #, TypeScript, AngularJS, Angular (v2 +), window.fetch APIBeaucoup de
Paramètres de générationNonil y ail y a
VueForfait NugetPaquet Nuget + utilitaire séparéUtilitaire séparé
La conclusion de

Swashbuckle ne convient pas, car vous permet d'obtenir uniquement la spécification. Pour générer du code client, vous devez utiliser une solution supplémentaire.

OpenApiTools est un outil intéressant avec un tas de paramètres, mais il ne prend pas en charge le code en premier. Son avantage est la possibilité de générer du code serveur dans de nombreuses langues.

NSwag est pratique car il s'agit d'un package Nuget. Il est facile de se connecter lors de la construction d'un projet. Prend en charge tout ce dont nous avons besoin: première approche du code et génération de code client en c #. Nous le sélectionnons.

Où organiser les contrats. Comment accéder aux services des contrats


Voici des solutions pour organiser le stockage sous contrat. Les solutions sont répertoriées par ordre croissant de complexité.

  • Le dossier de projet du service fournisseur est l'option la plus simple. Si vous devez exécuter l'approche, choisissez-la.
  • un dossier partagé est une option valide si les projets souhaités se trouvent dans le même référentiel. À long terme, il sera difficile de maintenir l'intégrité des contrats dans le dossier. Cela peut nécessiter un outil supplémentaire pour tenir compte des différentes versions des contrats, etc.
  • référentiel séparé pour les spécifications - si les projets se trouvent dans différents référentiels, les contrats doivent être placés dans un lieu public. Les inconvénients sont les mêmes que pour le dossier partagé.
  • via l'API de service (swagger.ui, swaggerhub) - un service distinct qui traite de la gestion des spécifications.

Nous avons décidé d'utiliser l'option la plus simple: stocker les contrats dans le dossier de projet du fournisseur de services. Cela nous suffit à ce stade, alors pourquoi payer plus?

À quel moment générez-vous


Vous devez maintenant décider à quel moment effectuer la génération de code.
Si les contrats étaient partagés, les services aux consommateurs pourraient recevoir les contrats et générer le code eux-mêmes si nécessaire.

Nous avons décidé de placer les contrats dans le dossier avec le projet du prestataire de services. Cela signifie que la génération peut être effectuée après l'assemblage du projet de service fournisseur lui-même.

Où placer le code client


Le code client sera généré par contrat. Reste à savoir où le placer.
Il semble judicieux de placer le code client dans un projet StorageServiceClientProxy distinct. Chaque projet pourra connecter cet assemblage.

Avantages de cette solution:

  • le code client est proche de son service et est constamment à jour;
  • les consommateurs peuvent utiliser le lien vers le projet dans un référentiel.

Désavantages:

  • ne fonctionnera pas si vous devez générer un client dans une autre partie du système, par exemple, un référentiel différent. Il est résolu en utilisant au moins un dossier partagé pour les contrats;
  • les consommateurs doivent être rédigés dans la même langue. Si vous avez besoin d'un client dans une autre langue, vous devez utiliser OpenApiTools.

Nous fixons NSwag


Attributs du contrôleur


Besoin de dire à NSwag comment générer les spécifications correctes pour nos contrôleurs.

Pour ce faire, vous devez organiser les attributs.

[Microsoft.AspNetCore.Mvc.Routing.Route("[controller]")]  //  url
[Microsoft.AspNetCore.Mvc.ApiController] //     
public class DescriptionController : ControllerBase {
[NSwag.Annotations.OpenApiOperation("GetDescription")] //    
[Microsoft.AspNetCore.Mvc.ProducesResponseType(typeof(ConversionDescription), 200)] //    200  
[Microsoft.AspNetCore.Mvc.ProducesResponseType(401)] //    401
[Microsoft.AspNetCore.Mvc.ProducesResponseType(403)] //    403
[Microsoft.AspNetCore.Mvc.HttpGet("{pluginName}/{binaryDataId}")] //  url
public ActionResult<ConversionDescription> GetDescription(string pluginName, Guid binaryDataId) { 
 // ... 
}

Par défaut, NSwag ne peut pas générer la spécification correcte pour le type d'application / flux d'octets MIME. Par exemple, cela peut se produire lors du transfert de fichiers. Pour résoudre ce problème, vous devez écrire votre attribut et votre processeur pour créer la spécification.

[Microsoft.AspNetCore.Mvc.Route("[controller]")]
[Microsoft.AspNetCore.Mvc.ApiController]
public class FileController : ControllerBase {
[NSwag.Annotations.OpenApiOperation("SaveFile")]
[Microsoft.AspNetCore.Mvc.ProducesResponseType(401)]
[Microsoft.AspNetCore.Mvc.ProducesResponseType(403)]
[Microsoft.AspNetCore.Mvc.HttpPost("{pluginName}/{binaryDataId}/{fileName}")]
[OurNamespace.FileUploadOperation] //  
public async Task SaveFile() { // ... }

Processeur pour générer des spécifications pour les opérations sur les fichiers


L'idée est que vous pouvez écrire votre attribut et votre processeur pour gérer cet attribut.

Nous suspendons l'attribut au contrôleur et lorsque NSwag le rencontre, il le traite à l'aide de notre processeur.

Pour implémenter cela, NSwag fournit les classes OpenApiOperationProcessorAttribute et IOperationProcessor.

Dans notre projet, nous avons fait nos héritiers:

  • FileUploadOperationAttribute: OpenApiOperationProcessorAttribute
  • FileUploadOperationProcessor: IOperationProcessor

En savoir plus sur l'utilisation des processeurs ici.

Configuration de NSwag pour la génération de spécifications et de code


Dans la config 3 sections principales:

  • runtime - Spécifie le runtime .net. Par exemple, NetCore22;
  • documentGenerator - décrit comment générer une spécification;
  • codeGenerators - définit comment générer du code selon la spécification.

NSwag contient un tas de paramètres, ce qui prête à confusion au début.

Pour plus de commodité, vous pouvez utiliser NSwag Studio. En l'utilisant, vous pouvez voir en temps réel comment divers paramètres affectent le résultat de la génération de code ou des spécifications. Après cela, sélectionnez manuellement les paramètres sélectionnés dans le fichier de configuration.

En savoir plus sur les paramètres de configuration ici

Nous générons la spécification et le code client lors de l'assemblage du projet du prestataire


Pour qu'après l'assemblage du projet de fournisseur de services, la spécification et le code soient générés, procédez comme suit:

  1. Nous avons créé un projet WebApi pour le client.
  2. Nous avons écrit une configuration pour Nswag CLI - Nswag.json (décrite dans la section précédente).
  3. Nous avons écrit une cible PostBuild dans le projet de fournisseur de services csproj.

<Target Name="GenerateWebApiProxyClient“ AfterTargets="PostBuildEvent">
<Exec Command="$(NSwagExe_Core22) run nswag.json”/>

  • $ (NSwagExe_Core22) run nswag.json - exécutez l'utilitaire NSwag sous .bet runtine netCore 2.2 avec la configuration nswag.json

Target fait ce qui suit:

  1. NSwag génère une spécification à partir d'un assembly de service fournisseur.
  2. NSwag génère du code client selon les spécifications.

Après chaque assemblage du projet de fournisseur de services, le projet client est mis à jour.
Le projet du client et du prestataire de services sont dans la même solution.
L'assemblage a lieu dans le cadre de la solution. La solution est configurée pour que le projet du client soit assemblé après le projet de service du fournisseur.

NSwag vous permet également de personnaliser impérativement la génération de spécifications / code via l'API logicielle.

Comment ajouter la prise en charge de JWT


Nous devons protéger notre service contre les demandes non autorisées. Pour cela, nous utiliserons des jetons JWT. Ils doivent être envoyés dans les en-têtes de chaque demande HTTP afin que le fournisseur de services puisse les vérifier et décider de répondre ou non à la demande.

Plus d' informations sur JWT ici jwt.io .

La tâche se résume à la nécessité de modifier les en-têtes de la requête HTTP sortante.
Pour ce faire, le générateur de code NSwag peut générer un point d'extension - la méthode CreateHttpRequestMessageAsync. A l'intérieur de cette méthode, il y a accès à la requête HTTP avant son envoi.

Exemple de code
protected Task<HttpRequestMessage> CreateHttpRequestMessageAsync(CancellationToken cancellationToken) {
      var message = new HttpRequestMessage();

      if (!string.IsNullOrWhiteSpace(this.AuthorizationToken)) {
        message.Headers.Authorization =
          new System.Net.Http.Headers.AuthenticationHeaderValue(BearerScheme, this.AuthorizationToken);
      }

      return Task.FromResult(message);
    }


Conclusion


Nous avons choisi l'option avec OpenAPI, car Il est facile à mettre en œuvre et les outils pour travailler avec cette spécification sont très développés.

Conclusions sur OpenAPI et GRPC:

OpenAPI

  • la spécification est détaillée;
  • , ;
  • ;
  • .

GRPC

  • , URL, HTTP ..;
  • OpenAPI;
  • ;
  • ;
  • HTTP/2.

Ainsi, nous avons reçu une spécification basée sur le code déjà écrit des contrôleurs. Pour ce faire, il fallait accrocher des attributs spéciaux sur les contrôleurs.

Ensuite, sur la base des spécifications reçues, nous avons implémenté la génération de code client. Maintenant, nous n'avons pas besoin de mettre à jour manuellement le code client.

Des études ont été menées dans le domaine des contrats de version et de test. Cependant, il n'a pas été possible de tester le tout dans la pratique en raison du manque de ressources.

Gestion des versions des contrats publics


Pourquoi versionner les marchés publics?


Après des changements de fournisseurs de services, l'ensemble du système doit rester dans un état opérationnel cohérent.

Les changements de rupture dans l'API publique doivent être évités afin de ne pas casser les clients.

Options de solution


Sans versionner les marchés publics


L'équipe du fournisseur de services fixe elle-même les services aux consommateurs.



Cette approche ne fonctionnera pas si l'équipe du fournisseur de services n'a pas accès aux référentiels de services aux consommateurs ou manque de compétences. S'il n'y a pas de tels problèmes, vous pouvez vous passer de la gestion des versions.



Utiliser le versioning des marchés publics


L'équipe du prestataire de services quitte la version précédente des contrats.



Cette approche ne présente pas les inconvénients de la précédente, mais ajoute d'autres difficultés.
Vous devez décider des éléments suivants:

  • quel outil utiliser;
  • quand introduire une nouvelle version;
  • combien de temps pour conserver les anciennes versions.

Quel outil utiliser


Le tableau présente les fonctionnalités d'OpeanAPI et de GRPC associées au contrôle de version.
gRPCOpenapi
Attribut de versionAu niveau du protobuf, il existe un package d'attributs [packageName]. [Version]Au niveau de la spécification, il existe des attributs basePath (pour URL) et Version
Attribut déconseillé pour les méthodesOui, mais non pris en compte par le générateur de code sous C #Oui, il est marqué comme obsolète
. NSwag n'est pas pris en charge avec le code d'abord, vous devez écrire votre processeur
Attribut déconseillé pour les paramètresEst marqué comme obsolèteOui, il est marqué comme obsolète
. NSwag n'est pas pris en charge avec le code d'abord, vous devez écrire votre processeur
Obsolète signifie que cette API ne vaut plus la peine d'être utilisée.

Les deux outils prennent en charge la version et les attributs obsolètes.

Si vous utilisez OpenAPI et la première approche du code, vous devez à nouveau écrire des processeurs pour créer la bonne spécification.

Quand introduire une nouvelle version


La nouvelle version doit être introduite lorsque les modifications des contrats ne préservent pas la compatibilité descendante.

Comment vérifier que les modifications violent la compatibilité entre la nouvelle et l'ancienne version des contrats?


Combien de temps pour maintenir les versions


Il n'y a pas de bonne réponse à cette question.

Pour supprimer la prise en charge de l'ancienne version, vous devez savoir qui utilise votre service.

Ce sera mauvais si la version est supprimée et que quelqu'un d'autre l'utilise. C'est particulièrement difficile si vous ne contrôlez pas vos clients.

Alors, que peut-on faire dans cette situation?

  • informer les clients que l'ancienne version ne sera plus prise en charge. Dans ce cas, nous pouvons perdre le revenu de nos clients;
  • prendre en charge l'ensemble des versions. Le coût du support logiciel augmente;
  • Pour répondre à cette question, vous devez vous poser la question suivante: les revenus des anciens clients dépassent-ils le coût de prise en charge des anciennes versions de logiciels? Serait-il plus rentable de demander aux clients de mettre à niveau?

Le seul conseil dans cette situation est d'accorder plus d'attention aux marchés publics afin de réduire la fréquence de leurs évolutions.

Si les marchés publics sont utilisés dans un système fermé, vous pouvez utiliser l'approche CDC. Nous pouvons donc savoir quand les clients ont cessé d'utiliser les anciennes versions du logiciel. Après cela, vous pouvez supprimer le support de l'ancienne version.

Conclusion


N'utilisez le contrôle de version que si vous ne pouvez pas vous en passer. Si vous décidez d'utiliser la gestion des versions, lors de la conception des contrats, pensez à la compatibilité des versions. Un équilibre doit être trouvé entre le coût de la prise en charge des anciennes versions et les avantages qu'il offre. Il convient également de décider quand vous pouvez arrêter de prendre en charge l'ancienne version.

Contrats de test et CDC


Cette section est illuminée superficiellement, comme Il n'y a pas de conditions préalables sérieuses pour la mise en œuvre de cette approche.

Contrats axés sur le consommateur (CDC)


CDC est la réponse à la question de savoir comment s'assurer que le fournisseur et le consommateur utilisent les mêmes contrats. Il s'agit d'une sorte de tests d'intégration visant à vérifier les contrats.
L'idée est la suivante:

  1. Le consommateur décrit le contrat.
  2. Le fournisseur exécute ce contrat à domicile.
  3. Ce contrat est utilisé dans le processus CI chez le consommateur et le fournisseur. Si le processus est violé, alors quelqu'un a cessé de se conformer au contrat.

Pacte


PACT est un outil qui met en œuvre cette idée.

  1. Le consommateur écrit des tests à l'aide de la bibliothèque PACT.
  2. Ces tests sont convertis en fichier d'artefact-pacte. Il contient des informations sur les contrats.
  3. Le fournisseur et le consommateur utilisent le fichier pacte pour exécuter les tests.

Pendant les tests client, un stub fournisseur est créé et pendant les tests fournisseur, un stub client est créé. Ces deux talons utilisent un fichier pacte.

Un comportement de création de stub similaire peut être obtenu via le Swagger Mock Validator bitbucket.org/atlassian/swagger-mock-validator/src/master .

Liens utiles sur Pacte





Comment CDC peut être intégré dans CI


  • déployer Pact + Pact Broker vous-même;
  • acheter une solution Pact Flow SaaS prête à l'emploi.

Conclusion


Un pacte est nécessaire pour garantir la conformité du contrat. Il montrera quand les changements dans les contrats violent les attentes des services aux consommateurs.

Cet outil convient lorsque le fournisseur s'adapte au client - le client. Cela n'est possible qu'à l'intérieur d'un système isolé.

Si vous rendez service au monde extérieur et que vous ne savez pas qui sont vos clients, alors Pact n'est pas pour vous.

All Articles