Gérer les évolutions des services à l'intérieur de votre SI
Cela n’est pas seulement changer une configuration de /v1/
à /v2/
: c’est de la gouvernance, du planning et du budget
On parle de quoi
L’essentiel des discussions sur les services porte sur les questions de design et de technique. Sur ces deux axes, on commence maintenant à disposer de pratiques bien établies. Cet article ne traitera donc pas de ces aspects.
Malheureusement, d’après mon expérience avec les grandes entreprises, la difficulté la plus grave dans ce domaine concerne le versionnement, et particulièrement le versionnement des services à l’intérieur du SI. Ces traitements sont ceux qui sont exposés et consommés par des applications du SI.
Le besoin est très simple :
- introduire de nouvelles versions de services pour répondre aux nouvelles demandes ;
- décomissionner les anciennes versions pour limiter le travail de maintenance et garder un SI évolutif.
Ce problème existe depuis qu’on fait de l’intégration entre projets, mais chaque avancée dans la manière de travailler (cycles plus courts, agile et maintenant micro-services) le rend plus critique, et aucune solution miracle n’a encore été inventée pour faciliter la tâche.
La plupart des grandes organisations s’en sortent mal, car cette question mélange les domaines les plus critiques : gouvernance, planning et budget.
Dans ce domaine, il n’y a pas de solution magique, la meilleure solution sera celle que vous serez capable de mettre en œuvre. Le plus important est de choisir une stratégie, même si elle n’est pas la meilleure, plutôt que de ne pas en avoir et de laisser régner le flou.
Nous allons commencer par voir les spécificités des services à l’intérieur de votre SI et les principes de gouvernances qu’on peut leur appliquer. Ensuite nous verrons comment et à quel rythme exposer de nouvelles versions, et en cas de besoin, comment faire quand un consommateur de service ne peut plus évoluer. Nous terminerons sur la question de budgets pour savoir comment peut s’organiser le financement.
Les spécificités des services à l’intérieur d’un SI
Contractualisation faible
Quand un service est exposé ou provient de l’extérieur, il fait l’objet d’une contractualisation. Sans obligatoirement aller jusqu’à un engagement de service détaillé (même si c’est ce qu’on devrait avoir), les engagements sont formalisés et les décisions sont prises.
En interne, la contractualisation est souvent beaucoup plus faible, voire inexistante : on se met d’accord de façon informelle, les engagements sont moraux, et au pire on négociera une extension de budget et de calendrier. C’est souvent là que le bât blesse et un des premiers axes d’amélioration. Une des missions d’un DSI est de s’assurer la bonne marche du SI, et faire en sorte que les services évoluent correctement en est un aspect essentiel.
Le cas des services avec des partenaires est un peu particulier : il est souvent "pire" que celui des services internes. En effet, pour les services internes, en escaladant suffisamment haut dans la hiérarchie, il est généralement possible d’arbitrer toutes les questions. Par contre, entre partenaires, la situation est souvent beaucoup plus floue : officiellement, on a un contrat mais, dans la réalité, tout est négociable et peut être remis en question.
Faible importance donnée au sujet
Les services exposés aux autres projets internes sont souvent négligés par rapport aux fonctionnalités exposées aux utilisateurs. Comme ils sont moins visibles et moins valorisée, on leur donne naturellement moins d’importance. De plus, comme ce sujet est généralement vu comme quelque chose d’uniquement technique, il n’intéresse souvent pas le métier et la MOA. Dans un SI, servir les autres projets et aussi important que de servir les utilisateurs. Il faut que l’ensemble des acteurs en ait conscience, et pas seulement les développeurs.
Si les consommateurs de services se plaignent de ne pas être entendus, le problème peut également venir de la manière dont les projets sont évalués. Ainsi si le niveau de satisfactions des consommateurs de services est pris en compte pour savoir si un projet est un succès, leurs besoins seront probablement mieux pris en compte que si seuls comptent les clients extérieur.
Principes de gouvernance
Comme on l’a déjà dit, la première étape est d’avoir une stratégie claire. Ensuite, il faut décider comment seront prises les différentes décisions. L’idéal est qu’elles le soient le plus localement possible, c’est-à-dire au niveau des projets, sous réserve que l’intérêt global du SI soit respecté.
Deux forces opposées, et un projet au milieu
La gestion des versions est difficile car elle est à la confluence de deux forces opposées :
- les consommateurs qui ont besoin de nouvelles fonctionnalités les veulent le plus vite possible ;
- les consommateurs qui n’en ont pas besoin ne veulent pas bouger.
Les applications du SI qui exposent leurs services sont prises entre ces deux feux. Il faut éviter à tout prix que l’arbitrage entre les deux soit délégué directement au projet :
- il aura tendance à avantager l’approche qui correspond à ses besoins propres ;
- il lui manquera l’appui hiérarchique nécessaire à trancher les conflits.
Comment exposer ?
L’exposition de services est régie par un principe simple : exposer un service, c’est créer un contrat qu’on s’engage à respecter vis-à-vis de ses consommateurs, et donc créer l’adhérence dans le SI.
Comme un des objectifs de l’approche services est de minimiser l’adhérence entre les applications pour que le système évolue le plus facilement possible, il est préférable que la surface d’exposition soit la plus réduite possible.
Pour cela, plusieurs solutions :
N’exposez que ce dont vous avez besoin
Pour limiter l’adhérence, la première étape est de n’exposer que ce dont vous avez besoin. Avec les outils récents, il est facile et tentant d’exposer l’intégralité de vos données dès le début, mais il vaut mieux le faire plutôt à la demande. Tant qu’une donnée n’est pas accessible à l’extérieur vous n’avez pas de compte à rendre si vous voulez la modifier ou même la supprimer.
L’autre avantage est que si vous exposer des informations sans avoir de besoins extérieur pour désigner votre API, vos services seront probablement calqués sur votre usage interne. Lorsqu’un premier consommateur voudra utiliser cette donnée, il est possible que son besoin nécessite de modifier le contrat de service. Dans ce cas l’exposition initiale n’aura donc servi à rien.
N’exposez pas trop tôt
Lorsque vous sortez un nouveau service, il y a de grandes chances que vous ne tombiez pas juste du premier coup. Tant que vous n’êtes pas confiant dans la maturité d’une API, il vaut mieux donc limiter le nombre de consommateurs à un petit groupe d'early adopters.
N’exposez pas tout à tout le monde
Les SI de grande taille (de plusieurs dizaines à plusieurs centaines d’applications) sont généralement déjà séparés en domaines métier. Mais souvent cette séparation n’est qu’une vision logique sur des schémas et ne se retrouve pas dans l’implémentation.
Notre conseil est de s’appuyer sur cette cartographie pour isoler les services des différents blocs : segmentez vos services entre ceux qui ont vocation à être utilisés par l’ensemble du SI et ceux qui ne le sont que par les applications "proches".
Cela permet de cranter les choses en termes d’exposition ou de rythme de migration car les deux groups de services peuvent évoluer différemment.
Attention à ne pas trop alourdir le process permettant de faire évoluer les services transverses : rappelez-vous que l’objectif n’est pas de les figer mais de les faire évoluer au mieux. Ces services sont d’ailleurs souvent aussi exposés à l’extérieur, dans ce cas ils suivent généralement d’autres règles.
Versions majeures à maintenir en parallèle
Une version majeure d’un service est une version qui n’est pas compatible avec la version précédente. Pour un consommateur, passer d’une version à l’autre peut donc demander des modifications. Pour que les projets puissent organiser ces migrations correctement, il est d’usage de maintenir plusieurs versions disponibles pendant un certain temps. Le choix du nombre de versions à maintenir en parallèle est un arbitrage entre le fournisseur de services et les consommateurs :
- plus le nombre de versions à maintenir est faible, moins le fournisseur de services a besoin de faire de la maintenance, mais plus les consommateurs doivent se mettre à jour souvent ;
- plus le nombre de version est élevé, plus le fournisseur de services est obligé de faire de la maintenance, mais plus les consommateurs ont de marge de manœuvre dans leurs migrations.
Le modèle le plus observé est d’avoir deux versions en parallèle en régime de croisière, et ponctuellement trois lors d’une bascule. Avec deux versions, le travail de maintenance est raisonnable pour le fournisseur de service, et laisser passer du temps entre la publication d’une nouvelle version et le décommissionnement de la version N-2 permet aux consommateurs qui le souhaitent de sauter une version sur deux.
Un exemple de gestion de version
Dans le cas d’une application évoluant vite qui nécessite donc une mise à jour rapprochée des services qu’elle expose mais alors que le consommateur ne peut suivre ce rythme, vous pouvez mettre en œuvre le modèle "Zones du SI" et dégraffer les services destinés à cette application du reste des services.
La capacité à conserver la compatibilité lors d’une mise à jour importante du modèle de donnée est une contrainte forte qui peut limiter le nombre de versions. Une restructuration peut par exemple nécessiter de supprimer certaines opérations existantes : qu’elles soient devenues trop coûteuses en calcul, trop lentes, ou tout simplement impossibles (par exemple si une clé de partitionnement est désormais obligatoire). Ces cas là sont à anticiper le plus possible, pour pouvoir s’organiser avec les consommateurs de données.
Modifications rétro-compatibles dans les évolutions mineures
Si vous utilisez une gestion de version sémantique avec des versions majeures et mineures, il est possible de pousser des évolutions dans les versions mineures, sous condition qu’elles soient rétro-compatibles.
Nous vous encourageons à suivre cette approche car elle permet d’ajouter de la flexibilité à vos services.
Même si les changements sont — en principe — transparents, attention à tout de même bien communiquer avec les consommateurs, notamment pour savoir quand telle version est déployée dans tel environnement. Cela vous évitera de perdre du temps. Les outils d’API management peuvent fournir cette fonctionnalité, mais une page wiki et des mails feront tout aussi bien l’affaire.
Cette approche doit toutefois être appliquée avec discernement : si les évolutions majeures sont trop compliquées à mettre en œuvre, le risque est de vouloir faire passer le maximum de changements dans des versions mineures, en tordant les contrats de service. Cela se remarque facilement à la lecture de la documentation où l’on se retrouve avec des phrases telles que "le paramètre montant
représente la somme de l’opération, sauf si la valeur est -1
dans ce cas cela signifie qu’il faut annuler l’opération précédente".
Le fait qu’on soit tenté d’aller dans cette direction est une indication claire qu’il faut travailler pour fluidifier les montées de versions majeures, voire qu’il faut en faire plus souvent.
Quand et quoi communiquer ?
Le cas idéal est toujours celui où les contrats d’interface sont définis en commun entre producteur et consommateurs. Dans cette situation, la communication est permanente et les problèmes sont identifiés au plus tôt.
Dans tous les cas, il faut communique officiellement dès qu’une interface a été définie, avant même que le code soit déployé ou même écrit. Pour être utilisable par les consommateurs, il ne faut pas vous limiter à un descriptif où à un contrat d’interface mais vous devez fournir :
- un contrat d’interface formalisé et complet (y compris les cas d’erreurs), peu importe le format tant qu’il est utilisable par vos utilisateurs (swagger, WSDL, RAML…) ;
- un document expliquant le service : si le contrat d’interface peut être suffisant pour bidouiller un appel par essai et erreur, se servir correctement d’un service nécessite de comprendre sa logique, et pour cela rien ne remplace du texte et des schémas ;
- le SLA ;
- des données d’exemples d’entrée et de sortie ;
- des mocks permettant de simuler des appels, qu’ils soient générés à partir de votre outil de description ou qu’ils soient codés à la main ;
- les personnes à contacter en cas de questions et de problèmes, en s’assurant qu’ils soient disponibles et motivés pour remplir ce rôle.
Si un seul de ces éléments manque, vous allez faire perdre du temps aux consommateurs et à vous-même : cela revient à avoir du code sans test et/ou sans documentation.
Un middleware pour gérer la compatibilité ?
Une des solutions mise en avant par les vendeurs d’ESB et d’API management consiste à gérer la rétro-compatibilité dans leur outil plutôt que dans du code application. En effet, ces outils proposent souvent des fonctionnalités spécifiques permettant de câbler des appels à l’aide d’un outil graphique ou d’un DSL. Il ne s’agit pas de gérer le routage entre deux instances — pour lequel ce type d’outil est bien adapté — mais bien d’implémenter les règles de compatibilité.
On ne traitera pas ce sujet en détail ici car il nécessiterait un article à lui tout seul.
Pour résumer notre approche : cela peut être pertinent mais uniquement à petite dose et quand le code de compatibilité est simple et très peu métier. Faites particulièrement attention quand ce travail est à la charge d’une équipe transverse : rappelez-vous qu’elle connaît moins bien le métier, et que vous ne maîtrisez pas son planning.
À quel rythme ?
Une fois qu’on sait comment faire, la prochaine étape est le "quand ?".
La solution la plus adaptée dépend d’un projet à l’autre, et varie dans la vie des projets : quand un projet devient mature, ses services vont avoir tendance à évoluer de moins en moins vite. D’autre part il s’agit d’un choix qui n’a pas d’impact global au niveau du SI. Il est donc possible de déléguer cette décision au niveau du groupe formé par le projet qui expose les services et les consommateurs de ses différents services.
Il y a deux stratégies possibles :
Rythme cadencé
Il s’agit de prévoir les évolutions de services à un rythme régulier, en général tous les X mois. Ce rythme ne correspond pas à celui de livraison du projet : il peut livrer régulièrement sans modifier les services qu’il expose. Par ailleurs, il s’agit de cadencer la possibilité de faire évoluer les services : ce n’est pas parce qu’on a réservé un créneau qu’il faut forcément s’en servir. S’il n’y a rien à changer, alors on le passe.
Cette possibilité est plus contraignante pour le projet qui expose, mais la prévisibilité permet aux consommateurs de s’organiser, voire de provisionner les budget de migration.
Elle est par exemple la plus indiquée dans un contexte non agile où on préfère la prévisibilité à la vitesse, particulièrement quand un rythme global est imposé à tout le SI.
Rythme opportuniste
Dans cette stratégie on fait évoluer le service quand on en a besoin.
Il ne s’agit pas de le faire à l’arrache : il faut bien entendu anticiper les modifications, et laisser aux consommateurs le temps de migrer : on ne dégrade pas la qualité sous prétexte qu’on est plus adaptable.
Mais plutôt que de prévoir des créneaux à l’avance, on préfère le faire en cas de besoin.
Cette approche convient le mieux en début et en fin de projet :
- au début les changements sont les fréquents, pouvoir agir à la demande est plus pratique ;
- en fin de projet lorsque les choses se sont stabilisées, les modifications deviennent occasionnelles.
Versions majeures et mineures
Il est possible de mixer les deux approches :
- une approche cadencée pour les versions majeures;
- une approche opportuniste pour les versions mineures.
Ainsi, les changements bloquants sont anticipés, et on peut fournir au plus tôt les changements transparents.
Rythme entre zones du SI
Le cas des zones du SI va complexifier le tableau : les services qui restent à l’intérieur d’une même zone vont avoir tendance à bouger plus rapidement que les services entre zones. On peut donc avoir une politique différente pour les deux types de services.
Si un consommateur est bloqué sur une ancienne version ?
La question ne se poserait pas dans un monde parfait, mais en pratique, elle arrive régulièrement : que faire si un projet ne peut pas évoluer et que la solution la plus évidente serait de conserver pour une version de vos service pour lui ?
Analyser comment vous en êtes arrivé là
Tout d’abord analysez comment cela a pu se produire.
En effet, cette situation est le signe clair que quelque chose s’est mal passé. Souvent elle n’est pas la conséquence d’un évènement ponctuel mais d’une longue chaîne de décisions qui a petit à petit rendu le problème inévitable. Faire en sorte que cela ne se reproduise pas est primordial : si vous vous contentez de traiter le symptôme, le problème se reproduira.
En général, cette situation trouve son origine dans une mauvaise priorisation, et la solution va donc passer par une meilleure implication du métier. Si les demandes de mises à jour sont poussées par les développeurs elles seront facile à ignorer. En revanche si le métier a compris que faire en sorte de migrer les consommateurs de service va permettre que les développeurs passent moins de temps en maintenance et plus de temps à développer de nouvelles fonctionnalités, ils seront probablement écoutés.
Traitez le problème
Ensuite, en anticipant sur la partie suivante, nous allons parler budget. Avant de choisir cette solution, il faut se rappeler que ce type de compatibilité a un prix. Ce prix ne se solde pas en une fois mais il s’agit d’un surcoût qui s’ajoute à chaque évolution pour maintenir la rétro-compatibilité. Par ailleurs, ce prix va augmenter au fur et à mesure que la version gelée s’écarte de la version standard et que la rétro-compatibilité devient de plus en plus complexe.
Ce prix est de trois ordres :
- en argent pour le temps passé ;
- en complexité dans le code pour gérer la compatibilité ;
- en motivation pour les développeurs qui doivent prendre en compte cette contrainte lors de chaque évolution.
Si l’on veut conserver une rétro-compatibilité pour un consommateur, la seule solution viable revient à lui faire financer ce budget. Cette approche présente trois avantages :
- elle est dissuasive car les projets sont incités à migrer ;
- elle est équitable car c’est le demandeur qui paie ;
- elle va dans le bon sens car, avec le prix qui va augmenter avec le temps, il y a l’espoir que le consommateur finisse par sauter le pas et se décide à migrer.
Le dernier avantage ne devrait pas avoir à s’appliquer, en théorie, car le maintien d’une compatibilité devrait durer un temps limité, fixé dès le début. Dans les faits, lorsqu’un projet décroche une telle exception, il est tentant de jouer les prolongation, l’augmentation du prix est donc là pour contrebalancer cette tendance.
Ne pas faire payer le consommateur revient à donner une prime à ceux qui ne jouent pas le jeu : si vous ne migrez pas, non seulement vous n’avez plus à vous embêter, mais en plus vous ne payez rien !
Une des solutions alternatives consiste à demander au projet consommateur d’implémenter lui-même la compatibilité dans un module dont il a la responsabilité et qu’il déploiera chez lui. Cette option simplifie l’organisation mais ne fonctionne que dans certains cas : par exemple si un service complexe est découpé en plusieurs services, il peut devenir impossible d’implémenter une rétro-compatibilité hors de l’application maître sans dégrader les temps de réponse.
Évitez la contagion
Si, malgré tout, vous faites le choix de garder une version pour un consommateur, il est très important de tout faire pour faire migrer toutes les autres. En effet, une fois que l’exception sera connue, d’autres vont être tentés de s’engouffrer dans la brèche. Économiquement cette approche est attirante car le prix de la maintenance est alors réparti entre tous les projets, à la limite un projet qui voudrait arrêter de migrer aurait même intérêt à en convaincre d’autres pour diviser sa note.
Cette approche est très dangereuse : plus il y a de consommateurs bloqués sur une version, plus il y a de chances que vous ayez à maintenir cette version longtemps, voire indéfiniment.
C’est l’un des cas où la gouvernance stratégique a sa place : l’optimisation globale du SI s’oppose directement à des solutions opportunistes.
Qui paie ?
Une fois toutes les bonnes résolutions prises, reste une question, probablement la plus importante : qui paie ?
Ici le mieux est probablement d’avoir une règle unique pour tous les projets pour limiter le nombre de fois où l’on rouvre ce débat. Les prises de décision sur les questions de budget étant extrêmement difficiles, cela limite les conflits, même si à chaque cycle budgétaire, la tentation est grande de tout remettre en cause.
Budget des consommateurs
Quand un consommateur de services a besoin de nouvelles fonctionnalités, il n’aura pas de soucis à financer la migration vers la nouvelle version d’une API qui va lui fournir ce dont il a besoin.
Les questions de budget surviennent quand le consommateur est satisfait de ce qu’il a déjà et qu’il faut migrer à la version suivante. Le problème se pose plus davantage pour les projets passés en maintenance, particulièrement en TMA.
Du point de vue du métier, qui a généralement la main sur les questions budgétaires, cette migration n’apporte rien, il est donc logique qu’il soit réticent à dépenser de l’argent pour cela.
Il s’agit du même arbitrage que celui qui se pose pour traiter la dette technique d’un projet, sauf qu’on se place ici au niveau du SI. Cela rend les choses plus compliquées car les intervenants sont plus nombreux. Le critère primordial n’est pas de fâcher le moins de monde possible, mais de s’assurer que le budget soit là pour que les migrations aient bien lieu en temps et en heure.
Nous avons rencontré quatre approches :
- Si la roadmap du projet qui expose les services est suffisamment claire et prévisible, il est possible de préallouer le coût des migrations dans chaque projet, au même titre que les autres dépenses externes comme l’infrastructure.
- Les consommateurs demandeurs de la nouvelle version peuvent payer pour faire migrer les autres. Cela a pour effet d’augmenter le prix des demandes d’évolutions, et donc diminier leur nombre.
- Un pot commun sous forme d’une taxe "X% du budget" appliquée à tous les consommateurs. Cela permet aux gros projets d’aider à financer les migrations des petits, pour que ceux-ci ne bloquent pas.
- Un pot commun à l’ensemble des projets. Cela permet d’avoir un pilotage par les risques à l’échelle du SI, mais l’arbitrage est difficile et peut être coûteux.
Une chose à surveiller dans les deux dernières approches : comme ce n’est pas le projet qui paie, il peut être tentant de gonfler la note, en profitant de la migration pour traiter d’autres sujets. Sans aller jusqu’à demander de comptes détaillés, gardez donc l’œil ouvert.
Budget du projet qui expose les services
Quand un projet expose des services, il le fait pour les autres, que ceux-ci contribuent à son budget.
La manière dont les consommateurs financent les évolutions influe sur le rapport de force vis-à-vis du projet : plus l’un d’entre eux a le sentiment de dépenser d’argent, plus il va vouloir se faire entendre lors des décisions. Ce n’est pas une mauvaise chose en soit — il est normal que certains projets aient plus d’influence que d’autres —, mais c’est à prendre en compte.
Trois approches :
Faire payer les demandeurs d’une évolution
C’est l’approche la plus directe, cependant elle présente deux effets secondaires :
- désavantager les petits projets par rapport aux gros ;
- provoquer une forme d’attentisme : si un projet a besoin d’un nouveau service mais qu’il sait que celui d’à côté l’a déjà demandé, il a tout intérêt à se taire et à laisser l’autre payer, vu qu’il y aura aussi accès.
Sur ce budget, une part est réservée pour financer la maintenance de l’existant.
Un pot commun
Il s’agit de mettre en commun les ressources des différents projets. Deux axes simples pour la répartition :
- une taxe "X% du budget";
- Au pro rata de la consommation de service (au nombre d’appels).
La première alternative désavantage les gros projets s’ils utilisent peu les services. La deuxième paraît plus juste du point de vue d’une approche service, mais elle ne reflète pas le coût réel pour le projet.
Composer les deux approches ?
Il est également possible de composer les deux approches : faire payer les demandeurs pour les évolutions (version N) et un pot commun pour la maintenance (version N-1).
C’est une approche tentante car elle semble plus juste, cependant elle demande de savoir dans quelle case mettre chaque tâche. Elle peut être intéressante sur des très gros projets en V, où les aspects budgétaires sont suivis de près et où la maintenance fait l’objet de chantiers spécifiques. Cependant dans des projets plus petits et agiles qui ont appris à refactorer en permanence, mieux vaut une approche plus basique qui évitera de bureaucratiser le projet.
À retenir
La gestion de version de vos services est avant tout un sujet de gouvernance, de planning et de budget. Et cette question ne peut pas se régler au niveau des projets mais globalement au niveau du SI. Suivant votre réussite dans ce domaine votre SI évoluera sans douleur ou accumulera de la dette.
Le plus important est de s’appuyer sur quelques principes directeurs et d’être capable d’arbitrer rapidement quand des questions se posent.
Les points d’attention :
- Exposer un service c’est créer un point d’adhérence entre deux systèmes, il faut donc le faire à bon escient.
- Les changements de versions rythment la vie de votre SI, il faut savoir le faire au bon tempo tout en prenant garde de ne laisser aucun projet en arrière.
- Exposer et consommer des services a un coût, et suivant vos pratiques ils ne se budgètent pas de la même manière.