From 701de65fdb9d1fe8fcb91fe5b6cf6322f352f3a1 Mon Sep 17 00:00:00 2001 From: github-actions-bot Date: Sun, 4 Feb 2024 22:53:26 +0000 Subject: [PATCH] Updates --- 404.html | 2 +- 404/index.html | 2 +- _next/static/chunks/nextra-data-en-US.json | 2 +- .../turn-the-ship-around-8c5721632809e0b7.js | 1 - .../turn-the-ship-around-d64d6fc7a5cccebf.js | 1 + .../_buildManifest.js | 2 +- .../_ssgManifest.js | 0 books/continuous-discovery-habits/index.html | 4 +- .../designing-cloud-data-platforms/index.html | 4 +- .../index.html | 4 +- books/effective-kafka/index.html | 4 +- books/effective-typescript/index.html | 4 +- .../index.html | 4 +- .../learning-domain-driven-design/index.html | 4 +- books/learning-to-scale/index.html | 4 +- books/monolith-to-microservices/index.html | 4 +- books/outcomes-over-output/index.html | 4 +- books/refactoring/index.html | 4 +- books/reinventing-organizations/index.html | 4 +- books/team-topologies/index.html | 4 +- books/the-design-of-web-apis/index.html | 4 +- .../index.html | 4 +- books/turn-the-ship-around/index.html | 42 ++++++++++++++++++- books/unit-testing/index.html | 4 +- index.html | 4 +- 25 files changed, 79 insertions(+), 41 deletions(-) delete mode 100644 _next/static/chunks/pages/books/turn-the-ship-around-8c5721632809e0b7.js create mode 100644 _next/static/chunks/pages/books/turn-the-ship-around-d64d6fc7a5cccebf.js rename _next/static/{WPsMrYQh3kDrKZ73vFyZ6 => y0tG3CL4iHFFu9Y5lT1wU}/_buildManifest.js (97%) rename _next/static/{WPsMrYQh3kDrKZ73vFyZ6 => y0tG3CL4iHFFu9Y5lT1wU}/_ssgManifest.js (100%) diff --git a/404.html b/404.html index f50ff8a8..f7b5a623 100644 --- a/404.html +++ b/404.html @@ -1 +1 @@ -404: This page could not be found

404

This page could not be found.

\ No newline at end of file +404: This page could not be found

404

This page could not be found.

\ No newline at end of file diff --git a/404/index.html b/404/index.html index f50ff8a8..f7b5a623 100644 --- a/404/index.html +++ b/404/index.html @@ -1 +1 @@ -404: This page could not be found

404

This page could not be found.

\ No newline at end of file +404: This page could not be found

404

This page could not be found.

\ No newline at end of file diff --git a/_next/static/chunks/nextra-data-en-US.json b/_next/static/chunks/nextra-data-en-US.json index b222187b..d883196a 100644 --- a/_next/static/chunks/nextra-data-en-US.json +++ b/_next/static/chunks/nextra-data-en-US.json @@ -1 +1 @@ -{"/books/continuous-discovery-habits":{"title":"Continuous Discovery Habits: Discover Products that Create Customer Value and Business Value","data":{"part-i---what-is-continuous-discovery#Part I - What is continuous discovery":"","1---the-what-and-why-of-continuous-discovery#1 - The what and why of continuous discovery":"Les équipes produit font deux activités distinctes : la discovery où il s'agit de décider de ce qui sera fait, et la delivery où il s'agit d'implémenter effectivement ces choses.\nLa plupart des entreprises investissent lourdement sur la delivery, mais peu sur la discovery pour bien choisir quoi construire.\nUn peu d'historique :\nDans les temps “anciens” la discovery se faisait exclusivement par les business leaders, qui décidaient de budgéter une fois par an. Les projets étaient à la fois hors délai, et servaient mal les besoins des utilisateurs.\nEn 2001, un groupe de développeurs a écrit le manifeste agile, qui poussait notamment l'idée de cycles courts avec un feedback rapide. Leur idée s'est répandue, mais les business leaders avaient du mal à lâcher la prise de décision sur ce qui doit être produit (discovery).\nPetit à petit, on en est arrivé à l'idée que la discovery doit être de la responsabilité des product managers plutôt que des business leaders, puis enfin de l'équipe produit plutôt que du product manager.\nOn a aussi raccourci le cycle de discovery jusqu'à co-créer la discovery avec le client sur une base continue plutôt que de valider les idées qu'on a au préalable. C'est ça qu'on appelle la continuous discovery.\nNDLR : on a ici une idée similaire à celle du DDD à propos de la collaboration permanente avec les domain experts, qui apportent leur expertise du domaine pendant que l'équipe produit apporte son expertise technique et d'analyse dans le cadre d'une co-création.\nQuand on parle de l'équipe produit, on parle ici de tous ceux qui sont en charge de créer le produit : ça inclut les product managers mais aussi les software engineers, les designers et tout autre rôle qui peut être rattaché à la construction de features pour un produit donné (product analyst, user researcher, customer success etc.).\nLe framework proposé dans ce livre est à destination d'un product trio composé au moins d'un product manager, d'un designer, et d'un software engineer.\nLe product trio peut être un quatuor ou même un quintuor pour être plus représentatif, mais il faut noter qu'il y a ici un trade-off : plus il y a de monde et plus on est inclusif sur les sensibilités permettant d'obtenir une discovery fine, mais en même temps plus on a va aussi prendre du temps pour prendre les décisions. 3 semble un nombre raisonnable la plupart du temps.\nIl y a 6 mindsets nécessaires pour adopter cette méthode :\n1 - Outcome-oriented : on s'intéresse à l'outcome (impact sur les utilisateurs et le business) plutôt qu'à l'output (nombre de features produites).\n2 - Customer-centric : les besoins de l'utilisateur sont un enjeu central, au même niveau que les enjeux business.\n3 - Collaborative : accepter la nature cross-fonctionnelle des équipes produit. Plutôt que le PM décide, le designer design, et le développeur code, on a chacun qui apporte son expertise au processus de discovery.\n4 - Visual : utiliser des supports visuels pour tirer parti de nos capacités spatiales de raisonnement.\n5 - Experimental : adopter une approche scientifique expérimentale en listant les hypothèses et en récoltant des preuves.\n6 - Continuous : plutôt que de voir la discovery comme quelque chose qu'on fait au début du projet, il faut la voir comme quelque chose qu'on fait tout au long du processus de développement, pour éviter de faire fausse route régulièrement.\nLes techniques modernes de discovery sont en fait déjà existantes dans de nombreuses entreprises. Ce qui est plus rare c'est l'aspect structuré et continu de ces techniques. Par conséquent la définition de la continuous discovery est la suivante :\nAt a minimum, weekly touchpoints with customers\nBy the team building the product\nWhere they conduct small research activities\nIn pursuit of a desired outcome","2---a-common-framework-for-continuous-discovery#2 - A common framework for continuous discovery":"Il y a en général une tension entre l'outcome business et la satisfaction de l'utilisateur, avec un risque de privilégier le 1er au détriment du 2ème, ce qui ne sera pas viable sur le long terme.\nExemple : en 2016, la banque Wells Fargo connaît un scandale. Les employés avaient fait accepter des services supplémentaires aux clients de manière frauduleuse à cause des objectifs uniquement business, sans couplage à la satisfaction utilisateur.\nPlutôt qu'avoir comme objectif d'augmenter à tout prix les revenus, si l'objectif était par exemple d'obtenir des clients qui veulent ouvrir plus de comptes, on aurait pu éviter le problème.\nAutre exemple : quand on navigue sur un site et qu'on est bombardé de pub désagréable.\nSouvent, les product trios prennent un outcome business, et commencent directement à générer des idées. Ils sautent l'étape importante du framing de l'opportunity space.\nOn va appeler dans la suite opportunity space l'ensemble des besoins utilisateur, leurs pain points, et leurs désirs.\nLes désirs sont importants aussi, on ne peut pas les réduire aux besoins. Exemple : manger une glace ou aller à Disneyland sont des désirs.\nIl y a en fait de nombreuses manières d'atteindre notre outcome, et celles-ci dépendent :\ndu framing de notre space opportunity (la manière dont on construit l'espace des problèmes et des désirs).\ndu choix de l'opportunity pour arriver à l'outcome.\nIl faut toujours examiner plusieurs opportinuties, et voir les conséquences de chacune avant de choisir.\nTeresa propose de structurer la discovery au travers d'un Opportunity Solution Tree (OST).\nIl s'agit d'une représentation graphique sous forme d'arbre avec l'outcome en haut, les opportunities possibles en dessous, les solutions possibles associées à chaque opportunity, et les assuption tests qui permettent de valider chaque solution. outcome ← opportunity ← solution ← assumption test\nCe modèle permet :\nDe résoudre la tension entre les besoins du customer et les besoins business.\nOn choisit l'outcome business et on liste ensuite toutes les opportunities qui permettent d'y parvenir, pour être sûr de satisfaire forcément les deux à la fois.\nD'aider à construire une connaissance partagée pour le product trio.\nNous avons tendance en général à sauter sur la première solution qui nous vient en tête, et à la défendre ensuite contre l'avis des autres. Ces crispations finissent dans une situation où PM décide parce qu'il a le dernier mot, au lieu d'une vraie collaboration.\nEn gardant sous les yeux une représentation graphique de toutes les possibilités, on s'évite d'en considérer une comme étant la “notre” à défendre à tout prix.\nD'aider le product trio à adopter un continuous mindset.\nVu qu'on a dans notre OST de plus petites opportunities qui permettent de réaliser une plus grosse opportunity, ça nous permet de délivrer de la vraie valeur à chaque sprint, plutôt qu'un bout de quelque chose de gros qui est censé délivrer de la valeur plus tard => On est vraiment agiles.\nDe prendre de meilleures décisions.\nVu qu'on a sous les yeux les possibilités, y compris celles qu'on a déjà explorées, on évite de tomber dans des biais qui vont fausser notre décision.\nPar exemple : à chaque fois qu'on a une nouvelle demande client, tomber dans le travers de se demander si on arrête tout pour l'implémenter ou non, au lieu de garder la vue d'ensemble.\nA ce propos, elle conseille un livre à lire juste après le sien : Decisive de Chip et Dan Heath.\nD'obtenir des cycles d'apprentissage rapides.\nOn dit souvent que les PM doivent définir le problème, et les développeurs apporter la solution. C'est une erreur. Pour être efficace, il faut que les mêmes personnes (product trio) soient en charge des deux.\nl'OST permet de visualiser les deux, et à chaque fois qu'une solution est implémentée et ne permet pas de satisfaire l'opportunity, on peut la garder en tête tout en sachant pourquoi ça n'avait pas marché, et pouvoir basculer sur une solution mieux pensée.\nDe construire une confiance dans le fait de savoir quoi faire ensuite.\nOn peut facilement se rendre compte qu'on n'a pas assez d'opportunities et faire des interviews client, ou pas assez de solutions et faire des ateliers pour générer des idées.\nLes meilleures équipes ne travaillent pas de haut en bas (définir un outcome clair, puis créer l'opportunity space, puis chercher les solutions, et enfin définir des assumption tests), mais sur tout l'arbre à la fois : chaque semaine ils affinent chaque élément de l'arbre, et travaillent sur plusieurs éléments en parallèle pour le faire évoluer.\nDe permettre un rapport aux stakeholders plus simple.\nMême avec de la bonne volonté, la direction a tendance à parfois revenir sur des demandes d'output, surtout en période de stress. Pour éviter ça il faut les garder au jus, avec la bonne quantité d'informations.\nIl ne faut ni leur donner trop d'informations, ni leur donner juste les conclusions du product trio. Pour qu'ils se sentent impliqués et puissent donner un feedback, il faut leur donner les principaux éléments qui ont été considérés et choisis ou rejetés. L'OST permet de servir de support visuel pour ça.","part-ii---continuous-discovery-habits#Part II - Continuous discovery habits":"","3---focusing-on-outcomes-over-outputs#3 - Focusing on outcomes over outputs":"L'idée d'utiliser les outcomes plutôt que les outputs existe depuis des décennies, récemment elle a été repopularisée avec les OKR chez Google.\nLes outcomes permettent de donner plus d'autonomie à l'équipe produit, en leur laissant trouver la bonne chose à faire pour régler un besoin business ou une problématique utilisateur, plutôt que leur donner une roadmap de features.\nIl existe 3 types d'outcomes :\nLes business outcomes mesurent l'avancement du business.\nExemple : on a une boutique de vente de nourriture personnalisée pour chien, on veut augmenter la rétention des clients mesurée sur 90 jours.\nÇa peut être soit des aspects financiers (rentrée d'argent, réduction de coûts), soit des initiatives stratégiques (parts de marché ou augmentation de ventes dans une catégorie spécifique, rétention d'utilisateurs etc.).\nCe sont en général des lagging indicators, c'est-à-dire qu'ils vont indiquer le résultat de ce qui s'est déjà produit depuis un moment.\nIls vont impliquer en général une coordination entre plusieurs fonctions business (produit, CSM, vente etc.).\nLes product outcomes mesurent à quel point le produit fait avancer le business.\nExemple : augmenter la valeur perçue de la nourriture pour chien, et augmenter le nombre de chiens qui aiment cette nourriture sont deux indicateurs qui permettront d'atteindre une meilleure rétention des clients à 90 jours.\nCe sont en général des leading indicators, qu'on peut utiliser pour itérer rapidement en fonction des résultats qu'on obtient semaine après semaine.\nIls sont sous la responsabilité de l'équipe produit, et représentent des objectifs sur lesquels l'équipe produit peut agir seule. C'est avec ces outcomes qu'elle avancera le mieux.\nLes traction metrics mesurent l'utilisation d'une feature particulière.\nExemple : augmenter le nombre de propriétaires de chiens qui utilisent la fonctionnalité calendrier.\nOn est bien ici sur de l'outcome, mais qui est limité à un output spécifique. Il y a donc un risque que l'équipe produit soit coincée si l'output choisi dont on mesure l'utilisation n'était pas le bon.\nEn général l'équipe produit avancera mieux avec un product outcome, mais il existe 2 cas où les traction metrics peuvent être utiles :\nQuand on a un product trio plutôt junior, limiter leur responsabilité en fixant un output particulier à optimiser peut être pertinent.\nSi on a un produit déjà mature (dont on a déjà fait pas mal de discovery pour défricher), et qu'on est sûr qu'une feature particulière est très utilisée, on peut vouloir l'optimiser et donc faire de la discovery avec une traction metric.\nLe choix des outcomes de l'équipe produit est le résultat d'une négociation entre le product trio et le product leader.\nLe product leader (par exemple CPO) amène la connaissance de ce qui est utile à ce moment là pour l'organisation, alors que le product trio amène la connaissance des clients et de la technique.\nLa négociation porte sur des outcomes et pas des outputs ou des solutions, et ne doit pas permettre au product leader de les réduire à des traction metrics. Il peut par contre demander à restreindre le champ, par exemple “augmenter le nombre de chiens qui aiment la nourriture dans telle ou telle région”.\nLes deux vont convenir d'une métrique à augmenter d'une certaine valeur, et en fonction de celle-ci, éventuellement aussi des ressources (software engineers) pour mener à bien cet outcome.\nLe fait que la participation des équipes à la définition de leurs outcomes apporte de meilleures performances est appuyé par des études.\nOn peut avoir des performance goals qui sont des objectifs mesurables et challenging (exemple : augmenter l'engagement de 10%), et des learning goals qui sont des objectifs non mesurés (exemple : découvrir les opportunités qui drivent l'engagement).\nLes études ont montré que les performance goals étaient plus efficaces quand la stratégie était déjà connue et qu'on avait identifié une métrique pertinente grâce à une précédente discovery, mais que les learning goals étaient plus efficaces dans le cas contraire.\nQuelques anti-patterns à éviter :\nAvoir trop d'outcomes en même temps : on s'éparpille en avançant un peu sur chacun des outcomes, mais on n'a pas de gros impact sur l'un d'entre eux. En général, le mieux est de chercher à satisfaire un outcome à la fois.\nFaire du ping pong entre plusieurs outcomes : on a tendance à basculer d'outcome en outcome à cause d'une culture du firefighting où un besoin client en chasse un autre. Rester sur le même outcome pendant plusieurs trimestres permet de profiter de ce qu'on a appris dans le 1er et d'être vraiment efficace sur les 2ème et 3ème trimestres.\nDéfinir des outcomes individuels plutôt que des outcomes pour le product trio : on a parfois des objectifs individuels pour obtenir de la rémunération, portant sur des outcomes business pour le PM, des outcomes UX pour le designer, et des outcomes techniques pour le tech. Ces objectifs empêchent le bon fonctionnement du product trio. Il vaut mieux des outcomes d'équipe.\nChoisir un output au lieu d'un outcome :\nVu qu'on est habitués à utiliser des outputs, on va avoir tendance à revenir à ça. Le bon critère à regarder pour voir si on est plutôt vers l'outcome ou l'output c'est l'impact.\nUn bon début c'est de s'assurer que notre outcome représente un nombre.\nSe focus sur un seul outcome au détriment de tous les autres : il faut en choisir un à la fois à faire avancer, mais il ne faut pas oublier de monitorer les autres outcomes pour s'assurer qu'ils ne se dégradent pas.\nPar exemple l'acquisition de clients, en vérifiant en même temps que leur satisfaction ne baisse pas.","4---visualizing-what-you-know#4 - Visualizing what you know":"Quand le product trio commence avec un outcome, il va d'abord créer un experience map pour représenter le workflow lié à cet outcome, et rassembler les connaissances actuelles du trio.\nIl faut définir le scope plus ou moins large pour notre experience map : un scope très large permettra d'explorer des marchés adjacents, et un scope plus étroit permettra de se cantonner à notre produit. En général on cherche quelque chose d'intermédiaire.\nExemple : On a une application de streaming, et notre outcome c'est d'augmenter le nombre de minutes regardées. Un scope large serait “Comment est-ce que les clients se divertissent en général ?”, et un scope étroit serait “Comment est-ce que les clients se divertissent avec notre service ?”. Quelque chose d'intermédiaire peut être “Comment est-ce que les clients se divertissent avec de la vidéo ?”.\nL'experience map doit représenter les étapes du point de vue du customer.\nPar exemple, si notre scope c'est “Comment est-ce que les clients se divertissent avec de la vidéo ?”, on peut commencer par imaginer quelqu'un qui nous partage le nom d'un contenu vidéo, puis on fait une action de recherche. On peut éventuellement être face à des problèmes pour trouver le contenu, et ainsi de suite.\nIl contient uniquement du contenu dessiné. Les mots étant moins précis, moins spécifiques que les images, on va dessiner des boîtes, des flèches, de petits pictogrammes etc. pour constituer nos étapes. Ne pas savoir dessiner n'est pas un problème.\nBien que ce soit contre-intuitif, il est plus efficace de construire l'experience map d'abord chacun de son côté pour ensuite mettre en commun. Si on travaille ensemble dès le début, la dynamique de groupe va faire que certains ne vont pas s'exprimer pleinement.\nPour la mise en commun :\nOn va d'abord chacun expliquer notre map aux autres. On pose des questions pour clarifier, mais le but n'est pas de “défendre” sa vision.\nEnsuite on construit un schéma commun qui va être la combinaison des maps initiaux. Il n'est pas question d'en choisir un des trois et d'avancer avec ça.\nOn récupère tous les noeuds de chacun des maps. Ce sont les événements qui représentent des étapes.\nOn crée un nouveau map dans lequel on met tous ces noeuds.\nOn fusionne les noeuds similaires.\nOn relie les noeuds par des flèches. Pas seulement le happy path mais aussi les chemins d'échec, qui vont créer des frustrations chez le client.\nOn ajoute du contexte sur ce que pensent, ressentent les clients, toujours sous forme visuelle.\nQuelques anti-patterns à éviter :\nS'embourber dans de longs débats sur des détails : quand c'est le cas, il vaut mieux dessiner ce que chacun veut dire pour faire ressortir clairement le désaccord (ou bien souvent l'accord parce qu'on était en quiproquo), et passer à la suite.\nUtiliser des mots à la place des dessins : les dessins font appel à une autre partie de notre cerveau et sont ici plus efficaces. Avec le temps, on se débrouille pour dessiner de moins en moins mal.\nAller à la suite en considérant que notre map représente la vérité : c'est juste un brouillon, on va le retoucher un grand nombre de fois à mesure qu'on parle aux clients.\nOublier d'affiner notre map commune à mesure que nos connaissances augmentent : même en assistant aux mêmes réunions avec les clients, si on ne refait pas le point sur la map régulièrement, chaque membre du trio retiendra des choses différentes, et aura en tête une experience map différente.","5---continuous-interviewing#5 - Continuous interviewing":"Bien souvent, les clients ne savent pas à l'avance les solutions qu'ils veulent.\nEn parlant aux clients, notre but est d'obtenir une meilleure compréhension de l'opportunity space, pas de leur demander quelle feature ils aimeraient (l'espace des solutions).\nIl est important que le client nous parle de quelque chose qui l'intéresse. Donc la première chose à obtenir de lui c'est quels sont les besoins, pain points, et désirs qui comptent le plus pour lui. On pourra ensuite choisir des sujets à aborder parmi ceux-là.\nOn est parfois déçu de ne pas pouvoir faire parler le client de ce qu'on veut, ou même parfois d'avoir des clients non coopératifs. Ce n'est pas très grave : on en aura au moins un autre la semaine prochaine.\nEn posant des questions directes aux gens, on peut bien souvent obtenir une réponse fausse, parce que le cerveau a tendance à rationaliser pour reconstruire des informations manquantes, ou transformer les choses pour les mettre en cohérence avec la vision globale de la personne, la perception qu'elle a de son identité etc.\nCeci est démontré par des décennies de recherches sur la manière d'interroger les personnes.\nExemple : Teresa pose à une personne la question de savoir quel critère elle utilise pour s'acheter des jeans. Elle répond “le fit” comme critère numéro 1. Puis Teresa lui demande de lui raconter la dernière fois qu'elle a acheté un jean : c'était sur Amazon. Donc les vrai critères c'est le prix, la marque, la disponibilité rapide. Le fit n'était pas le critère numéro 1 pour l'achat.\nEn fait il va falloir distinguer les research questions qui sont les questions directes qu'on poserait pour obtenir des informations sur l'opportunity space, et les interview questions qui sont celles qu'on va réellement poser.\nLes interview questions vont consister à demander au client de nous raconter des cas concrets de choses qu'il a pu faire sur le sujet qui nous intéresse.\nExemple : Au lieu de demander “Quel critère utilise-tu pour acheter des jeans”, on demande “Raconte-moi la dernière fois que tu as acheté des jeans”.\nOn peut aussi faire varier le scope en fonction de ce qu'on recherche, de la même manière qu'avec l'experience map.\nExemple : pour le service de streaming.\nRacontez-moi la dernière fois que vous avez utilisé notre service de streaming. => en apprendre plus sur notre produit pour l'améliorer.\nRacontez-moi la dernière fois que vous vous êtes diverti en regardant du streaming. => comprendre comment on se place par rapport aux concurrents.\nRacontez-moi la dernière fois que vous-vous êtes diverti. => découvrir avec quoi la catégorie de notre produit est en compétition (exemple théâtre etc.).\nIl faut aider le client à raconter son histoire :\nLes gens sont habitués à parler autant que leur interlocuteur, si on veut les faire parler plus que nous, il faut leur dire dès le début qu'on veut qu'ils racontent leur histoire avec le plus de détail possible.\nIl faut l'aider à avancer dans les étapes :\nNoter les personnages clé, les challenges rencontrés etc. pour pouvoir le relancer.\nUtiliser notre experience map pour suivre son histoire à travers nos propres nœuds, et éventuellement le relancer sur des étapes dont il n'aurait pas parlé.\nSi le client généralise une réponse (“Quel challenge avez-vous eu ?” “En général les challenges sont…”) alors il faut lui demander de revenir à l'exemple concret. On a vu que la généralisation menait à une vision faussée de la réalité.\nPour pouvoir se rappeler des interviews sur la durée, il faut résumer le résultat de chaque interview dans un interview snapshot, qui est une page résumant l'interview.\nSi possible il faut y mettre une photo de la personne qu'on a interviewé, ou une image qui va nous faire penser à elle.\nIl faut aussi y mettre une phrase qui nous a marqué, pour pouvoir nous en rappeler.\nNDLR : un peu comme le channel #thingsclientssaid.\nLa partie quick facts permet d'indiquer de quel type de client il s'agit, en fonction de la catégorisation qu'on souhaite faire de nos clients. Ca permettra ensuite de comparer ce qui a été remonté avec les clients de la même catégorie.\nEnsuite on a de la place pour noter les opportunities.\nSi le client donne des solutions (features) qu'il aimerait, il faut essayer d'en extraire l'opportunity. Par exemple en posant la question : Qu'est-ce que cette feature vous apporterait ?\nIl faut s'efforcer de formuler l'opportunity avec les mots du client, pour que ça représente son point de vue, et non le point de vue de notre entreprise.\nOn a aussi une place pour noter des insights qui ne sont pas des opportunities (ni des besoins, ni des pain points, ni des désirs).\nEt enfin on a une place pour dessiner la story racontée par le client, avec des boîtes et des traits, sur le même modèle que notre experience map.\nIl est très important de faire des interviews de clients chaque semaine. Commencer ou arrêter une habitude est beaucoup plus difficile que d'en maintenir une.\nLe plus difficile pour ça c'est d'automatiser le process de recrutement des personnes qu'on va interviewer. On a parfois des urgences business, des absents dans l'équipe etc. et dans ces moments on a besoin d'avoir son interview de la semaine déjà bookée sans rien faire.\nParmi les manières de recruter :\nLe plus simple est d'avoir un petit bandeau qui s'affiche dans notre application, et qui dit “Est-ce que vous auriez 20mn à nous accorder pour améliorer notre service, en échange de 20€ ?”.\nÇa marche bien si on a beaucoup de trafic, dans ce cas on peut demander le numéro de la personne avec un formulaire.\nSi on a moins de trafic, à la place du numéro on peut proposer un service de scheduling en ligne pour que le client puisse directement réserver un créneau avec nous.\nSi on n'a pas de trafic du tout, on peut toujours utiliser de la publicité qui redirige vers une landing page, où il y aura le formulaire proposant de nous aider.\nUne autre solution est de demander aux équipes customer facing (CSM, sales etc.).\nD'abord on leur demande de rejoindre un de leurs calls 5 mn à la fin, pour prendre une story. Le but est de mettre un pied dans la porte pour qu'ils disent oui facilement.\nUne fois qu'on est plus à l'aise, on peut demander aux équipes CSM de programmer des interviews pour nous. On leur donne des “triggers” qu'on met à jour régulièrement.\nPar exemple : si un client pose une question sur la feature X, programme lui un interview avec nous.\nEnfin, si on a une audience trop petite ou trop difficile à aller chercher, on peut aussi constituer un customer advisory board, c'est-à-dire convaincre un groupe de clients de participer à des meetings réguliers avec nous.\nL'avantage c'est que ça permet de suivre l'évolution de leurs problématiques dans le temps. Le désavantage potentiel c'est qu'on risque d'avoir un échantillon pas forcément représentatif.\nIl faut que les interviews soient faites par l'ensemble du product trio.\nSi l'un des membres se retrouve à être “la voix du customer”, il acquerra trop de pouvoir dans le groupe, et déséquilibrera le processus de décision dans la discovery, avec un “Oui mais c'est ça que le client veut”.\nLe product trio est composé de personnes diverses justement pour prendre les meilleures décisions possibles, chacun amenant sa sensibilité.\nQuelques anti-patterns à éviter :\nCompter sur une seule personne pour le recrutement ou le fait de faire les interviews : si elle n'est pas là, l'habitude d'interviewer s'arrête. Il faut plutôt que chaque membre du trio sache recruter et interviewer.\nPoser des questions “Who, What, Why, How, When” : ça fait de longues interviews et donne des données non fiables. Il faut plutôt préparer des research questions, et des interviews questions correspondantes qu'il faudra poser.\nFaire des interviews seulement quand on en a besoin : l'habitude en elle-même a beaucoup de valeur. Et en plus on aura des réponses rapides.\nPartager ce qu'on apprend avec le reste de l'équipe en envoyant des pages de notes ou des enregistrements des interviews : il vaut mieux utiliser les interview snapshots. On ne peut pas demander au reste de l'équipe de consacrer autant de temps que nous aux interviews.\nS'arrêter pour synthétiser les 6 à 12 dernières interviews : si on est dans un mode continu, on synthétise aussi en continu avec les interview snapshots, pas par batch.","6---mapping-the-opportunity-space#6 - Mapping the opportunity space":"Avoir un backlog d'opportunities est un bon début, mais la priorisation est compliquée parce que le sizing de chaque opportunity est différent, et qu'elles s'overlap parfois. => L'OST répond à ces problématiques.\nIl faut essayer de trouver plus de sub-opportunities à mesure qu'on fait des interviews, pour deux raisons :\nÇa permet de répondre à des opportunities qui paraissent trop grosses.\nÇa permet de délivrer de la valeur en continu, en suivant la philosophie agile.\nDans l'OST, à propos de la relation entre les noeuds parents / enfants :\nLe noeud enfant permet d'avancer sur la problématique du parent.\nDeux nœuds enfants du même niveau doivent pouvoir être résolus chacun de leur côté sans impacter l'autre.\nExemple : Dans le cadre du streaming, “Je ne sais pas comment chercher un show particulier” permet de faire avancer la problématique parente “Je ne trouve rien à regarder”, sans faire avancer l'autre enfant du même niveau “J'ai terminé les épisodes de mon show favori”.\nIl y a deux techniques pour que les opportunities du même niveau soient indépendants, à chaque fois il faut les associer avec des moments distincts dans le temps :\nOn peut reprendre notre experience map, et associer chaque nœud à un nœud d'opportunity top level dans l'OST.\nSi notre experience map n'est pas encore bien formée, on peut parcourir nos interview snapshots, et essayer de trouver des nœuds qui reviennent dans le dessin de la story. Une fois qu'on a ces nœuds on peut les associer avec les noeuds top level de notre OST.\nUne fois qu'on a nos nœuds top level de l'OST, on peut y ajouter nos opportunities à partir de nos interview snapshots.\nAvant d'ajouter chaque opportunity, on revérifie bien qu'elle permet d'avancer sur notre outcome, et que c'est vraiment une opportunity.\nOn les place d'abord simplement en dessous de l'opportunity top level correspondante.\nOn va ensuite, branche top level par branche top level, ajouter de la structure à notre OST :\nOn va trouver des opportunities qui se ressemblent, et on va leur chercher une opportunity parente.\nSi l'opportunity parente n'existe pas, c'est OK de la créer, vu que les enfants eux sont bien issus de vrais interviews.\nSi deux opportunities sont vraiment les mêmes formulées différemment, on peut les regrouper en une.\nOn va ensuite chercher à regrouper nos mini-arbres ensemble avec des parents communs, jusqu'à arriver à l'opportunity top level.\nLe framing de l'opportunity space est une étape importante.\nSi la construction de l'OST nous prend 30mn, c'est qu'on l'a fait un peu vite fait. Mais il ne faut pas non plus y passer des heures et des heures.\nC'est un processus itératif, on va de toute façon remodeler l'OST de nombreuses fois.\nQuelques anti-patterns à éviter :\nCréer les opportunities depuis la perspective de l'entreprise : on peut se demander à chaque nœud de l'OST : est-ce que j'ai entendu un client dire ça ? Ou si on a dû le créer : est-ce qu'un client dirait ça ?\nExemple : un client ne dira jamais “J'aurais bien aimé avoir plus d'abonnements au service de streaming”, mais il pourra dire “J'aurais aimé avoir plus de contenu intéressant”.\nAvoir des sous-arbres verticaux : si on est dans ce cas, c'est\nsoit qu'on a deux opportunities qui sont en fait à peu près les mêmes, et qu'on peut reformuler en une.\nsoit il nous manque des opportunities de même niveau parce que l'opportunity enfant n'est pas suffisante pour résoudre le parent. Dans ce cas, il faudra trouver d'autres opportunities pour ne pas rester sur un sous-arbre vertical.\nDes opportunities ont plusieurs parents : si on a bien nos opportunities top level qui sont vraiment des moments différents dans le temps ça ne doit pas arriver. Il est possible que l'opportunity problématique soit formulée de manière trop générale.\nDes opportunities non spécifiques: des opportunities comme “J'aimerais que ce soit facile à utiliser” ne sont pas assez spécifiques, on peut les rendre plus spécifiques, par exemple “J'aimerais que voir un show soit plus facile”.\nDes opportunities qui sont des solutions déguisées : Il faut qu'il y ait plusieurs solutions possibles à une opportunity, sinon c'est déjà une solution.\nPar exemple : “Je veux passer rapidement les pubs” est en fait une solution. L'opportunity pourrait être “Je n'aime pas voir les pubs”, et auquel on pourra répondre par les passer, mais aussi par “Faire des pubs plus attrayantes” ou encore “Avoir un abonnement sans pub”.\nCapturer des sentiments comme des opportunities : si on a un sentiment qui est donné par le client, c'est qu'il y a une opportunity pas loin. Mais il faut alors chercher la cause de ce sentiment pour avoir l'opportunity.\nExemple : “Je me sens frustré” => Pourquoi => “Je déteste retaper mon mot de passe à chaque achat”.","7---prioritizing-opportunities-not-solutions#7 - Prioritizing opportunities, not solutions":"Il faut chercher à répondre à une seule opportunity à la fois.\nC'est en cohérence avec la philosophie Kanban qui consiste à limiter le nombre de tâches en cours.\nDes études ont montré que limiter le nombre de tâches en cours permet d'avoir une meilleure qualité, de délivrer de manière plus consistante, et d'avoir moins de plaintes côté client.\nGrâce à l'OST on va pouvoir choisir notre prochaine opportunity sans avoir à tout prioriser et re-prioriser.\nOn part du haut, et on compare les opportunities top level, pour en choisir une seule.\nOn descend dans le sous-arbre de celle qu'on a choisi, et on fait pareil : on choisit l'enfant de même niveau le plus prioritaire.\nOn fait ça jusqu'à atteindre le bas de l'arbre : on a alors notre opportunity.\nLes critères pour prioriser sont :\nLe sizing de l'opportunity : l'impact que l'opportunity a sur nos clients.\nPas besoin que ce soit précis : on a juste besoin de pouvoir comparer les nœuds enfants de même niveau entre eux.\nOn peut utiliser comme données : des analytics, le funnel des sales, les tickets de support, des sondages, nos interview snapshots etc.\nIl faut aussi distinguer combien de clients sont touchés, et à quelle fréquence ils sont touchés. Parfois c'est peu de clients très touchés, ou beaucoup de clients peu touchés.\nLes facteurs liés au marché :\nIl s'agit de maîtriser notre positionnement par rapport aux entreprises concurrentes, et éventuellement des segments de marché extérieurs qui pourraient venir grignoter notre marché actuel (par exemple la vidéo par câble qui grignote notre service de streaming).\nEn fonction de notre positionnement actuel, on pourra prioriser soit des enjeux importants pour les sales, soit des enjeux stratégiques pour nous maintenir sur notre marché et en conquérir d'autres.\nLes facteurs liés à l'entreprise : On doit prendre en compte la stratégie, la vision et les valeurs de l'entreprise, pour aller dans le même sens.\nLes facteurs liés au client : Il s'agit de l'impact sur la satisfaction client : on va choisir l'opportunity qui en procurera le plus.\nIl ne faut pas attribuer de note précise à chaque opportunité et pour chaque critère. Les critères permettent d'avoir un débat au sein du product trio, et le choix doit être grossier et subjectif.\nIl est important que les décisions de discovery en général soient considérées par le product trio comme potentiellement modifiables au bout de quelques jours (la discovery est continuous).\nAvec cet état d'esprit on n'a pas besoin de passer trop de temps à choisir l'opportunity. Si elle se révèle mauvaise, on la changera très vite.\nQuelques anti-patterns à éviter :\nRemettre la décision à plus tard, quand il y aura plus de données : on en apprendra plus en examinant les conséquences d'une opportunity qu'on choisit maintenant, qu'en attendant d'avoir plus de données pour un meilleur choix.\nLe mieux est de limiter le temps de décision : on se donne une heure ou deux (ou au pire un jour ou deux) et à la fin du délai on décide de l'opportunity.\nUtiliser principalement un des 4 critères pour le choix de l'opportunity : les 4 critères permettent d'avoir un point de vue différent sur l'importance de chaque opportunity. Il faut les utiliser tous.\nAvoir une conclusion pré-établie, et tenter de la justifier : faire ça est une perte de temps. Il faut faire l'exercice avec une ouverture d'esprit pour s'ouvrir à d'autres perspectives.","8---supercharged-ideation#8 - Supercharged ideation":"Les études montrent que quand on recherche la créativité, les meilleures idées sont parmi les dernières qu'on trouve. Plus on en cherche, et plus on tombe sur des choses originales et pertinentes.\nIl faut donc résister à notre propension à choisir la première idée ou solution qui nous passe par la tête.\nToutes les opportunities ne nécessitent pas d'y passer du temps pour trouver des solutions originales. On en a besoin surtout pour celles qui sont stratégiques, où on veut se distinguer de la concurrence.\nLes études montrent que faire un brainstorming est moins efficace que de chercher des idées chacun de son côté : on trouve plus d'idées différentes, des idées plus originales et plus variées.\nLes raisons sont entre autres le fait d'être poussé à plus chercher quand on est seul, alors qu'on peut se reposer sur les autres quand on est en groupe. Il y a aussi le fait que les premières idées du groupe formatent les suivantes, avec une gêne à proposer des idées à priori pas assez bonnes.\nCeci dit on est moins souvent coincés en groupe, donc on a l'impression d'être plus performants.\nUne solution encore plus efficace est d'alterner les sessions seul et en groupe : on trouve efficacement des idées chacun de son côté, puis on est débloqués grâce à l'apport du résultat des autres, et on peut à nouveau avancer chacun de son côté à partir de là.\nOn est tous performants pour générer des idées. Ça peut prendre un peu de temps au début si on est rouillé mais ça revient toujours.\nPlutôt que de prendre une grosse heure pour trouver des idées, il vaut mieux répartir ça dans la journée et dans différents endroits (entre deux meetings, en marchant après manger etc.).\nOn peut aussi profiter du fait que notre cerveau travaille alors qu'on arrête d'y penser, et le lendemain on tombe sur une bonne idée qui était en gestation.\nOn peut s'inspirer de la concurrence, et aussi d'entreprises dans des domaines qui n'ont rien à voir (bien des problématiques sont communes à différents domaines).\nOn peut aussi considérer le point de vue d‘utilisateurs particuliers : les utilisateurs qui arrivent pour la première fois, les utilisateurs handicapés, jeunes, vieux, vivant loin etc.\nConcrètement pour générer des solutions à nos opportunities :\n1 - On revoit notre opportunity cible et son contexte, on vérifie qu'elle est bien sizée etc.\n2 - On génère des idées seul, avec les techniques ci-dessus.\n3 - On partage nos idées avec l'équipe. Ça peut être en live ou en asynchrone (slack ou autre).\n4 - On répète les étapes 2 et 3, jusqu'à obtenir 15 à 20 idées.\n5 - On élimine les idées qui ne répondent pas à l'opportunity qu'on vise.\n6 - On va dot-voter avec l'équipe : chacun a 3 points à mettre sur les idées de son choix (avec possibilité de mettre sur les mêmes), et on élimine celles qui en ont le moins, jusqu'à arriver à seulement 3 idées.\nLes études montrent que pour ce qui est du choix des idées, on est plus efficaces en groupe.\nOn peut être amenés à faire plusieurs tours de dot-vote pour éliminer les idées.\nPas besoin de consensus sur chaque idée, par contre il faut que chacune des idées choisies aient au moins une personne qui soit enthousiasmée par celle-ci. Si ce n'est pas le cas, il faut revoter.\nQuelques anti-patterns à éviter :\nNe pas inclure une diversité de perspectives : si la plupart des exercices du livre sont faits pour le product trio, il est préférable de faire la génération d'idées avec l'équipe produit entière. On peut même inviter d'autres stakeholders importants. Plus on aura du monde, plus on aura des idées diverses.\nGénérer trop de variations de la même idée : quand on est bloqués, on a tendance à reprendre les mêmes idées un peu différentes pour avoir l'impression d'en avoir plein. Il faut se forcer à en trouver d'autres, y compris en allant chercher ce que font d'autres produits.\nLimiter la recherche d'idées à une seule session : les études montrent que le fait sur un temps plus long, par petites sessions, est plus efficace.\nChoisir des idées qui ne permettent pas de répondre à l'opportunity choisie : avant de dot-voter, il faut bien éliminer les idées sans rapport, même si elles ont l'air intéressantes en elles-mêmes. Sinon on se disperse.","9---identifying-hidden-assumptions#9 - Identifying hidden assumptions":"On a en général tendance à s'accrocher à nos idées.\nParmi les biais ici il y a notamment :\nLe biais de confirmation qui fait qu'on va accorder de l'importance à ce qui confirme notre idée, et peu d'importance à ce qui la réfute.\nLe biais des coûts irrécupérables qui fait que plus on investit dans une idée, plus on pense devoir continuer.\nPour éviter ce phénomène, on a choisi de traiter 3 idées en même temps, et on va itérer rapidement pour en traiter le maximum sans rester longuement sur une idée particulière.\nPour pouvoir tester 3 idées en même temps, on ne peut pas les implémenter à chaque fois toutes. Il faut à la place tester les assumptions sous-jacentes.\nLa plus grande difficulté c'est de trouver ces assumptions.\nIl y a 5 types d'assumptions qui nous intéressent :\nDesirability assumptions : le fait de savoir si nos utilisateurs ont envie de faire ce qu'on imagine qu'ils ont envie de faire à travers notre idée.\nViability assumptions : le fait de savoir si l'idée va vraiment apporter au business suffisamment de valeur (et pas seulement aux clients).\nFeasibility assumptions : le fait de savoir si c'est faisable d'un point de vue technique, mais aussi de potentiels problèmes légaux, culturels etc.\nUsability assumptions : le fait de savoir si les clients vont pouvoir l'utiliser, le comprendre etc.\nEthical assumptions : le fait de savoir si on pourrait causer des problèmes éthiques ou dangereux.\nCa concerne ce qu'on va faire des données qu'on collecte :\nEst-ce qu'on va partager ces données avec des tiers ?\nEst-ce que nos clients comprennent ce qu'on fait de leurs données.\nS'ils le comprenaient est-ce qu'ils seraient d'accord ?\nÇa peut aussi être d'autres types de problèmes :\nEst-ce que notre produit peut devenir addictif et nuire à l'utilisateur ?\nEst-ce que certains utilisateurs seront exclus de notre feature ?\nEst-ce qu'on contribue aux inégalités sociales ?\nEst-ce qu'on expose l'identité de personnes à qui ça pourrait causer du tort ?\nComment les trolls d'internet pourraient-ils détourner ça ?\nCa pourrait aussi être des dommages pour notre business :\nEst-ce que la solution va aider ou nuire à notre marque ?\nEst-ce qu'on comblera les attentes des clients ou est-ce qu'on les laissera déçus ?\nUne question qui marche bien pour trouver les problèmes : “Si le New York Times (ou un autre grand média) publiait un article en première page, détaillant notre solution, l'ensemble de nos échanges internes, les conséquences sur l'écosystème etc. Est-ce que ce serait une bonne chose ou non ?”\nChaque membre du trio peut mettre des assumptions différentes derrière une idée. Pour s'aligner sur les assumptions, on va utiliser le story mapping : cartographier les étapes faites par l'utilisateur pour obtenir de la valeur de notre produit.\n1 - On va partir du principe que la solution existe déjà, et voir l'apport de valeur sur l'utilisateur. Il ne s'agit pas de mesurer la difficulté d'implémentation.\n2 - On va ensuite identifier les acteurs clés : ça peut être plusieurs users dans un réseau social, des acheteurs/vendeurs, un user et un chatbot etc.\n3 - On liste les étapes nécessaires pour chaque acteur, pour que la fonctionnalité qui nous intéresse apporte de la valeur.\n4 - On dispose les étapes en séquence horizontale sur un graphique.\nIl faut respecter l'ordre causal des étapes entre acteurs.\nSi on a des étapes optionnelles on les représente aussi.\nOn représente les successful paths, et s'il y en a plusieurs, on les représente tous.\n5 - On explicite les suppositions résultant de nos étapes. On va passer sur chacune des étapes et se poser la question de chaque type d'assumption (desirability, feasibility etc.). On va en avoir facilement des dizaines.\nExemple : nous explorons 3 solutions pour notre service de streaming. Parmi elles, on commence par “Intégrer des chaînes locales dans notre service (ABC, NBC etc.)”.\nOn part du principe que le service existe déjà, et on liste les acteurs : le client, la plateforme et la chaîne locale partenaire.\nOn liste les étapes :\nLe client arrive pour regarder du sport en direct.\nNotre plateforme montre les choix de contenu possible.\nLe client choisit.\nLa chaîne locale envoie le contenu.\nLe client le regarde.\nOn va ensuite expliciter les assumptions qui en résultent.\nPar exemple pour l'étape 1 “Le client arrive pour regarder du sport en direct”, on a les assumptions suivantes :\nDesirability : notre client veut regarder du sport.\nDesirability : Notre client veut regarder du sport sur notre plateforme.\nUsability : Notre client sait qu'il peut regarder du sport sur notre plateforme.\nUsability : Notre client pense à notre plateforme quand il est temps de regarder du sport.\nFeasibility : Notre plateforme est disponible quand le client veut regarder du sport.\nOn peut aussi tirer des assumptions de viability ou ethical à partir de la story map. Par exemple :\nViability : Intégrer un flux de chaîne partenaire locale ne coûtera pas trop cher.\nEthical : Les clients seront d'accord pour que nous partagions les données de vues avec les chaînes locales.\nIl ne faut pas trop s'inquiéter d'avoir des dizaines d'assumptions. Si on a bien fait notre travail, la plupart seront justes, et l'important c'est surtout de trouver celles qui sont risquées.\nUne autre technique pour identifier les assumptions risqués c'est de faire une session pre-mortem.\nIl s'agit de se placer dans quelques mois, d'imaginer que le produit a été un échec cuisant, et se demander pourquoi.\nPour que ça marche il est important d'imaginer que le produit a été un échec, pas qu'il pourrait l'être.\nUne autre manière de les trouver encore c'est d'utiliser l'opportunity solution tree, en remontant depuis les solutions vers les opportunities, puis vers l'outcome.\nIl s'agit d'utiliser une phrase du genre “La solution permet d'adresser l'opportunity parce que…”, ou “L'opportunity permet de driver l'outcome parce que…”.\nPar exemple :\nAjouter des chaînes locales permettra à nos clients de regarder du sport en direct parce que…\nLes sports que nos clients veulent sont sur les chaînes locales.\nLa plupart des sports populaires sont sur les chaînes locales.\nNos clients ont des chances de vouloir regarder des sports populaires.\nAdresser l'opportunity “Regarder des sports en direct” permettra de faire avancer l'outcome produit “Augmenter les minutes regardées par semaine” parce que…\nLes gens regarderont des sports en plus de ce qu'ils regardent déjà.\nMême s'ils regardent moins les autres choses, les show sportifs sont plus longs et le temps augmentera.\nSi chaque session de visionnage est plus longue, le temps global regardé sera plus long.\nAdresser l'outcome produit “Augmenter les minutes regardées par semaine” permet d'adresser l'outcome business “Augmenter le nombre de renouvellement d'abonnement” parce que…\nLes gens qui regardent longtemps sont plus enclins à renouveler leur abonnement.\nLa combinaison de ces méthodes permet de trouver les assumptions de chaque catégorie. Mais à force, si on devient suffisamment fort, on ne sera plus obligé de les utiliser toutes.\nSouvent, les équipes ont des points faibles, qu'ils peuvent combler par une ou deux techniques. Par exemple remonter l'OST pour les viability assumptions, utiliser les questions à se poser pour les ethical assumptions etc.\nPour prioriser nos assumptions à tester, on va utiliser l'assumption mapping.\nIl s'agit de trouver les assumptions les plus risquées, celles qui impliquent un “acte de foi” (leap of faith).\nOn va placer les assumptions sur un graphique avec en abscisse le niveau de preuves qu'on a sur le fait que notre assumption est vraie, et en ordonnée l'importance de l'assumption pour la réussite de notre solution.\nToutes les assumptions sont certes importantes, mais certaines sont plus problématiques à contourner si jamais elles se révèlent fausses.\nIl n'y a pas besoin d'être précis, tout ce qui compte c'est de placer les assumptions dans le graphique relativement aux autres assumptions.\nIl s'agira ensuite de récolter les assumptions “leap of faith” en haut à droite de notre graphique, de le faire pour les 3 idées et de tester chacune de celles-ci.\nQuelques anti-patterns à éviter :\nNe pas générer assez d'assumptions : Teresa en génère en général 20/30 par idée. On n'aura pas à toutes les tester, mais si n'en génère pas beaucoup on ne trouvera pas non plus les plus risquées.\nFormuler les assumptions de manière négative : on est parfois tenté de formuler une assumption comme “Les clients ne retiendront pas leur mot de passe”, mais cette formulation rendra le test plus difficile. Il vaut mieux formuler ce qui est nécessaire pour que la solution fonctionne (que les clients retiennent leur mot de passe par exemple).\nNe pas être assez spécifique : une assumption comme “Les clients auront le temps” n'est pas assez précise, il vaut mieux quelque chose comme “Les clients prendront le temps de parcourir toutes les options de la page de démarrage”.\nFavoriser certaines catégories au détriment d'autres : si on a par exemple des difficultés de faisabilité, on aura tendance à oublier de tester que la solution est au moins désirable. On oublie aussi souvent les problématiques éthiques etc. Il faut utiliser les catégories pour trouver les angles morts.","10---testing-assumptions-not-ideas#10 - Testing assumptions, not ideas":"Une fois qu'on a notre “leap of faith” d'assumptions pour les 3 idées, il vaut mieux éviter de se précipiter pour les tester.\nParfois les tests ne sont pas pensés pour les assumptions visées, mais pour tester l'idée en entier.\nParfois on teste sur la mauvaise audience, ou on s'éparpille vers des données intéressantes mais sans rapport avec le problème.\nIl faut tester les trois idées en même temps. Si on les traite une par une, on risque de céder à nos biais (confirmation et coûts irrécupérables).\nPour rendre nos assumptions les plus risqués moins risqués, on va collecter des données sur ce que les clients font vraiment dans un cas spécifique, pas sur ce qu'ils disent qu'ils feraient en général.\nIl va s'agir de simuler une mise en situation, et de comparer le comportement de l'utilisateur avec celui que notre assumption aurait sous-tendu.\nExemple : si on reprend notre exemple de plateforme de streaming, et qu'on veut tester l'assumption “Nos clients veulent regarder du sport”. On va pouvoir simuler la situation en présentant un mockup de la page d'accueil de notre plateforme, et leur demander : “Qu'est-ce que vous aimeriez regarder maintenant ?” en leur proposant plusieurs options.\nOn se retrouve souvent avec plusieurs idées qui partagent la même assumption : du coup la traiter permet de traiter plusieurs idées.\nIl est important de définir à l'avance la condition de succès. Si on ne la définit pas à l'avance, on n'aura pas de résultat actionable, et surtout on va céder à nos biais.\nExemple : Pour le test de notre assumption sur les utilisateurs qui veulent regarder du sport, on décide à l'avance que le succès serait d'avoir 4 utilisateurs sur 10 qui choisissent de regarder du sport.\nPour ce qui est du chiffre en question, on va le négocier au sein du product trio. A noter qu'il ne s'agit pas de prouver la chose, mais juste de réduire le risque.\nIl est préférable de commencer par de petits tests pas chers pour avoir un potentiel signal d'échec rapide, plutôt qu'investir beaucoup sur un gros test sans avoir déjà eu un retour.\n1 - On choisit l'assumption la plus risquée pour faire un test et la rendre moins risquée ou l'éliminer.\n2 - On choisit à nouveau l'assumption la plus risquée. Si la précédente est toujours la plus risquée, alors on fait un test plus important sur elle, sinon on prend une autre plus risquée avec un petit test.\n3 - On fait des tests de plus en plus gros sur nos assumptions risquées qui ont survécu jusqu'à ce qu'implémenter l'idée dans notre application soit moins cher que le test.\nAvec de petits échantillons on risque d'avoir des faux positifs ou des faux négatifs, mais ce n'est pas très grave.\nPour alléger ce risque, on peut tenter de diversifier les personnes interviewées, en particulier sur des critères en rapport avec l'assumption testée.\nSi on tombe sur un faux négatif (l'assumption est invalidée alors qu'elle était bonne), on pourra toujours se rattraper sur l'assumption suivante. Au pire on se retrouvera avec un petit redesign ou une idée abandonnée. Mais des idées il y en a des milliers.\nC'est pareil en cas de faux positif (l'assumption est validée alors qu'elle était fausse), on s'en rendra compte au test de assumption suivante, ou au test plus important de cette même assumption qui sera probablement en contradiction avec notre faux positif.\nComparé à ce que fait la science : on utilise ici une méthode proche de la méthode scientifique, mais notre but est d'aller bien plus vite. On réduit simplement le risque plutôt que de rechercher la Vérité.\nSelon Marty Cagan, les meilleures équipes font 15 à 20 itérations de discovery par semaine (qu'est-ce qu'elle veut dire par “itération de discovery” ?).\nPour être efficaces il y a deux outils pour tester rapidement les assumptions :\n1 - Le user-testing non modéré (unmoderated user-testing) : on a un outil qui nous permet de créer un prototype, et d'ajouter des tâches à faire ou des questions. On envoie ça à des utilisateurs qui pourront le remplir quand ils auront le temps. Et on n'aura plus qu'à visionner les vidéos de ce qu'ils ont fait.\nCes outils (elle ne donne pas d'exemples) sont des game-changers : ils permettent de faire en un jour ou deux ce qu'on mettait plusieurs semaines à faire en terme de test d'assumptions.\n2 - Le sondage à question unique (one question survey) : on crée un formulaire avec une seule question, et on l'envoie à nos clients.\nÇa peut être utile par exemple pour trianguler un test d'assumption qu'on aurait déjà fait avec le user-testing non modéré, en posant la question d'une autre manière.\nÇa peut être pour tester les préférences des utilisateurs : “Veuillez sélectionner vos sports préférés parmi la liste”.\nLes mêmes règles d'assumption testing s'appliquent à ces outils : on simule des instances spécifiques pour mettre la personne en situation ou aller chercher un comportement passé précis. Et on ne demande pas ce qu'elle fait en général ou ce qu'elle fera dans le futur.\nOn a aussi parfois les données qu'on cherche déjà dans nos bases de données.\nPar exemple : le nombre de fois où les sports ont été recherchés par les utilisateurs.\nAttention là aussi à définir à l'avance le nombre qui constituerait un critère de succès du test.\nPour aller plus loin sur les types de tests à mettre en place elle conseille deux livres :\nUX for Lean Startups de Laura Klein.\nTesting business ideas de David Bland.\nQuelques anti-patterns à éviter :\nDes simulations trop longues : le but est d'aller vite pour pouvoir itérer dans la bonne direction. Nos tests devraient être complétés en un jour ou deux, ou une semaine max.\nUtiliser des pourcentages au lieu des nombres bruts pour les critères de succès : définir les nombres (total et de succès) permet de garder en tête la fiabilité au cours des différents tests plus ou moins importants.\nNe pas définir suffisamment de critères d'évaluation : il faut au moins le nombre total de personnes à interroger, et le nombre total de succès. Mais parfois si notre test est plus complexe, il peut y avoir plusieurs valeurs à mesurer au total et de succès.\nPar exemple, si on envoie des emails, on peut mesurer ceux qui ouvrent, ceux qui cliquent sur notre lien etc.\nTester avec les mauvais utilisateurs : bien s'assurer que les personnes interviewées ont bien les besoins, pain points ou désirs de l'opportunity visée.\nConcevoir les tests avec un scénario plus difficile que le plus basique : il faut que notre scénario soit celui qui a le plus de chances de marcher, et on le challengera par la suite avec plus de monde et des cas plus complexes. Ca permet d'accorder une vraie valeur à un cas d'échec, plutôt que de se dire que ça aurait pu marcher avec d'autres personnes ou fait autrement.","11---measuring-impact#11 - Measuring impact":"Il ne faut pas essayer de tout mesurer dès le début.\nOn peut passer des semaines à perdre du temps à essayer de tout planifier, choisir le nom des events qu'on track etc. pour se rendre compte que ce n'était pas ce qu'on croyait.\nIl faut commencer par mesurer ce dont on a besoin pour valider nos assumption tests, et pas au-delà.\nExemple : Teresa était dans une université, sur un système permettant d'aider les étudiants à trouver un job.\nSon équipe avait remarqué qu'on posait les mauvaises questions aux étudiants sur l'application, et qu'à cause de ça ils ne s'engageaient pas sur la plateforme.\nIls ont créé un prototype d'une version alternative, et l'ont utilisé pour tester des assumptions :\nLes étudiants feront plus de recherches si on leur pose des questions plus simples.\nLes étudiants verront plus de jobs recommandés.\nLes étudiants postuleront à plus de jobs recommandés.\nPour tester ces assumptions, les critères d'évaluation étaient :\n250 visiteurs sur 500 utiliseront l'interface.\n63 étudiants sur 500 verront un job recommandé.\n7 étudiants sur 500 postuleront à un job recommandé.\nEt pour mesurer ça, ils ont collecté :\n# de personnes qui ont visité la page de recherche\n# de personnes qui ont fait une recherche\n# de personnes qui ont vu au moins un job\n# de personnes qui ont postulé à au moins un job\nIls n'ont pas commencé par mesurer tous les clics mais bien seulement les actions pour valider leurs tests.\nOn peut mesurer le nombre total d'actions, ou le nombre de personnes faisant l'action au moins une fois.\nPour choisir l'un ou l'autre, on peut se demander si une même personne effectue l'action plusieurs fois, est-ce que ça apporte plus de valeur vis-à-vis de ce qu'on recherche.\nIl faut aussi mesurer l'impact sur l'outcome, même si c'est difficile, et ne pas se contenter de leading indicators.\nSi on reprend l'exemple de l'université, les mesures faites représentaient des leading indicators (nombre d'étudiants postulant à un job via la plateforme), mais ne mesuraient pas l'outcome business qui était : “Les étudiants trouvent un job via la plateforme”.\nL'outcome business lui-même était hors de leur contrôle du fait que l'obtention du job se passait hors de la plateforme.\nIls l'ont mesuré quand même au travers d'un questionnaire envoyé aux étudiants quelques temps après leur process. Ils ont itéré dessus pour augmenter le nombre de réponses.\nQuelques anti-patterns à éviter :\nRester coincé en essayant de tout mesurer : le problème le plus fréquent est de penser qu'on peut connaître à l'avance ses besoins produit à mesurer.\nIl vaut mieux mesurer seulement les assumption tests du moment, puis leur lien avec le product outcome, et avec le temps le lien avec le business outcome.\nSe concentrer à fond sur les assumption tests et en oublier de remonter dans l'OST : il ne faut pas oublier de remonter de l'outcome produit vers l'outcome business pour bien vérifier qu'on apporte de la valeur d'une manière qui sera durable.","12---managing-the-cycles#12 - Managing the cycles":"Ce chapitre présente de vrais exemples pour illustrer le fait que le processus de discovery n'est pas linéaire. Il faut prendre en compte les résultats pour soit continuer, soit revenir à une autre étape pour changer quelque chose.\n1 - Simply Business est une compagnie d'assurance.\nL'équipe produit reçoit régulièrement un pain point de la part de leurs clients : les retards de paiements. Elle confirme aussi ça avec une étude de marché.\nElle imagine 3 solutions pour aider les clients : des articles pour conseiller les petites entreprises, des rabais pour les payeurs rapides de leurs clients, et une solution technique de collection des paiements.\nEn une semaine ils ont fait 3 assumption tests pour chaque solution, et les ont proposé sous forme d'unmoderated tests. Les premiers tests cherchaient à savoir si les utilisateurs avaient bien compris les différentes offres.\nLes résultats tombent très vite : les utilisateurs n'étaient en fait pas intéressés, parce qu'ils pensaient qu'être sortis de la boucle serait dommageable pour leur business. Ils ont le problème, mais ne veulent pas de l'aide de Simply Business sur ça.\nL'équipe produit a donc choisi de déprioriser cette opportunity, et d'en choisir une autre dans leur OST, puis d'utiliser leurs interviews suivantes pour rebondir rapidement sur une autre sujet.\nConclusion : ils ont gagné du temps en évitant de créer une feature sur un mauvais sujet.\n2 - CarMax reconditionne et revend des voitures.\nL'équipe produit repère une opportunity : “Je veux être confiant sur l'état de la voiture”.\nElle teste d'abord que des réparations cosmétiques sont vraiment importantes pour le client avec un assumption test présentant une voiture avec un défaut cosmétique moins cher, et une voiture sans le défaut mais plus chère. Le test est concluant.\nIl y a 2 types de solutions possibles :\nCelles qui vont être spécifiques à chaque voiture, où il faudra indiquer ce qui a été réparé par CarMax. Celles-ci sont difficiles à mettre en œuvre et impliqueront plusieurs équipes.\nEt puis il y a la solution de type “quick win” où il s'agira de communiquer sur la qualité générale de l'entreprise, et son attention portée au détail.\nIls ont décidé de tenter la solution quick win, et ont mené des assumption tests avec des communications sur certaines photos bien placées.\nMalheureusement leurs seuils n'ont pas été atteints, parce que les clients voulaient absolument des éléments spécifiques à la voiture qui les intéresse.\nIls ont donc choisi de remettre l'opportunity à plus tard parce qu'elle prendrait du temps. En attendant, ils ont lancé l'idée auprès des autres équipes, et sont repartis sur autre chose.\nEt justement ils ont fini par la mettre en place plus tard.\nConclusion : il faut parfois explorer des solutions, et les repousser à plus tard où elles seront plus pertinentes.\n3 - FCSAmerica prête de l'argent à des agriculteurs.\nL'équipe produit s'est intéressée à la possibilité de digitaliser certaines actions, mais ils avait aussi noté que les clients aimaient la relation de confiance avec de vraies personnes de l'entreprise.\nIls ont investigué et on trouvé que les clients cherchaient déjà en ligne pour savoir combien ils pouvaient emprunter.\nIls ont donc choisi de digitaliser ce service. Et pour ajouter la touche humaine, ils y ont ajouté un chat interactif.\nMalgré toutes les tentatives pour le rendre attractif, les clients fermaient systématiquement le chat. La conclusion a été que les clients ne voulaient pas de contact humain à ce moment-là, mais seulement plus tard.\nConclusion : on peut parfois se baser sur ce que les clients font déjà, pour les pousser plus loin et les faire aller dans une direction qui sert les besoins business.\n4 - Snagajob permet aux chômeurs de trouver un emploi.\nPour améliorer la satisfaction des clients, l'équipe produit s'est attaquée à un pain point côté employeurs : “Les candidats ne répondent pas aux appels”.\nIls ont commencé par aller les voir, et les conseiller gratuitement pour suivre ce qu'ils faisaient pendant un mois.\nLe premier problème constaté était que les candidats avaient changé, et utilisaient beaucoup le mobile avec les textos, et très peu les appels vocaux.\nLa solution ne pouvant pas être d'utiliser des textos côté employeurs parce qu'ils ne voulaient pas, ils ont créé un service accessible via mobile pour les candidats.\nPuis ils ont découvert un problème de lenteur pour réserver un rendez-vous avec beaucoup de va-et-vient. Ils ont alors développé une solution pour améliorer ça.\nIls ont alors découvert que les candidats ne se présentaient parfois pas etc. A chaque fois ils ont amélioré la solution en allant d'opportunity en opportunity.\nConclusion : adresser des opportunities et sub-opportunities permet de petit à petit résoudre un outcome.\nQuelques anti-patterns à éviter :\nTrop s'engager sur une opportunity : les histoires de Simply Business et CarMax montrent qu'il faut savoir accepter qu'une opportunity peut être intéressante mais pas pour notre contexte, ou pas pour tout de suite.\nÉviter les opportunities difficiles : les quick wins sont bien sûr intéressants, mais une fois qu'on les a réalisés il ne faut pas rejeter les opportunities difficiles à traiter qui peuvent apporter beaucoup. Dans l'histoire de CarMax, ils ont certes laissé l'opportunity pour plus tard, mais ils ont quand même lancé l'idée pour qu'elle fasse son chemin, et ont fini par la traiter.\nTirer des conclusions à partir d'éléments superficiels : dans le cas de FCSAmerica ils auraient pu abandonner l'opportunity de digitalisation de par l'information que les gens préféraient le contact humain pour la confiance. Mais ils ont trouvé un moyen de concilier le besoin business avec les besoins des clients en cherchant un peu.\nAbandonner avant que les petits changements aient pu porter leurs fruits : dans l'histoire de Snagajob ils ont dû régler plusieurs petites opportunities avant d'entrevoir l'amélioration itérativement.","13---show-your-work#13 - Show your work":"Même en faisant une excellente discovery, si on n'a pas le reste de l'entreprise et en en particulier les décideurs avec soi, nos idées ne seront pas mises en place. Il faut donc les convaincre.\nQuand on présente notre travail, il ne faut pas aller directement à la conclusion et donc aux solutions, mais plutôt insister sur les opportunities.\nTout le monde a des solutions pré-établies en tête, et les leaders aussi. Si on amène la discussion sur le terrain des solutions, on va entrer en confrontation avec celles du leader. Or c'est lui qui a le pouvoir et qui aura donc le dernier mot, même s'il n'a pas fait le travail de discovery.\nIl y a même un dicton chez les PM : “The HiPPO always wins” (HiPPO = Highest Paid Person's Opinions).\nIl faut présenter le cheminement qu'on a suivi en s'appuyant sur l'OST, et en prenant le temps de le faire.\nL'intérêt de faire ça c'est qu'on va rendre les personnes comme actrices du processus de discovery, et donc plus enclines à accepter les choix qui en découlent.\nD'abord présenter l'outcome, puis donner des éléments de contexte au travers des opportunities.\nIl faut bien mentionner les choix importants qu'on a pu faire au cours de la discovery et pourquoi. On peut leur présenter certains interview snapshots pour appuyer ce qu'on dit.\nIl ne faut pas oublier de prendre des feedbacks de la part de nos interlocuteurs à chaque étape, pour les intégrer à notre travail.\nUne fois le contexte posé on peut présenter les solutions et les assumption tests, en prenant toujours les feedbacks.\nQuelques anti-patterns à éviter :\nDire les conclusions au lieu de montrer le cheminement.\nSurcharger les stakeholders avec trop de détails : ne pas aller directement aux conclusions ne veut pas dire détailler l'analyse de chaque interview snapshot et le résultat de chaque discussion en interne.\nEn fonction de qui il s'agit, la personne peut vouloir plus ou moins de détails : notre manager voudra sans doute des détails chaque semaine, alors que le CEO voudra quelque chose de très concis\nAttention cependant : concis ne veut pas dire parler des conclusions plutôt que des opportunities. On peut être concis en mentionnant le cheminement de la discovery avec seulement les éléments les plus déterminants qui nous ont mené à nos conclusions.\nArgumenter avec les stakeholders sur pourquoi leurs idées ne peuvent pas fonctionner : même si l'idée est mauvaise au regard des éléments de discovery, il faut donner des éléments au stakeholder pour qu'il parvienne à cette conclusion par lui-même, plutôt que lui dire et risquer de le braquer.\nSi son idée est bonne mais ne fit pas avec l'outcome, il faut le guider dans l'OST et lui dire qu'on peut la considérer, mais plutôt pour un autre outcome.\nEssayer de gagner le “combat idéologique” au lieu de nous concentrer sur les décisions qui dépendent de nous : quelle que soit la qualité de notre discovery, si une personne plus haut placée que nous veut prendre une décision qui va contre nos conclusions, nous ne pourrons pas gagner contre elle.\nSi on se retrouve à dire “C'est la manière dont c'est censé être fait”, alors il faut prendre une inspiration et aller faire un tour.\nLa seule chose qu'on puisse faire c'est donner les clés de compréhension pour que la personne arrive à nos conclusions par elle-même. Si elle a des conclusions différentes malgré tout, on ne gagnera de toute façon pas contre elle. Teresa conseille de choisir ses combats, et de choisir plutôt ceux qu'on peut faire avancer.","part-iii---developing-your-continuous-discovery-habits#Part III - Developing your continuous discovery habits":"","14---start-small-and-iterate#14 - Start small, and iterate":"Durant toute sa carrière, Teresa a pu être confrontée à des environnements qui ne pratiquaient pas la discovery moderne, où il s'agissait de créer des produits puis de les présenter aux clients.\nSa méthode qui a toujours marché a été de faire les choses bien de son côté, sans se préoccuper du fonctionnement global de l'entreprise qui ne dépendait pas d'elle.\nLa chose la plus importante qu'elle a cherché à faire à chaque fois c'est chercher un contact avec les clients, et le garder tout au long de l'évolution du produit.\nPour mettre en place la méthode de ce livre dans notre entreprise, elle conseille de :\n1 - Se constituer un product trio.\nLes activités décrites dans ce livre sont destinées à être faites en groupe. Si on est PM, il faut trouver un software engineer et un designer qui acceptent de participer aux activités de discovery. Si pas de designer dans l'entreprise, on peut prendre une personne qui a une sensibilité sur le sujet.\nOn peut commencer petit en les incluant dans certaines activités et décisions, et itérer en allant de plus en plus loin.\n2 - Commencer à parler aux clients.\nUne fois qu'on a notre trio, on est prêt pour établir notre keystone habit : le contact hebdomadaire avec les clients. Cette habitude sera la pierre angulaire des autres habitudes de la continuous discovery.\nIl est souvent difficile d'établir ce contact avec les clients. Soit parce que les sales ou autres veulent en garder l'exclusivité, soit parce que les clients sont difficilement joignables etc. Mais il y a toujours des moyens d'y arriver.\n3 - Travailler à l'envers.\nDans le cas où on se trouve dans une entreprise orientée delivery uniquement, et où les features à faire sont élaborées par la direction sans discovery, on peut partir des features qu'on nous donne et remonter la chaîne : vers l'opportunity, puis vers l'outcome produit et l'outcome business.\nSi on parle régulièrement avec les clients, on pourra confirmer les opportunities sous-jacentes, et trouver des assumption tests pour les features. On pourra alors identifier les tests qui peuvent être problématiques.\nOn peut demander aux stakeholders ce qu'ils espèrent comme impact de la feature, et mesurer ça. Si l'impact n'est pas atteint (ce qui, sans discovery, va forcément arriver), on peut revenir vers eux et leur dire qu'on peut faire mieux, en leur proposant de générer des idées avec nous à partir de notre OST.\nAttention par contre à ne pas jouer les je-sais-tout ou, je-vous-l'avais-dit. Il faut qu'ils se sentent acteurs de cette nouvelle manière de faire, et pas en position défensive.\n4 - Utiliser les rétrospectives pour s'améliorer.\nOn peut par exemple utiliser les rétrospectives de Scrum si on applique cette méthode pour y ajouter une partie sur la discovery.\nOn peut se poser la question “Qu'est-ce qu'on a appris qui nous a surpris dans ce sprint ?”, suivi de “Qu'est-ce qu'on aurait pu faire pour le savoir plus tôt ?”.\nSi on n'a pas eu l'impact espéré, est-ce qu'on a négligé de tester une assumption ? Est-ce qu'on l'avait mal catalogué hors de notre leap of faith ? Est-ce qu'on a eu des problèmes sur la faisabilité et pourquoi ?\nQuelques anti-patterns à éviter :\nSe dire que ça ne fonctionnera jamais chez nous : Teresa a vu cette méthode implémentée dans toutes sortes d'entreprises, petites comme grandes. Il vaut mieux se concentrer sur ce qui est en notre pouvoir pour faire marcher la discovery.\nÊtre le champion de “la bonne manière” de faire : vouloir appliquer la méthode strictement et d'un coup peut aussi être dommageable. Il faut y aller itérativement et l'adapter à son contexte.\nAttendre d'avoir la permission au lieu de commencer à faire ce qui est dans nos possibilités : il ne faut pas hésiter à parler aux clients si on en a la possibilité. Ne pas hésiter aussi à parler à des personnes similaires à nos clients dans notre entourage. Tout est bon à prendre pour commencer.","15---whats-next#15 - What's next":"Teresa propose plusieurs ressources pour aller plus loin :\nSouscrire à la newsletter mensuelle “Product Talk” : chaque mois il y a un article, soit sur une team qui marche bien, soit sur un sujet particulier de discovery.\nRejoindre la communauté “Continuous Discovery Habits” pour interagir avec d'autres personnes intéressées par le sujet.\nS'inscrire à une Master Class en petit groupe avec Teresa.\nS'inscrire à un cours d'approfondissement.\nEmbaucher un coach de chez Product Talk (teresa@producttalk.org)."}},"/books/designing-cloud-data-platforms":{"title":"Designing Cloud Data Platforms","data":{"1---introducing-the-data-platform#1 - Introducing the data platform":"Les analytics permettent essentiellement d'obtenir des métriques pour faire des choix business.\nAvant l'avènement des ordinateurs, les entreprises utilisaient des moyens manuels, et leur intuition.\nDans les années 80 on a vu émerger le concept de data warehouse, qui est une base centralisée de données venant de diverses sources.\nLes data warehouses posent de plus en plus de problèmes de nos jours.\nLes tendances suivantes y contribuent :\nLes données sont issues de sources de diverses nature, y compris certaines d'entre-elles non structurées, et leur volume est de plus en plus important.\nLe découpage des applications en microservices fait que collecter des données revient forcément à devoir agréger de multiples sources.\nLes data scientists ont aussi besoin d'accéder à une version brute de la donnée, et cet usage ne peut pas passer par un data warehouse.\nElles ont du mal avec les 3V (Variety, Volume, Velocity).\nVariety : les data warehouses ne supportent que les structured data dont le schéma est stable, c'est-à-dire en pratique qui sont issues de DB relationnelles.\nOr avec l'avènement des SaaS, des réseaux sociaux, et de l'IoT, on se retrouve avec :\nDes semistructured data du type JSON, Avro etc, dont le schéma varie souvent.\nDes unstructured data comme le binaire, le son, la vidéo.\nVolume : le fait que dans un data warehouse, la puissance de calcul et le stockage doivent se trouver sur la même machine physique, implique qu'on ne peut pas scaler les deux séparément, et donc les coûts explosent.\nMême les petites organisations peuvent être amenées à traiter plusieurs TB de données.\nVelocity : les data warehouses ne sont pas adaptées aux analytics en mode real time, elles sont plus orientées batch processing.\nLe machine learning en particulier pose tous les problèmes en même temps : il nécessite une grande quantité de données variées, et accapare la puissance de calcul du data warehouse.\nLes data lakes répondent en partie à ces problèmes.\nL'idée principale des data lakes c'est qu'on stocke de la donnée telle quelle (ou quasi), et qu'on essayera de la traiter et de lui coller un schéma dès qu'on en aura besoin.\nLes data lakes se sont généralisés à partir de 2006 avec l'arrivée de Hadoop, qui est un filesystem distribué sur plusieurs machines pas chères.\nHadoop répond en partie aux 3V :\nA la Variety par l'écriture schema-less.\nAu Volume par le fait que ce soit distribué sur des machines pas chères.\nA la Velocity par la facilité de streaming à partir du filesystem distribué.\nMais il a aussi des problèmes :\nC'est un système complexe qu'il faut installer sur un datacenter et gérer par des Ops expérimentés.\nD'un point de vue business, c'est plus difficile de travailler avec les outils qui traitent les données non structurées qu'avec du SQL comme dans un data warehouse.\nBien qu'il soit distribué sur de petites machines pas chères, le computing et le stockage ne sont pas séparés, ce qui limite quand même la réduction de coût quand on a besoin de beaucoup de l'un sans l'autre.\nLe cloud public vient répondre aux problèmes de Hadoop.\nLes data warehouses et les data lakes ont été proposés par les cloud providers, avec de nombreux avantages :\nLa possibilité de scaler la puissance de calcul et le stockage séparément.\nPayer uniquement à l'usage des machines qu'on emprunte.\nNe plus avoir à gérer la complexité de l'infrastructure.\nDes outils et frameworks avancés développés par les cloud providers autour de leurs produits.\nExemple : AWS EMR permet de lancer un cluster sur lequel on va pouvoir exécuter des jobs Hadoop et Spark,\nOn a juste à indiquer le nombre de nœuds qu'on veut, et les packages qu'on veut installer dessus.\nEt on a la possibilité de faire des allers-retours vers S3 pour scaler différemment le calcul et le stockage.\nLa cloud data platform moderne utilise à la fois le data warehouse et le data lake, hébergés dans un cloud public, chacun d'entre eux remplissant un usage particulier.\nPour être polyvalente et pas chère, la data platform doit avoir des 4 composants principaux faiblement couplés, interagissant entre-eux avec une API bien définie.\nIngestion layer : on va chercher les données chez les différents types de sources (DB relationnelle, DB NoSQL, API externes etc.).\nOn va en général utiliser un ensemble d'outils open source ou commerciaux pour chaque type de données à aller chercher.\nIl ne faut surtout pas altérer la donnée à cette étape, pour que la donnée brute soit disponible pour les data scientists qui en auraient l'usage.\nStorage layer : on utilise le stockage cloud comme stockage de notre data lake, dans lequel on met ce qu'on a ingéré.\nLe stockage cloud a l'avantage de ne pas avoir besoin de planifier la capacité de stockage : il grossit automatiquement au besoin.\nProcessing layer : on transforme la donnée pour la rendre utilisable par la plupart des clients de la plateforme.\nC'est la partie calcul de notre data lake, il va lire depuis le cloud storage puis écrire à nouveau dedans.\nDans le cas du streaming, on ne passe pas par le storage layer qui prend trop de temps, mais on envoie la donnée directement au processing layer, qui va ensuite la rendre disponible au layer d'après.\nLe processing est généralement fait avec des outils open source, les plus connus étant Spark, Beam et Flink.\nServing layer : on rend la donnée disponible sous divers formats, selon les besoins des clients de la plateforme.\nLes usages peuvent être :\nDes analystes qui ont besoin d'exécuter des requêtes SQL sur la donnée.\nOn peut charger la donnée dans un data warehouse chez le cloud provider.\nDes applications qui ont besoin d'un accès rapide à la donnée.\nOn peut la charger dans une key / value DB, ou une document DB.\nDes équipes de data scientists / engineers ont besoin de transformer la donnée eux-mêmes.\nOn peut leur donner accès au storage du data lake, et les laisser utiliser Spark, Beam ou Flink.\nLa cloud data platform répond aux 3V :\nL'ingestion layer couplé au stockage sans schéma permet une grande Variety des données.\nLa séparation calcul / stockage et le fait de ne payer que ce qu'on utilise permet d'optimiser les coûts, et d'avoir un gros Volume.\nLa possibilité d'envoyer directement au processing layer permet de la Velocity.\nOn peut aussi prendre en compte deux autres V :\nLa Veracity qui indique le niveau de data governance, c'est-à-dire la qualité de la donnée. On l'obtient itérativement, au cours d'étapes au sein du data lake.\nEt la Value qu'on peut tirer de la donnée, qui peut être plus élevée si on prend plus de données en amont de notre processus de nettoyage.\nIl faut comprendre les cas d'usages principaux d'un data lake, pour éviter de le transformer en data swamp. Parmi les plus courants il y a :\nLa vue 360° des clients, où il s'agit de récupérer toutes les données d'interaction avec eux, pour proposer ensuite des services plus personnalisés, vendre plus etc.\nLes données venant d'IoT, qui ont la particularité d'être incertaines et d'avoir un gros volume, ce qui rend l'utilisation du data warehouse peu intéressante.\nLe machine learning qui a besoin d'une très grande quantité de données, et qui tire avantage de puissance de calcul séparée des autres use-cases grâce au data lake.","2---why-a-data-platform-and-not-just-a-data-warehouse#2 - Why a data platform and not just a data warehouse":"Ce chapitre donne des arguments pour le choix d'une cloud data platform, plutôt qu'une simple data warehouse.\nOn implémente les deux solutions pour une situation d'exemple qu'on va utiliser dans ce chapitre :\nNous sommes l'équipe data, et le département marketing a besoin que nous récupérions deux sources de données et qu'on les corrèle régulièrement.\nL'une des sources est une table de campagnes de marketing, issue d'une DB MySQL interne.\nEt l'autre est constituée de fichiers CSV de clics utilisateurs, issus de logs applicatifs (et donc semistructured).\nOn part sur Microsoft Azure pour les deux solutions.\nConcernant l'implémentation data warehouse only :\n1 - On va utiliser deux Azure Data Factory pour récupérer la donnée dans le serveur de DB et les fichiers CSV dans le serveur SFTP. C'est notre ingest layer.\n2 - Ensuite on redirige ça vers l'Azure Synapse, qui est la data warehouse de chez Azure. Elle va faire office de store layer, process layer et serve layer.\nConcernant l'implémentation cloud data platform :\n1 - On a notre ingest layer avec Azure Data Factory, qui redirige les données vers le store layer.\n2 - Le store layer est implémenté avec Azure Blob Storage. Il s'agit d'un stockage de type data lake.\n3 - On a un process layer qui utilise Azure Databricks, et qui fait tourner Spark.\n4 - Le serve layer enfin utilise Azure Synapse qui est le data warehouse.\nConcernant l'ingestion.\nPour la version data warehouse only :\nLa pipeline contient :\nDes linked services : ici la data source MySQL en entrée, et la data sink Azure Synapse en sortie.\nDes data sets : il s'agit de la description du schéma de données d'entrée et de sortie, et leur mapping.\nSi le schéma de la DB source change, il faudra mettre à jour le schéma défini dans la pipeline et le mapping.\nMais surtout il faudra gérer soi-même la migration du data sink.\nPour la version cloud data platform :\nCette fois le data sink est un Azure Blob Storage.\nIl n'y a plus besoin de spécifier les schémas et le mapping entre input et output puisque l'output accueille la donnée telle quelle.\nSi le schéma de la DB source change, il n'y a rien à faire côté ingestion : on écrira de toute façon la donnée dans un nouveau fichier.\nOn déplace le problème de mapping plus loin.\nConcernant le processing.\nDans la version data warehouse only :\nOn va charger les deux données :\nLa DB MySQL sans charger sa structure parce qu'elle est déjà relationnelle.\nLa donnée CSV semistructurée dans des rows de type texte qu'on parsera en JSON avec une fonction SQL built-in.\nLa requête SQL qu'on va écrire aura les désavantages suivants :\nElle sera peu lisible, à cause du code de parsing nécessaire.\nOn pourrait la rendre plus lisible en pré-parsant la donnée, mais ça veut dire plus de temps et des coûts plus élevés.\nUne autre solution de lisibilité pourrait être d'ajouter des UDF (User Defined Functions), qu'il faudrait maintenir et déployer sur chaque instance d'Azure Synapse.\nElle sera difficile à tester.\nElle risque de ne pas profiter de la performance offerte par la structure en colonne du data warehouse, parce que les données texte qu'on parse en JSON ne sont pas organisables physiquement en colonnes.\nDans la version cloud data platform :\nOn a la possibilité d'utiliser un distributed data processing engine comme Apache Spark.\nOn pourra écrire des requêtes SQL pour des expérimentations rapides.\nEt on pourra aussi écrire du code lisible, maintenable et testable dans un langage comme Python ou Scala, quand il s'agit de projet de plus long terme.\nConcernant l'accès à la donnée.\nIl peut y avoir plusieurs types de consommateurs :\nDes utilisateurs plutôt orientés business comme des équipes marketing.\nIls vont préférer utiliser des outils de reporting type Power BI, et donc auront besoin de la donnée sous forme relationnelle, par exemple dans Azure Synapse.\nDes utilisateurs orientés data analyse / data science.\nIls pourront bénéficier de SQL qu'ils utilisent souvent directement, au travers de Spark SQL.\nIls pourront avoir accès à des données non filtrées pour leur projets data science, grâce Spark directement.\nAu final la cloud data platform, qui contient à la fois la donnée sous forme brute dans le data lake, et la donnée dans le data warehouse, est adaptée à chaque usage.\nA propos des coûts financiers.\nIl est difficile de comparer les coûts des services cloud.\nEn général on constate que le stockage est plutôt pas cher, et que l'essentiel des coûts se trouve dans les calculs.\nL'elastic scaling consiste à pouvoir calibrer le service pour l'usage exact qu'on en a, et de ne pas avoir à payer plus.\nC'est un des éléments qui permet de vraiment optimiser les coûts.\nPour la version data warehouse only, l'essentiel des coûts va aller dans Azure Synapse.\nLe scaling de ce service peut prendre des dizaines de minutes, donc c'est quelque chose qu'on ne peut faire que de temps en temps.\nPour la version cloud data platform, l'essentiel des coûts est porté par le processing layer, par exemple Spark.\nSpark est particulièrement élastique, au point où il est commun de démarrer une instance juste le temps d'une requête.","3---getting-bigger-and-leveraging-the-big-3-amazon-microsoft-and-google#3 - Getting bigger and leveraging the Big 3: Amazon, Microsoft, and Google":"Il existe un trade off entre choisir des services vendor-specific de type PaaS, et choisir des services open source.\nD'un côté on se couple au vendor mais on minimise les coûts d'Ops, et de l'autre on permet une meilleure portabilité mais on augmente les coûts d'Ops.\nLes auteurs trouvent que la solution vendor-specific est celle qui a en général le moins de désavantages.\nPour répondre aux problématiques de la data moderne, il faut une architecture en 6 couches.\n1 - Data ingestion layer.\nSon but est de :\nSe connecter aux sources et récupérer la donnée dans le data lake sans trop la modifier.\nEnregistrer des statistiques et un statut dans le metadata repository.\nSelon les auteurs, il vaut mieux mettre en place à la fois un mécanisme de type batch et un mécanisme de type streaming.\nL'industrie est en train de se diriger vers le streaming, mais de nombreuses sources externes fournissent la donnée sous un format de type batch avec des éléments groupés, par exemple CSV, JSON, XML.\nOn pourrait utiliser la partie batch pour ingérer des données par petits batchs, et éviter de faire la version streaming. Mais ça créerait de la dette technique parce qu'on finira par avoir besoin du streaming à un moment ou un autre.\nLa lambda architecture consiste à avoir la donnée qui passe à la fois par le mécanisme de batch et par le mécanisme de streaming.\nCette duplication était nécessaire parce que le streaming n'était pas fiable dans les débuts de Hadoop, mais ce n'est plus le cas.\nLa cloud data platform ne consiste pas à faire une telle duplication : selon la source, la donnée va passer par le mécanisme de streaming ou de batch.\nOn entend parfois plusieurs choses différentes quand on parle de real time pour des analytics :\n1 - La real time ingestion consiste à avoir la donnée disponible pour de l'analyse dès qu'elle arrive.\n2 - Le real time analytics consiste à avoir des fonctionnalités d'analytics qui se mettent à jour à chaque arrivée de donnée.\nCette dernière est plus difficile à faire, donc il vaut mieux bien clarifier les besoins.\nExemple : détection de fraude en temps réel.\n2 - Storage layer.\nSon but est de :\nStocker la donnée pour du court terme et du long terme.\nLa rendre disponible pour la consommation streaming et la consommation batch.\nLe slow storage est là pour le mode batch.\nLa donnée y est persistée pour pas cher, grâce à la possibilité de scaler le stockage sans ajouter de capacité de calcul.\nPar contre les temps d'accès sont grands.\nLe fast storage est là pour le mode streaming.\nIl s'agit d'utiliser un outil qui est fait pour l'accès rapide, comme Apache Kafka.\nPar contre, on n'a en général pas la possibilité de scaler le stockage sans ajouter de puissance de calcul, et donc les coûts sont plus grands.\nOn va donc purger régulièrement la donnée du fast storage, et de la transférer dans le slow storage.\n3 - Processing layer.\nSon but est de :\nLire la donnée depuis le stockage et y appliquer de la business logic.\nPersister la donnée modifiée à nouveau dans le stockage pour un usage par les data scientists.\nDélivrer la donnée aux autres consumers.\nIl faut un ou plusieurs outils qui permettent de réaliser des transformations de données, y compris avec du calcul distribué.\nUn exemple peut être Google Dataflow, qui est une version PaaS d'Apache Beam, qui supporte à la fois le mode streaming et le mode batch.\n4 - Technical metadata layer.\nSon but est de :\nStocker des informations techniques sur chaque layer.\nÇa peut être les schémas d'ingestion, le statut de telle ou telle étape, des statistiques sur les données ou les erreurs, etc.\nPermettre à chaque layer d'ajouter/modifier ou consulter des informations.\nPar exemple, le processing layer peut vérifier dans la technical metadata layer qu'une certaine donnée est disponible pour aller la chercher, plutôt que de demander à l'ingestion layer.\nCe qui permet un certain découplage.\nD'autres exemples peuvent impliquer des usages de monitoring.\nLa business metadata est une autre notion qui peut avoir son layer, mais qui n'est pas explorée dans ce livre.\nIl s'agit d'identifier l'usage business qui est fait de chaque donnée qu'on récupère des sources, et d'en faire un catalogue.\nIl n'y a pas vraiment d'outil unique qui permette de remplir ce rôle pour le moment, donc on devra sans doute en utiliser plusieurs.\nPar exemple Confluent Schema Registry et Amazon Glue peuvent supporter certains des cas d'usages.\n5 - Serving layer.\nSon but est de :\nServir les consumers qui ont besoin de données relationnelles via une data warehouse.\nServir les consumers qui ont besoin de la donnée brute, en accédant directement au data lake.\nLes data scientistes vont en général vouloir y accéder via le slow storage.\nEt l'accès via le fast storage va plutôt intéresser les applications qui s'abonnent en mode streaming.\nPar exemple un système de recommandation ecommerce en temps réel.\n6.1 - Orchestration overlay layer.\nSon but est de :\nCoordonner l'exécution de jobs, sous la forme d'un graphe de dépendance.\nGérer les échecs et les retries.\nC'est un peu le complément du technical metadata layer pour permettre le faible couplage entre les layers.\nL'outil le plus connu d'orchestration est Apache Airflow, adopté par Google Cloud Platform sous le nom de Google Composer.\nAWS et Azure ont quant à eux choisi d'inclure des fonctionnalités d'orchestration dans leur outil d'ETL.\n6.2 - ETL overlay layer.\nSon but est de :\nPrendre en charge les fonctionnalités de certains layers (ingestion, processing, metadata, orchestration) avec peu ou pas de code.\nOn pourrait faire l'ensemble de notre pipeline avec cet outil ETL, la question à se poser c'est : à quel point il est ouvert à l'extension ?\nOn peut vouloir à l'avenir par exemple utiliser un autre outil de processing, ou s'interfacer avec un outil open source.\nDans le cas où il y a une incompatibilité avec un usage qu'on a, on peut toujours l'implémenter à part de l'outil ETL.\nLe problème c'est qu'au bout d'un moment, les usages à côté deviennent aussi complexes que la solution entière sans l'outil ETL, mais avec une architecture spaghetti.\nParmi les outils ETL il y a AWS Glue, Azure Data Factory et Google Cloud Data Fusion.\nIl existe des solutions commerciales non cloud-natives comme Talend et Informatica, mais ce livre se limite au cloud-native et aux outils open source.\nLes couches doivent être bien séparées et découplées.\nUne première raison est de pouvoir utiliser les outils les plus adaptés aux besoins de chaque couche.\nLe cloud bougeant très vite, on voudra sans doute pouvoir changer seulement l'un d'entre eux quand on a une meilleure alternative pour une couche en particulier.\nUne autre raison est qu'on peut avoir plusieurs équipes en charge de la data platform, et il vaut mieux qu'elles ne se gênent pas.\nPar exemple, on voudra souvent avoir l'ingestion plutôt centralisée, et le processing plutôt en mode libre service pour chaque équipe qui en a besoin.\nLes outils pouvant servir dans une des couches de notre plateforme sont classés en 4 catégories (les auteurs les priorisent dans cet ordre) :\n1 - Solutions cloud-native PaaS d'AWS, GCP ou Azure.\nLeur avantage principal c'est le gain de temps : on n'a pas à se préoccuper de la compatibilité. On configure très facilement et c'est en prod.\nPar contre, c'est la solution qui va être la moins extensible : si par exemple un connecteur n'est pas supporté, on aura du mal à l'ajouter.\nElle est aussi peu portable, vu qu'on n'a pas les mêmes services d'un cloud provider à un autre.\n2 - Solutions serverless.\nIl s'agit de pouvoir déployer son code custom, mais sans avoir à se préoccuper des serveurs, de leur configuration, du scaling etc.\nC'est une solution intermédiaire d'un point de vue trade-offs sur la flexibilité, la portabilité et le gain de temps.\n3 - Solutions open-source.\nLeur avantage c'est c'est la flexibilité et la portabilité maximales, mais de l'autre côté on a à gérer soi-même des VMs dans le cloud donc plus de travail d'Ops.\n4 - Solutions SaaS commerciales.\nElles peuvent avoir un intérêt si elles ont une fonctionnalité non disponible sous forme PaaS ou open source.\nDans les faits, on va utiliser un mix de solutions des 4 catégories en fonction des layers et des besoins qu'on a.\nOn a de plus en plus d'entreprises qui utilisent des solutions de plusieurs cloud providers. Par exemple le gros des services sur AWS, et le use-case machine learning sur GCP.\nOutils sur AWS.\nBatch ingestion.\nAWS Glue supporte l'ingestion à partir de AWS S3, ou à partir d'une connexion JDBC.\nAWS Database Migration Service sert à la base à transférer ses DBs vers AWS, mais on peut l'utiliser comme ingestion layer.\nAWS DMS permet d'implémenter un mécanisme de change data capture à partir d'une DB.\nSi aucune des solutions PaaS ne supporte notre data source, on peut utiliser la solution serverless AWS Lambda où il faudra écrire et maintenir du code.\nStreaming ingestion.\nAWS Kinesis est un message broker pour lequel il faudra écrire du code pour publier dedans. Il a malheureusement très peu de connecteurs entrants.\nEn revanche il a des connecteurs sortants appelés Kinesis Firehose, qui permettent par exemple d'envoyer la donnée de Kinesis dans un S3 sous format Parquet.\nAWS Managed Streaming for Apache Kafka (MSK) est une version de Kafka entièrement managée.\nOn peut l'utiliser à la place de Kinesis, par exemple si on migre une application avec Kafka vers AWS.\nStorage.\nAWS S3 permet de stocker de la donnée de manière scalable, avec la possibilité de choisir entre plusieurs formules avec des latences plus ou moins grandes.\nBatch processing.\nAWS Elastic MapReduce (EMR) est une version managée de Spark.\nOn va en général lire la donnée depuis S3, faire le calcul, puis détruire le cluster EMR.\nStreaming processing.\nAWS Kinesis Data Analytics permet de se brancher sur Kinesis, et de faire du processing en streaming.\nSi on utilise AWS MSK, on peut brancher dessus Kafka Streams pour le processing en streaming.\nData warehouse.\nAWS Redshift est un data warehouse distributé sur plusieurs noeuds.\nRedshift Spectrum permet de faire des requêtes depuis Redshift pour obtenir des données qui sont en fait sur S3.\nIl faudra définir des “tables externes”, et la performance de la query sera moins bonne, mais ça permet d'économiser de la place dans le data warehouse.\nDirect access.\nAWS Athena permet de faire une requête SQL distribuée en utilisant directement la donnée sur S3.\nOn lance l'instance le temps de la requête, puis on détruit l'instance.\nETL overlay et metadata repository.\nAWS Glue est un outil d'ETL complet.\nIl est construit autour de Spark, et possède des templates pour faciliter de nombreuses transformations.\nIl a aussi des add-ons Spark non-standards, ce qui nuit à la portabilité par rapport à un simple Spark managé.\nIl maintient un Data Catalog à partir des données disponibles sur S3.\nIl maintient un ensemble de statistiques sur l'exécution des jobs.\nOrchestration.\nAWS Step Functions permet de créer des workflows qui mettent en jeu différents services, y compris ceux qui ne seraient pas gérés par Glue comme AWS Lambda avec du code custom.\nConsumers.\nPour les outils comme Tableau qui ont besoin d'une connexion JDBC/ODBC qui supporte SQL, elles peuvent se connecter à Redshift ou Athena.\nPour du streaming avec faible latence, on peut envoyer la donnée dans un key/value store comme DynamoDB, ou dans une DB comme AWS RDS ou AWS Aurora.\nOutils sur GCP.\nBatch ingestion.\nCloud Data Fusion est un ETL overlay qui permet d'ingérer des données depuis une DB relationnelle avec JDBC, des fichiers depuis Google Cloud Storage, et même depuis un FTP ou depuis AWS S3.\nIl est basé sur un projet open source, et donc supporte des connecteurs custom.\nBigQuery Data Transfer Service permet d'ingérer de la donnée depuis les services SaaS de Google, et depuis des centaines d'autres services SaaS connus grâce à un partenariat avec Fivetran.\nPar contre, la donnée va directement dans le data warehouse, ce qui ne permet pas vraiment l'architecture modulaire qu'on vise.\nCloud Functions représente l'équivalent d'AWS Lambda, avec le désavantage d'avoir une limite de temps d'exécution des fonctions serverless.\nStream ingrestion.\nCloud Pub/Sub est un broker équivalent à AWS Kinesis.\nStorage.\nGoogle Cloud Storage est un équivalent à AWS S3.\nBatch processing.\nDataproc est un Spark managé équivalent à AWS EMR.\nCloud Dataflow est un Apache Beam managé.\nBeam a l'avantage d'offrir une même API pour le batch processing et le streaming processing, là où Spark ne supporte que le batch mais est une techno plus mature.\nStreaming processing.\nCloud Dataflow représente la manière cloud-native de faire du streaming sur GCP.\nDataproc avec Spark Streaming peut représenter une alternative, mais il s'agit en fait de micro-batch et non pas de traiter les messages un par un.\nLes auteurs conseillent Beam, sauf si on a déjà investi en temps ou connaissances sur Spark.\nData warehouse.\nBigQuery est un équivalent à AWS Redshift.\nIl a l'avantage de scaler le nombre de nœuds tout seul.\nPar contre il a un modèle de facturation basé sur la donnée lue par chaque requête, ce qui peut rendre les coûts difficiles à prédire.\nDirect access.\nGCP ne propose pas de services pour accéder au data lake directement avec du SQL.\nOn peut éventuellement créer des tables vers de la donnée externe (donc dans le data lake) à partir de BigQuery.\nOn peut aussi utiliser Spark SQL pour identifier et lire de la donnée sur le data lake.\nETL overlay et metadata repository.\nCloud Data Fusion est un ETL overlay équivalent à AWS Glue. Il fournit une UI qui permet de configurer la pipeline.\nIl met à disposition un moyen d'analyser quelle partie de la pipeline peut affecter telle ou telle donnée.\nIl met aussi à disposition des statistiques sur l'exécution des jobs.\nOrchestration.\nCloud Composer permet de créer des flows d'orchestration entre jobs.\nIl est basé sur Apache Airflow.\nConsumers.\nBigQuery n'a pas de connexion JDBC/ODBC pour y connecter un outil BI par exemple.\nIl a une API REST, et il est directement compatible avec certains outils BI.\nSi on veut consommer la donnée avec une faible latence, on peut la mettre dans le key/value store Cloud Bigtable.\nOutils sur Azure.\nBatch ingestion.\nAzure Data Factory est un ETL overlay permettant de faire de l'ingestion depuis diverses sources (DB, SaaS externes, S3, GCS etc.).\nIl est celui qui a le plus de connecteurs comparé à AWS Glue et Cloud Data Fusion.\nAzure Functions est l'équivalent d'AWS Lambda.\nIl ne supporte que Java et Python.\nStreaming ingestion.\nAzure Event Hubs est équivalent à AWS Kinesis.\nIl a la particularité d'être compatible avec Apache Kafka.\nStorage.\nAzure Blob Storage est équivalent à AWS S3.\nAzure Data Lake Storage est une version améliorée qui supporte mieux le calcul distribué avec de grandes quantités de données.\nBatch processing.\nPour le batch processing, Azure a choisi de miser sur un partenariat avec Databricks, qui est un service créé par les créateurs de Spark.\nLa version managée de Databricks est disponible sur AWS et Azure, mais elle est celle par défaut sur Azure, donc mieux supportée par son écosystème.\nStreaming processing.\nAzure Stream Analytics se branche sur Event Hubs et permet de faire du streaming processing.\nData warehouse.\nAzure Synapse est le data warehouse d'Azure.\nIl est entre AWS Redshift et Google BigQuery dans la mesure où il nécessite de choisir la capacité de calcul, mais il scale l'espace disque tout seul.\nDirect access.\nAzure Databricks est la manière privilégiée d'accéder à la donnée sur le data lake, soit par l'API native de Spark, soit en SQL avec Spark SQL.\nETL overlay et metadata repository.\nAzure Data Factory est équivalent à AWS Glue.\nIl s'intègre parfaitement avec Databricks pour les transformations complexes.\nIl fournit des métriques sur la data pipeline.\nOrchestration.\nLa partie orchestration des jobs est prise en charge par Azure Data Factory.\nConsumers.\nAzure Synapse fournit une connexion JDBC/ODBC pour connecter les outils de BI.\nAzure Databricks fournit la même chose, mais il faut un cluster Spark toujours allumé, ce qui peut coûter cher.\nCosmos DB est une DB orientée document où on peut stocker les résultats de processings pour un accès faible latence.\nAlternatives commerciales ou open source.\nCertains logiciels open source sont trop difficiles à mettre en place, par exemple un data warehouse distribué comme Apache Druid.\nBatch ingestion.\nIl existe pas mal d'outils open source et commerciaux qui permettent d'ingérer des données, leur valeur ajoutée étant en général le grand nombre de sources supportées.\nApachi NiFi est une solution open source qui supporte de nombreuses sources, et permet d'en ajouter soi-même en Java.\nIl existe de nombreux outils SaaS commerciaux qui gèrent l'ingestion.\nCes outils vont souvent envoyer la donnée directement dans un data warehouse.\nIl faut bien réfléchir à la problématique de la sécurité.\nStreaming ingestion.\nApache Kafka est le principal outil utilisé en dehors d'une solution managée de streaming.\nIl a l'avantage de pouvoir se connecter à de nombreuses sources avec Kafka Connect, et il a un moyen d'implémenter des applications de streaming avec Kafka Streams.\nLes raisons de choisir Kafka plutôt qu'une solution cloud-native peuvent être l'investissement qu'on a déjà dans Kafka (par exemple connaissances), ou le besoin de performance nécessitant le fine-tuning du serveur Kafka.\nOrchestration.\nApache Airflow est le principal outil utilisé en dehors d'une solution managée d'orchestration.\nLa raison de l'utiliser en mode non managé peut être de profiter de sa flexibilité, avec ses fichiers en Python.","4---getting-data-into-the-platform#4 - Getting data into the platform":"Le layer d'ingestion peut avoir besoin d'ingérer différents types de données :\n1 - Les bases de données relationnelles.\nLeurs données sont organisées en colonnes et typées, mais chaque vendor a des types à lui.\nIl y a donc un mapping à faire entre le type de chaque colonne et notre modèle.\nCe mapping va changer régulièrement en fonction des évolutions fonctionnelles des applications qui possèdent ces DBs.\nVu que la donnée est normalisée, elle se trouve dans des centaines de tables.\nIl faudra donc automatiser le mapping pour éviter de le faire à la main.\nLa donnée change régulièrement dans la DB, pour refléter l'état de l'application, elle est volatile.\nIl faudra donc aller chercher régulièrement les derniers changements.\n2 - Les fichiers.\nLes fichiers sont structurés selon divers types de format texte ou binaire (CSV, JSON XML, Avro, Protobuf etc.) qui ne contiennent pas d'information de type.\nIl faut donc pouvoir supporter le parsing de tous ces formats.\nLes fichiers ne garantissent aucun schéma, et on voit beaucoup plus souvent des changements dans celui-ci que pour les DB relationnelles.\nIl faut donc gérer les changements de schéma fréquents.\nLes fichiers représentent en général de la donnée figée.\nLa nouvelle donnée est écrite dans un autre fichier, donc on se retrouve à devoir ingérer de nombreux fichiers.\n3 - La donnée SaaS via API.\nLes données SaaS sont en général disponibles via une API REST, qui renvoie du JSON.\nChaque provider a sa propre API, et son propre format. Il faudra donc implémenter la partie ingestion pour chacun des providers.\nIl faudra faire la validation du schéma à chaque fois.\nIl faudra la tenir à jour en fonction des changements d'API.\n4 - Les streams.\nLes mêmes données peuvent arriver plusieurs fois, donc il faut que notre pipeline puisse gérer les duplicatas.\nLes events des streams sont immutables, et peuvent être corrigés en ajoutant un autre message modifié au stream.\nDonc il faut que notre pipeline gère la réconciliation entre plusieurs versions d'un même message.\nLes données de streaming ont en général un grand volume, donc il faut une infrastructure qui le supporte.\nConcernant le cas des bases de données relationnelles.\nIl y a deux moyens d'ingérer de la donnée depuis une DB relationnelle :\n1 - L'utilisation de requêtes SQL.\nIl s'agit d'avoir un composant qui va :\n1 - Exécuter la requête vers la DB concernée.\nCa peut être un simple :\nSELECT * FROM table\n2 - Récupérer la donnée sous un format qu'il comprend.\n3 - Mapper la donnée dans le bon format pour la stocker sur le storage layer.\nIl y a donc 2 mappings qui se produisent pendant l'opération.\nAlors que la donnée opérationnelle s'intéresse à l'état actuel (“Quels sont les articles dans le panier ?”), la donnée analytique s'intéresse à l'évolution de l'état dans le temps (“Quels articles ont été ajoutés ou enlevés et dans quel ordre ?”).\nIl faut donc un moyen pour capturer l'évolution de la donnée dans le temps.\nUne 1ère solution pour garder l'évolution dans le temps est de faire une full table ingestion.\nOn va récupérer l'ensemble des données d'une table à intervals réguliers, sauver ces snapshots dans le data lake, et les charger dans le data warehouse.\nPour en tirer quelque chose, il faut superposer les rows des snapshots dans la même table du data warehouse.\nPour différencier les rows de chaque snapshot, on peut ajouter une colonne INGEST_DATE.\nOn peut directement utiliser du SQL pour obtenir les données qu'on veut, mais pour certains usages on aura besoin de faire une transformation dans le processing layer.\nParmi les données dérivées qu'on voudra créer, il peut y avoir :\nCréer une view qui ne montre que les rows du dernier snapshot.\nDe la donnée qui identifie les suppressions, en identifiant les rows qui existaient dans un snapshot et n'existaient plus dans le suivant.\nUne version “compactée”, qui élimine les rows qui n'ont pas changé par rapport au snapshot précédent.\nLe problème de la full table ingestion, c'est la charge sur la machine de DB, et l'énorme quantité de données qu'on finit par avoir.\nUne autre solution peut être l'incremental table ingestion.\nIl s'agit toujours de récupérer des snapshots à intervalles réguliers, mais seulement de la donnée qui a changé depuis le précédent snapshot.\nPour savoir quelle donnée a changé :\nLa table d'origine doit avoir un champ LAST_MODIFIED, mis à jour automatiquement par la DB.\nEn retenant le MAX(LAST_MODIFIED) du dernier run d'ingestion (qu'on appelle le highest watermark), on peut construire une query qui récupère uniquement les nouvelles données :\nSELECT * FROM subscriptions WHERE LAST_MODIFIED > \"2019-05-01 17:01:00\"\nOn pourra mettre le highest watermark dans le technical metadata layer.\nAWS Glue gère nativement le stockage de ce genre de données, mais on peut le mettre dans une DB managée comme Google Cloud SQL ou Azure SQL Database.\nCette incremental table ingestion permet d'ingérer moins de données dupliquées, mais elle a encore des inconvénients :\nIl faut faire du processing pour faire apparaître les données supprimées, en comparant les snapshots entre eux.\nLes données qui sont insérées puis supprimées entre deux snapshots ne seront pas capturées par ce mécanisme, donc on perd des informations.\n2 - Le Change Data Capture (CDC).\nLe CDC permet de récupérer l'ensemble des opérations qui ont lieu sur la table, sans aucun doublon.\nIl s'agit de lire le log de changements créé par la DB, à l'aide d'une application qui sait le faire.\nL'application peut être fournie par la DB, ou une application cloud-native comme AWS Database Migration Service, ou une application open source comme Debezium.\nEtant donné que les DBs ne gardent pas longtemps leur log de changements, le CDC nécessite une infrastructure de type streaming pour être récupéré.\nLe format des messages récupérés depuis le log de changements contient la valeur du row avant, sa valeur après l'opération, le type d'opération, et des metadata.\nOn va vouloir mettre dans le data warehouse uniquement la valeur après l'opération et le type d'opération.\nLa table dans le data warehouse ressemble du coup au cas de l'incremental table ingestion : on a une entrée par changement.\nLe CDC sur une DB Oracle.\nOracle fournit Oracle GoldenGate, une application qui permet de lire son log de changement et de le transférer vers diverses plateformes.\nIl faut acheter la licence pour pouvoir l'utiliser.\nOn peut mettre en place Debezium qui est open source, mais il faudra qu'il puisse se connecter à Oracle XStream API, qui lui-même nécessite quand même une licence GoldenGate.\nOracle fournit un outil d'analyse qui s'appelle LogMiner, qui est considéré comme pas 100% fiable.\nCertains outils comme AWS Database Migration Service l'utilisent malgré tout.\nUne alternative moins chère à GoldenGate peut être SharePlex, un produit fait par Quest.\nLe CDC sur une DB MySQL.\nMySQL écrit les changements dans un log servant principalement à la réplication, pour ajouter des DBs followers.\nVu que c'est une DB open source, il existe de nombreux outils pour servir d'application CDC à partir de ce log, par exemple : Debezium et Apache NiFi.\nLe CDC sur une DB MS SQL Server.\nMS SQL Server fournit la possibilité de rediriger le log de changements d'une table vers une table créée spécialement pour ça.\nOn peut donc facilement implémenter un outil CDC qui n'a qu'à utiliser SQL pour lire cette nouvelle table régulièrement.\nParmi les outils qui supportent le CDC sur MS SQL Server, il y a par exemple: Debezium, Apache NiFi et AWS Database Migration Service.\nLe CDC sur une DB PostgreSQL.\nPostgreSQL supporte le fait de fournir son log de changements sous un format Protobuf ou JSON, ce qui facilite le travail des applications CDC.\nIl existe de nombreux outils qui savent lire ces données, par exemple : Debezium et AWS Database Migration Service.\nConcernant le mapping des données depuis la DB vers le data warehouse, il va falloir faire une analyse pour vérifier la compatibilité :\n1 - On prépare une liste des types de données supportées par la DB dont on veut capturer les données.\nIl vaut mieux prendre l'ensemble des types, en prévision d'ajout de colonnes avec des types qui n'étaient pas utilisés jusque là par l'application.\n2 - On prépare une liste des types supportés par le data warehouse de destination, et on identifie les différences avec la précédente.\n3 - On identifie les types qui ne correspondent pas exactement, mais permetteront une conversion sans perte d'information.\nPar exemple un type SMALLINT sur MySQL comme source, et le seul entier disponible sur Google BigQuery qui est l'équivalent d'un BIGINT.\n4 - On identifie les types qui n'ont pas de correspondance satisfaisante, et pourraient mener à une perte d'information.\nOn essaye de voir si on ne peut pas trouver un workaround, par exemple transformer des données géospatiales en string, puis utiliser du processing pour les parser au moment de la lecture.\n5 - Si on est devant une impasse, on essaye de voir s'il n'y a pas un outil de data warehouse plus adapté.\n6 - Dans le cas où notre application d'ingestion n'est pas faite à la main, on vérifie les types qu'elle supporte, et leur compatibilité avec la source et la destination.\nLes auteurs conseillent de faire plusieurs PoC, et disent de ne pas faire confiance aux documentations de ces outils.\n7 - Si on écrit l'application d'ingestion à la main, il faut vérifier les types supportés par le driver qui nous permet d'accéder à la DB. Par exemple le driver JDBC.\nLes DBs NoSQL sont à traiter différemment des DBs relationnelles.\nParmi les solutions courantes :\nOn peut utiliser un outil SaaS commercial qui supporte notre DB NoSQL : dans ce cas rien de plus à faire.\nImplémenter l'application d'ingestion à la main, en utilisant l'API de notre DB NoSQL directement pour accéder aux données.\nUtiliser une application CDC si c'est disponible.\nPar exemple Debezium supporte MongoDB.\nOn peut utiliser l'outil d'export de données de notre DB NoSQL, et le faire tourner régulièrement pour avoir un snapshot des données.\nMongoDB permet d'obtenir les données sous un format CSV ou JSON, et l'outil permet d'ajouter des requêtes, donc on peut avoir une colonne qui a la date de la dernière modification, et faire un incremental table ingestion.\nCassandra permet d'obtenir les données sous un format CSV, mais uniquement en mode full table ingestion.\nConcernant les metadata liées à l'ingestion.\nIl faut sauvegarder un certain nombre de statistiques pour pouvoir ensuite faire des vérifications sur la qualité des données ingérées, et du monitoring de l'ingestion.\nOn va mettre tout ça dans notre technical metadata layer.\nParmi les statistiques qu'on veut :\nLe nom et l'adresse IP du serveur de DB.\nLe nom de la base de données ou du schéma.\nLe nom de la table.\nLe type de DB dans le cas où on en gère plusieurs.\nPour de l'ingestion en batch, le nombre de rows ingérées.\nOn pourra à partir de ça vérifier que l'ensemble des données sont arrivées à destination.\nOn peut monitorer ce chiffre pour être alerté dans le cas d'une variation anormale.\nLa durée de chaque job d'ingestion, de même que le début et la fin de l'ingestion.\nC'est un moyen de monitorer la santé de la pipeline.\nPour de l'ingestion en streaming, on prend les statistiques par fenêtre temporelle.\nPar exemple insérer un row toutes les 5 mn dans notre DB de technical metadata. Plus on a besoin de réagir vite, et plus on va choisir une fenêtre petite.\nOn peut aussi ajouter le nombre d'inserts, updates, deletes etc. pour chaque fenêtre.\nLes changements dans le schéma de la DB source, ce qui nous permettra d'être alerté et d'adapter la pipeline.\nConcernant le cas des fichiers.\nLes fichiers (par exemple CSV, JSON) permettent un bon découplage entre deux systèmes.\nOn a en général deux moyens de mettre à disposition des fichiers :\nVia un serveur dédié qui expose un protocole FTP.\nVia le service de storage d'un cloud provider.\nLes avantages sont l'aspect elastic, et les mécanismes de sécurité pré-configurés.\nLe désavantage principal c'est que c'est cloud provider met en place des coûts pour faire sortir la donnée de son infrastructure.\nLes fichiers sont immutables une fois qu'ils sont écrits, ce qu'on aura besoin de tracker c'est quels fichiers ont déjà été ingérés.\n1 - Une approche recommandée par les auteurs c'est d'avoir deux dossiers dans le système source qui met à disposition les fichiers : incoming et processed :\nL'application d'ingestion va ingérer un fichier depuis incoming, puis une fois que l'ingestion est terminée, elle va le copier dans processed et le supprimer d'incoming.\nOn le laisse dans processed pendant quelques jours dans un but de débug et de replay, avant de le supprimer.\nParmi les avantages :\nOn n'a pas besoin de tracker quels fichiers ont été traités : il suffit de traiter ceux du dossier incoming.\nOn peut facilement rejouer l'ingestion en replaçant le fichier depuis processed vers incoming.\n2 - Dans le cas où l'approche des deux dossiers n'est pas possible, parce que le système source veut organiser autrement ses fichiers, ou qu'on n'a pas la possibilité de les modifier, on peut mettre en place l'approche des timestamps.\nChaque fichier va avoir un timestamp de la dernière fois qu'il a été modifié, et on va devoir garder le timestamp le plus récent dont on a ingéré un fichier dans le technical metadata layer.\nVu que le filesystem ne fournit pas de système d'indexation, on va devoir lire à chaque fois les metadata de l'ensemble des fichiers pour savoir s'ils sont plus récents ou moins récents que notre timestamp sauvegardé.\nOn peut se retrouver face à un problème de performance, surtout avec les stockages cloud de masse.\nCette méthode rend plus compliqué le replay des fichiers : on devra possiblement modifier notre dernier timestamp sauvegardé, et on aura du mal avec les fichiers qui ont le même timestamp de modification.\nCertains outils comme Apache NiFi implémentent déjà ce mécanisme.\nIl faudra faire attention à faire un backup de ces données pour ne pas avoir à reprocesser tous les fichiers.\n3 - Une variante de l'approche des timestamps consiste à organiser les fichiers source dans une arborescence de dossiers représentant la date d'ajout.\nExemple :\n/ftp/inventory_data/incoming/2019/05/28/sales_1_081232\nOn peut s'en servir pour ne lire que les metadata des fichiers qui sont à la date qu'on veut ingérer.\n4 - Des outils cloud-native existent pour copier des fichiers d'un storage à un autre, et de ne copier que les nouveaux fichiers à chaque fois qu'il y en a.\ngsutil permet de le faire chez Google Cloud, blobxfer chez Azure, et s3 sync chez AWS.\nIl est compliqué de faire du replay avec ces outils, parce qu'il n'y a pas de dernier timestamp stocké à modifier.\nConcernant les metadata techniques à garder :\nOn ne va pas à ce stade récupérer de statistiques sur le nombre de rows dans le fichier, parce que ce serait techniquement coûteux pour l'ingestion layer.\nOn le fait pour les DBs parce que c'est pas cher.\nParmi les statistiques à récupérer :\nNom permettant d'identifier la source.\nTaille du fichier.\nDurée de l'ingestion.\nLe nom du fichier et le path où il était (peut contenir des infos importantes).\nConcernant le cas des streams.\nIl s'agit ici de lire de la donnée disponible dans Kafka, ou encore dans un équivalent cloud-native.\nOn parle ici seulement d'ingestion en mode streaming, c'est-à-dire que la donnée est disponible dans la plateforme dès que possible, mais elle sera exploitée plus tard.\nLes étapes à mettre en place sont :\n1 - La 1ère étape est de lire le stream source, et de l'écrire dans le fast storage de notre cloud data platform, qui est aussi un stream.\nOn peut faire ça avec Kafka Connect, qui permet de lire et écrire entre deux topics Kafka, mais aussi de lire depuis Kafka et écrire dans une solution de streaming cloud-native, ou l'inverse.\nOn peut aussi faire notre propre application consumer Kafka à la main, mais il faudra alors s'occuper de la gestion des erreurs, du logging, et du scaling de notre consumer. Les auteurs le déconseillent.\n2 - On va ensuite l'écrire dans le data warehouse.\nLes auteurs conseillent fortement d'utiliser une solution cloud-native pour ça, en fonction de notre fast storage :\nAzure Stream Analytics qui lit depuis Azure Event Hubs pour écrire dans Azure SQL Warehouse.\nGoogle Cloud Dataflow qui lit depuis Cloud Pub/Sub pour écrire dans BigQuery.\nAWS Kinesis Data Firehose qui lit depuis AWS Kinesis pour écrire dans Redshift.\nPour BigQuery on peut ingérer dans le data warehouse en streaming, mais pour les deux autres, il faudra faire de petits batchs.\n3 - L'autre chose à faire en parallèle c'est d'écrire la donnée depuis le fast storage vers le slow storage.\nOn peut là aussi utiliser les solutions cloud-natives.\nIl va falloir écrire la donnée par batchs pour des raisons de performance. Les auteurs recommandent des batchs de plusieurs centaines de MB si c'est possible.\nKafka (et les solutions cloud-natives similaires) doit faire le commit de son offset, et en général il le fait après avoir traité plusieurs messages pour des raisons de performance;\nÇa veut dire que si il y a un crash, les message traités mais non commités seront traités à nouveau. Donc il faut gérer la duplication.\nUn des moyens de le faire c'est d'avoir un identifiant unique par message, et ensuite d'enlever les doublons dans la phase de processing.\nKafka a en général une cleanup policy qui est de l'ordre de la semaine, ce qui fait que pour rejouer de la donnée, il faut prévoir une étape qui va la chercher dans le slow storage, et la remet dans le fast storage.\nConcernant les metadata techniques à garder :\nLes metadata à garder ressemblent à ceux du cas CDC depuis les DBs relationnelles.\nOn mesure le nombre de messages ingérés par fenêtre de temps (dont la taille dépendra du type de données ingérées).\nConcernant le cas des applications SaaS qui fournissent de la donnée.\nLes applications SaaS vont en général exposer leurs données via une API REST, le contenu étant formaté en JSON ou parfois en XML.\nIl faut d'abord s'authentifier, souvent avec OAuth.\nEt ensuite il faut étudier la documentation du provider SaaS pour savoir quel call faire.\nIl y a un certain nombre de difficultés.\nChaque provider va designer son API selon ses contraintes. Et donc si on veut supporter de nombreux providers, il va falloir adapter l'ingestion layer pour chacun d'entre eux.\nChaque provider va fournir soit du full data export soit de l'incremental data export, et parfois les deux.\nLe full data export consiste à obtenir une liste d'objets, puis à aller chercher les données pour chacun d'entre eux.\nL'incremental data export consiste à obtenir une liste d'objets qui ont changé entre deux timestamps qu'on fournit, pour ensuite aller chercher leurs données récentes uniquement.\nLe JSON reçu est en général imbriqué sur plusieurs niveaux.\nCertains data warehouses gèrent les données imbriquées, mais ce n'est pas le cas de Redshift pour lequel il faudra faire une étape de processing pour mettre ces données à plat.\nDe manière générale, mettre les données à plat dans plusieurs tables plus petites est plus pratique pour les data scientists.\nEtant donné la difficulté à implémenter et maintenir une pipeline ingérant de la donnée de sources SaaS, les auteurs conseillent de bien réfléchir à l'implémenter soi-même.\nS'il s'agit d'une source pas trop compliquée, ça peut passer.\nSi par contre il s'agit de nombreuses sources, alors il nous faudra une grande quantité de code et de maintenance.\nLes auteurs conseillent plutôt une solution off-the-shelf comme Fivetran qui supporte la plupart des sources SaaS connues.\nConcernant les metadata techniques à garder :\nIl s'agit du même type de metadata que pour les sources en batch comme les DBs ou les fichiers.\nOn voudra notamment :\nLe nom de la source.\nLe nom de l'objet qu'on va chercher dans la source.\nLes temps de début et fin d'ingestion.\nLe nombre de rows qu'on récupère.\nPour des questions de sécurité, il est préférable d'encapsuler notre cloud data platform dans un virtual private cloud (VPC).\nPour faire le lien entre la plateforme dans le VPC et la donnée qu'on veut aller chercher, on peut utiliser un VPN Gateway, qui permet de passer par internet de manière sécurisée.\nDans le cas des SaaS comme source, ils fournissent des APIs sécurisées par HTTPS, et disponibles globalement sur internet, donc il n'est pas nécessaire d'établir une connexion via VPN Gateway.\nDans le cas où on veut transférer des centaines de GB par jour, il vaut mieux mettre en place une connexion directe.\nLes solutions cloud-natives ont leur outil de connexion directe : AWS Direct Connect, Azure ExpressRoute, Google Cloud Interconnect.","5---organizing-and-processing-data#5 - Organizing and processing data":"Les architectes de l'ancienne école ont encore tendance à recommander de faire le processing dans le data warehouse.\nLes auteurs du livre suggèrent que la manière moderne est de le faire sur des machines à part, par exemple avec Spark, qui lirait et écrirait dans le data lake.\nLes arguments sont les suivants ((1) pour faire le calcul dans le data warehouse, et (2) pour utiliser la layered architecture) :\nFlexibility : avec la (1) le résultat du processing n'est utilisable que dans le data warehouse, avec la (2) on peut facilement le rediriger ailleurs.\nDeveloper productivity : il y a plus de personnes qui connaissent le SQL, donc le (1) a un avantage court terme, bien que Spark soit plus puissant, il faut souvent former les devs.\nData governance : la source principale étant le data lake, faire les transformations au même endroit permet d'être sûr d'avoir toutes les versions alignées. Dans le cas où on fait ça dans le data warehouse, il est préférable de ne pas le faire dans le data lake pour ne pas avoir de divergence.\nCross-platform portability : changer de cloud vendor est bien plus simple avec Spark qu'avec du code SQL qu'il faudra au moins tester.\nPerformance : avec la (1) le processing impacte le data warehouse, avec le (2) on fait le calcul complètement à part et on n'impacte personne.\nSpeed of processing : avec la (1) on peut faire du real time analytics dans certains cas avec difficulté, avec la (2) ça marche facilement.\nCost : tous les providers de data warehouse ne le font pas (mais ils vont finir par le faire), mais pour ceux qui font payer le processing ça revient plus cher que de faire le processing sur des machines complètement à part.\nReusability : avec la (1) on peut parfois utiliser des stored procedures, avec la (2) on a du code qu'on peut directement réutiliser.\nLe processing se décompose en stages.\nChaque stage contient : une area de stockage dans le data lake, et un job de calcul distribué (par exemple avec Spark), qui va créer la donnée pour l'étape suivante.\nLes jobs sont coordonnés par l'orchestration layer.\nLes jobs peuvent être de deux types :\nCommon data processing : les transformations communes, par exemple dédupliquer les messages, valider les dates etc.\nBusiness logic specific processing : les transformations spécifiques à chaque use-case, qui vont par exemple filtrer les campagnes de marketing à succès uniquement si le use-case c'est d'afficher les meilleures campagnes.\nAvoir un ensemble de stages standardisés est important pour que chacun puisse s'y retrouver malgré le scale.\nLes étapes proposés par les auteurs sont :\n1 - Landing area : c'est là que la donnée arrive en premier, il ne s'agit pas d'un stockage long terme.\n2 - Staging area : la donnée subit des checks basiques de qualité, et on vérifie qu'elle est conforme au schéma attendu. Elle est stockée sous format Avro.\n3 - Archive area : la donnée est copiée depuis la landing area vers l'archive area.\nCette opération n'est effectuée qu'après que la donnée ait pu aller vers la staging area avec succès.\nOn pourra refaire le processing de la donnée simplement en la copiant depuis l'archive area vers la landing area.\n4 - Production area : la donnée subit la transformation business nécessaire pour un use-case particulier avant d'aller là.\nElle est aussi transformée du format Avro vers Parquet, qui est plus adapté pour faire de l'analytics.\n4.1 - Pass-through job : il s'agit d'un job qui copie la donnée de la staging area vers la production area sans transformation autre que le format Parquet, et ensuite la copie dans le data warehouse.\nCe use-case “basique” est utile pour débugguer les autres use-cases.\n4.2 - Cloud data warehouse and production area : les use-cases qui ont besoin de la donnée dans le data warehouse passent d'abord par le processing de la staging area vers la production area.\n5 - Failed area : chaque étape peut faire face à des erreurs, qu'elles soient liées à la donnée ou à des échecs temporaires de la pipeline.\nLes messages qui n'ont pas réussi une étape vont dans cette area où on pourra les examiner et voir ce qu'il faut corriger.\nUne fois la correction faite, il suffit de les copier dans l'area de l'étape où ils ont échoués.\nChaque area doit être dans un container du service de stockage de notre cloud provider.\nLes containers contiennent des folders.\nIls sont appelés buckets chez AWS et GCP.\nC'est au niveau de ces containers qu'on peut configurer les droits d'accès, et choisir le prix qu'on paye pour les performances qu'on aura (hot / cold / archive storage).\nParmi nos 5 areas, toutes sont de type hot, sauf l'archive area qui peut être cold / archive.\nOn a besoin d'une organisation des folders claire dans chaque area.\nLes éléments communs sont :\nLe namespace représente la catégorisation la plus high level, pour les petites organisations ça peut être juste le nom de l'organisation, mais pour les plus grandes ça peut être le nom du département.\nLe pipeline name représente le nom d'un job en particulier. Il faut qu'il soit clair par rapport à ce que fait le job, et utilisé partout pour parler de lui.\nLe data source name identifie une source. C'est l'ingestion layer qui choisit ce nom et le note dans le metadata layer.\nLe batchId représente l'identifiant de chaque batch de donnée écrit dans la landing area par l'ingestion layer.\nOn peut utiliser un UUID pour le représenter, ou encore un ULID, qui a la particularité d'être plus court et de permettre de savoir facilement si un autre ULID est plus grand ou plus petit.\nPour la landing area, les auteurs proposent la folder structure :\nlanding/NAMESPACE/PIPELINE/SOURCE_NAME/BATCH_ID/\nlanding représente le nom du container.\nExemple : /landing/my_company/sales_oracle_ingest/customers/01DFTQ028FX89YDFAXREPJTR94/\nPour la staging area, de même que pour les autres areas, il s'agit de stocker la donnée sur le long terme, donc on aimerait une structure qui fasse apparaître le temps, avec 3 folders supplémentaires :\nIl s'agit d'ajouter 3 folders supplémentaires qui viennent de la convention de Hadoop : year=YYYY/month=MM/day=DD.\nExemple : /staging/my_company/sales_oracle_ingest/customers/year=2019/month=07/day=03/01DFTQ028FX89YDFAXREPJTR94/\nDe nombreux outils (y compris Spark) vont reconnaître ce format, et si notre batchId est un ULID, les folders les plus récents seront présentés en premier.\nPour la production area, on ne peut pas vraiment reporter les sources qui ont servi à la donnée dans le folder name - il y en a potentiellement des dizaines.\nOn va donc plutôt créer des sources dérivées dont on mettra le nom à la place de la source, et on documentera ces sources dérivées dans le metadata layer.\nLa donnée qui arrive en streaming :\nVa passer directement vers la version streaming du processing layer sans être stockée d'abord dans le slow storage. C'est traité au chapitre 6.\nMais on va quand même l'envoyer dans le slow storage en parallèle pour un but d'archivage et rejeu si besoin.\nUn job va lire depuis le fast storage où arrive la donnée en streaming, par batchs suffisamment gros, et va écrire ça dans la landing area.\nLes fichiers seront ensuite passés de stage en stage jusqu'à la production area.\nParmi les common processing steps :\nFile format conversion.\nL'approche data lake traditionnelle consiste à laisser les données telles quelles, et laisser chaque pipeline parser elle-même la donnée et faire les traitements dont elle a besoin.\nMais cette approche a du mal à scaler.\nDans la cloud data platform architecture, on choisit de faire certains traitements en amont, pour éviter d'avoir à tester et maintenir du code qui fait ça dans chaque pipeline.\nAvro et Parquet sont des formats binaires intégrant un schéma.\nIls permettent de ne pas répéter le nom des champs, et donc d'économiser de la place.\nIls permettent de garantir le schéma de la donnée.\nAvro est organisé en blocs de rows, alors que Parquet est organisé en blocs de columns.\nLes fichiers organisés en rows sont utiles quand on lit la donnée de toutes les colonnes pour certains rows donnés. La staging area sert principalement à faire des transformations ou de l'exploration ad-hoc, donc Avro est adapté.\nLes fichiers organisés en columns sont utiles quand on ne veut traiter qu'une column sur un grand nombre de rows. La production area sert à faire des requêtes d'analytics, donc Parquet est adapté.\nPour la conversion depuis le format initial vers Avro, puis vers Parquet, Spark permet de lire et écrire ces différents formats.\nExemple :\nclicks_df = spark.read.json(in_path)\nclicks_df = spark.write.format(\"avro\").save(out_path)\nData deduplication.\nOn s'intéresse ici au fait d'avoir un attribut sur notre donnée qui soit unique dans l'ensemble des données.\nA partir du moment où on n'a pas de garanties d'unicité, on peut se retrouver dans une situation de duplication, par exemple si le metadata repository est corrompu, si la source envoie une donnée dupliquée, ou encore si un dev rejoue certaines données qui avaient déjà marché.\nLe problème existe aussi avec Kafka, où des transactions existent si on lit un record et qu'on écrit dans un topic Kafka, mais pas si on écrit sur un service de storage.\nSpark a une fonction intégrée dropDuplicates() qui permet de dédupliquer en fonction d'une ou plusieurs colonnes.\nOn peut dédupliquer sur un batch qui arrive dans la landing area pour pas cher :\nusers_df = spark.read.format(\"csv\").load(in_path)\nusers_deduplicate_df =\n users_df.dropDuplicates([\"user_id\"])\nSi on veut vraiment dédupliquer sérieusement, il faut aussi joindre l'ensemble des données déjà présentes dans la staging area au batch courant, et appliquer la déduplication dessus, par exemple avec du SQL qu'on passe à Spark.\nincoming_users_df\n .createOrReplaceTempView(\"incomgin_users\")\nstaging.users_df\n .createOrReplaceTempView(\"staging_users\")\nusers_deduplicate_df = spark.sql(\n \"SELECT * FROM incoming_users u1\n LEFT JOIN staging_users u2\n ON u1.user_id = u2.user_id\n WHERE u2.user_id IS NULL\"\n)\nLe problème c'est que la déduplication à chaque fois avec l'ensemble des données coûte cher. Donc il faut vérifier que notre use-case le nécessite d'un point de vue business.\nOn peut aussi dédupliquer avec seulement les fichiers dans le dossier de l'année actuelle, du mois actuel etc. depuis la staging area.\nData quality checks.\nUne vérification minimale de la qualité de la donnée est en général nécessaire pour la plupart des cas d'usages. Par exemple :\nLa longueur de certaines colonnes.\nLa valeur numérique acceptable de certaines colonnes.\nLe fait d'avoir certaines colonnes “obligatoires”.\nLe fait d'avoir certaines colonnes respecter un pattern, par exemple l'email.\nSpark a la fonction filter() qui permet d'obtenir les colonnes qui respectent une mauvaise condition.\nOn a aussi subtract() qui permet d'enlever ces rows du batch, pour passer les rows valides à la production area, et les rows invalides à la failed area.\nAttention à la consistance des données, en fonction du contexte business, il peut être plus judicieux de laisser passer la donnée, et de simplement informer les data engineers du problème.\nDe manière générale, il faut réfléchir à la criticité de chaque problème de qualité pour décider quoi faire en cas de donnée malformée : filtrer la donnée, laisser passer et prévenir quelqu'un, ou annuler l'ingestion du batch entier.\nExemple :\nusers_df = spark.read.format(\"csv\").load(in_path)\nbad_user_rows =\n users_df.filter(\n \"length(email) > 100 OR username IS NULL\"\n )\nusers_df = users_df.subtract(bad_user_rows)\nOn peut créer des jobs configurables : l'orchestration layer lance un job, en lui donnant d'abord la configuration contenant les sources à traiter, le schéma à valider en fonction des sources, la folder structure où insérer les nouveaux fichiers etc.\nÇa permet d'économiser du code, au moins pour les jobs de transformation “common”.\nLe bon endroit pour la configuration c'est le metadata layer.\nPour déclencher nos jobs, il faut qu'il y ait une forme de monitoring de la landing area, soit avec du code qu'on écrit nous-mêmes, soit avec la fonctionnalité de monitoring d'un outil d'orchestration cloud.","6---real-time-data-processing-and-analytics#6 - Real-time data processing and analytics":"La notion de real-time (ou streaming) dans le contexte d'une pipeline data peut recouvrir deux choses différentes :\n1 - real-time ingestion : on ingère la donnée une par une avec un mécanisme de message streaming, et on l'amène jusqu'au data warehouse. Mais la consommation de la donnée ne se fait pas en temps réel.\nL'aspect “real-time” ne concerne que l'ingestion layer.\nLe processing se fait à la demande, et peut prendre des secondes voire des minutes, mais il se fait sur une donnée fraîche.\nIl peut se faire selon un schedule, ou à la demande des utilisateurs humains qui attendront un peu avant d'avoir un résultat.\nExemple : un data analyste veut pouvoir exécuter une requête pour afficher un dashboard sur des données fraîches quand il en a besoin. Le dashboard n'est pas mis à jour en continu mais juste à l'exécution de cette requête.\n2 - real-time processing : on récupère la donnée une par une, et on la redirige vers un autre système qui va réagir à chaque donnée qui arrive pour se mettre à jour.\nLe real-time processing nécessite la real-time ingestion. L'aspect “real-time” concerne donc l'ingestion layer et le processing layer.\nOn est dans un cas d'usage où on a besoin que le processing se fasse très vite et en continu, en général à destination d'un autre système.\nExemple : la donnée qui arrive dans la pipeline est ensuite mise à disposition d'un système de jeu vidéo pour adapter le comportement du jeu en fonction de ce que fait le joueur en temps réel. Par exemple, ajuster la probabilité de faire apparaître un monstre.\nLa donnée est traitée par un real-time job qui tourne en permanence et ajuste les calculs en fonction des nouvelles données.\nElle est ensuite mise à disposition d'un key/value store ou éventuellement d'une DB relationnelle, pour un accès rapide. Le data warehouse est trop lent et est fait pour des requêtes à la demande sur de grandes quantités de données.\nElle peut aussi être postée dans le fast storage, c'est-à-dire comme event de streaming pour déclencher un autre processing.\nC’est parce que ce job tourne en continu avec des choses chargées en RAM qu’il donne un résultat rapide, contrairement à une requête SQL dans un data warehouse par exemple, qui ne se déclenche qu’au moment où on la lance.\nIl est très important de clarifier le besoin : dans le cas où on n'a besoin que de real-time ingestion, la complexité de mise en œuvre est beaucoup moins grande.\nLes auteurs conseillent d'utiliser la real-time ingestion plutôt que la batch ingestion, sauf quand la source ne supporte pas le real time.\nLa real-time ingestion implique moins de nécessité d'orchestration et de monitoring.\nPour éviter l'incohérence pour les utilisateurs, il vaut mieux éviter de mixer des données real-time avec des données qui viennent en batch.\nOn n'a en général pas la possibilité d'utiliser le même système pour traiter les données qui arrivent en real-time et les données qui arrivent par batch.\nPar exemple Google Cloud Dataflow le permet avec l'utilisation de Beam, mais la plupart du temps on aura besoin de deux outils.\nSelon les auteurs, la plupart du temps quand les utilisateurs demandent du “real-time”, ils veulent en fait juste de la real-time ingestion.\nQuand des utilisateurs demandent à pouvoir afficher leur dashboard en “real-time” alors qu'il tourne une fois par jour, bien souvent avoir de la real-time ingestion et faire tourner le processing du rapport toutes les heures ou toutes les 15 minutes leur suffira.\nParmi les cas d'usage qui pourraient nécessiter du real-time processing : les systèmes d'action in-game, les systèmes de recommandation, les systèmes de détection de fraude.\nTransiter une pipeline de la batch ingestion à la real-time ingestion se fait sans trop de difficulté.\nTransiter du batch processing au real-time processing est bien plus complexe vu qu'il va falloir en général changer d'outils, et donc il faut penser ça en amont.\nLe fast storage est composé d'un système d'event streaming du type Kafka.\nLes messages (qui font entre quelques KB et 1 MB) sont traités un par un, et stockés dans des topics. Ils sont identifiables par leur offset.\nLes producers écrivent dans les topics, et les consumers lisent depuis les topics. Kafka a des mécanismes qui leur permettent de publier et consommer de manière fiable malgré les fautes.\nPour permettre de scaler, le contenu des topics est séparé en partitions, qui se trouvent sur des machines différentes, avec des copies pour plus de fiabilité.\nLà où lire et écrire dans S3 mettrait quelques centaines de ms, le faire dans Kafka en prend 10 fois moins, mais surtout Kafka tient la charge avec une très grande quantité de petits messages.\nDe même que pour le slow storage et le batch processing, le fast storage est organisé en areas qui servent à des stages de processing.\nLes étapes sont :\n1 - Landing area : l'ingestion layer écrit la donnée dans cet endroit.\n2 - Staging area : la donnée subit des checks basiques de qualité, et on vérifie qu'elle est conforme au schéma attendu.\n3 - Archive area : la donnée est copiée depuis la landing area vers l'archive area.\nIl s'agit d'espace de stockage cloud classique.\nOn pourra refaire le processing de la donnée simplement en la copiant depuis l'archive area vers la landing area.\n4 - Production area : la donnée subit la transformation business nécessaire pour un use-case particulier avant d'aller là.\n4.1 - Pass-through job : il s'agit d'un job qui copie la donnée de la staging area vers la production area sans transformation, et ensuite la copie dans le data warehouse.\nCe use-case “basique” est utile pour débugguer les autres use-cases.\n4.2 - Staging to production : des jobs lisent la donnée à partir de la staging area dans un but de reporting/analytics, et créent un dataset dans la production area, pour charger la donnée ensuite dans le data warehouse ou dans une DB relationnelle ou NoSQL.\n5 - Failed area : chaque étape peut faire face à des erreurs, qu'elles soient liées à la donnée ou à des échecs temporaires de la pipeline.\nLes messages qui n'ont pas réussi une étape vont dans cette area où on pourra les examiner et voir ce qu'il faut corriger.\nUne fois la correction faite, il suffit de les copier dans l'area de l'étape où ils ont échoués.\nCôté organisation en topics :\nLes providers limitent en général le nombre de topics à quelques milliers, et c'est l'abstraction principale qu'on a. Avec des centaines de tables par DB qu'on utilise comme source, les topics sont vite très nombreux.\nSelon les auteurs, l'organisation la plus pertinente pour le cas général serait d'utiliser un topic par area, et de faire la distinction entre sources avec un champ à l'intérieur des messages.\nMais dans le cas où on a des sources qui donnent des messages structurés très différemment, ou qui ne permettent pas d'utiliser des jobs de processings communs, on peut faire des topics différents par source.\nUne autre raison de séparer en topics par source peut être la limitation en termes de quotas par topic, de la part du provider.\nUne autre raison pour publier dans des topics différents peut être la structure interne des équipes, et les questions de sécurité, pour restreindre certaines données à certaines équipes.\nPour rendre les jobs configurables, on peut faire en sorte qu’ils lisent le contenu du message et appellent une librairie qui va faire quelque chose de particulier en fonction de la valeur lue.\nParmi les transformations qu'on a couramment dans les systèmes real-time, il y a la déduplication des messages.\nLes duplications sont courantes dans les systèmes real-time, elles ont deux origines :\n1 - Des duplications issues de la source, sur lesquelles on n'a pas de contrôle.\n2 - Des duplications qui sont dues au fonctionnement des systèmes real-time, et à leur nature distribuée.\nOn peut par exemple avoir un producer qui envoie un message, mais ne reçoit pas l'acknowledgement à cause d'un problème réseau. Un autre broker sera élu master de la partition et on se retrouvera avec une duplication.\nCôté consumer, il suffit que l'un d'entre eux envoie un message et crash avant de commiter. Il va alors renvoyer le même message quand il reviendra à la vie.\nLa difficulté pour dédupliquer avec les systèmes real-time c'est qu'on a une donnée qui arrive en permanence, et qui est distribuée sur plusieurs machines.\nUne solution peut être d'utiliser une time window : on choisit un début et une fin de timestamp, et on récupère tous les messages correspondants pour faire une déduplication parmi eux.\nOn peut avoir par exemple une sliding window qui se déplace dans le temps, ou tumbling window qui va diviser le temps en tranches disjointes.\nLe problème c'est qu'on est limités sur la tranche de temps qu'une machine peut traiter, et la déduplication ne se fait que pour les messages de cette tranche, et pas avec les autres tranches.\nUne autre solution est d'avoir un key/value cache dans lequel on met l'ID de chaque message traité, et qu'on réinterroge à chaque fois pour éviter de le retraiter encore.\nLa taille va rarement être un problème : stocker 1 milliard de UUID fait ~15 Go.\nPar contre il faut que le store soit highly available et performant, donc une solution cloud est bien adaptée.\nExemples de key/value stores : Azure Cosmos DB, Google Cloud Bigtable, AWS DynamoDB.\nUne 3ème solution peut être de laisser les messages dupliqués jusqu'au data warehouse, et dédupliquer ensuite par un job en mode batch, soit dans le data lake, soit dans le data warehouse.\nCa permet d'avoir une real-time ingestion particulièrement rapide, mais il faut que la duplication soit OK dans un premier temps.\nUne autre transformation courante est la conversion de format.\nLes messages dans le système real-time sont consommés un par un, donc il est capital d'avoir des schémas bien définis entre producers et consumers. Le metadata layer pourra nous aider à le stocker.\nConcernant le format :\nJSON ne fournit pas de mécanisme de gestion de schéma, et est plus volumineux. Il peut être compressé, mais ce serait surtout efficace avec plusieurs messages où des noms de champ se répètent par exemple.\nAvro permet de minimiser la taille du message, et permet une gestion du schéma avec la possibilité de le stocker dans un store.\nParquet n'apporte aucun avantage dans un système real-time puisque son but est de permettre de lire de grandes quantités de données pour faire du processing dessus, et qu'on est ici sur du message par message.\nConcernant les quality checks, on peut avoir un job qui vérifie la qualité de chaque message avant de le placer dans l'area du stage suivant ou dans la failed area.\nDans le cas où on a de nombreuses sources gérées par plusieurs équipes, la difficulté va surtout être dans la définition de ce qu'est une donnée avec une qualité suffisante.\nNos quality checks peuvent impliquer de vérifier une caractéristique impliquant plusieurs messages, par exemple “pas plus de 10% des commandes avec le statut cancelled”.\nIl faudra alors utiliser les techniques de windowing comme avec la déduplication.\nSi la durée sur laquelle on veut faire les checks est trop grande par rapport à ce que supportent nos outils, il faudra faire passer le flow par le batch processing.\nDans le cas où on veut combiner une source de données real-time et une source batch, on peut :\n1 - Avoir le job real-time qui lit le message batch à combiner avec les données du message real-time, et qui le stocke dans sa RAM.\n2 - Puis ce job combine les deux pour les mettre dans la real-time production area.\n3 - Et il continue avec les messages suivants en utilisant le message batch qui est dans sa RAM, jusqu'à ce qu'il y en ait un nouveau.\nLa limitation pourrait être la taille du message batch : s'il ne rentre pas dans la ram des VMs qui font le real-time processing, on peut fallback sur du batch processing.\nLes 3 cloud vendors principaux fournissent chacun deux outils pour le real-time processing : un outil de real-time storage type Kafka, et un outil qui fait le real-time processing.\nAWS.\nKinesis Data Streams est équivalent à Kafka.\nIl fournit des clients dans 5 langages, dont Node.js.\nIl a l'équivalent des topics sous le nom de Data Streams.\nIl a l'équivalent des partitions sous le nom de shard, et limite la throughput à 1 MB/s par shard.\nIl supporte le “resharding” à la hausse ou à la baisse.\nIl limite la taille des records à 1 MB.\nLa rétention par défaut est d'un jour, et va jusqu'à une semaine.\nKinesis Data Analytics est l'outil de processing real-time.\nIl fournit une API SQL pour créer les jobs, mais c'est limité à des records qui contiendront du CSV ou du JSON.\nIl fournit aussi une API Java, qui utilise Apache Flink et permet plus de flexibilité sur le format des records.\nIl ne fournit pas de mécanisme de déduplication.\nGCP.\nCloud Pub/Sub est un peu différent de Kafka et il abstrait plus de choses.\nIl fournit des clients dans 7 langages, dont Node.js.\nLes topics permettent de regrouper les records, mais il n'y a pas de notion de partition, ou en tout cas elle est abstraite derrière l'API.\nLes consumers peuvent faire une subscription à un topic pour consommer les records.\nIls peuvent aussi utiliser une subscription pour recevoir de la donnée combinée de plusieurs topics.\nOn se sert aussi des subscriptions pour scaler le throughput : on a le droit à 1 MB/s par subscription.\nLes records sont limités à 10 MB.\nLa rétention des données maximale est d'une semaine.\nIl ne fournit pas d'offsets pour les records, ce qui limite la possibilité de rejouer certains messages particuliers.\nOn peut faire des snapshots pour pouvoir les rejouer, mais ils sont limités à 5000 par projet.\nOn a aussi la possibilité de rejouer par timestamp, mais c'est peu précis.\nCloud Dataflow est l'outil de processing real-time.\nIl fournit une API SQL pour créer les jobs, mais c'est limité à des records qui contiendront du JSON.\nIl fournit aussi une API Java et Python, qui utilise Apache Beam et permet plus de flexibilité sur le format des records.\nIl permet de dédupliquer les messages issus de problèmes techniques, et propose aussi une déduplication des messages par ID, sur une fenêtre de 10 minutes.\nAzure.\nEvent Hubs est équivalent à Kafka.\nIl fournit des clients en .NET et Python, mais des versions open source sont disponibles pour d'autres langages.\nIl supporte 3 protocoles pour s'y intégrer en tant que producer ou consumer : HTTPS, AMQP et Kafka. Ça permet de migrer vers Azure sans avoir à tout réécrire.\nIl a l'équivalent des topics dans le cas de Kafka, ou des hubs dans le cas d'AMQP.\nIl a l'équivalent des partitions, qu'il faut définir à l'avance comme pour Kafka, et à l'inverse de Kinesis Data Streams pour lequel on peut “resharder”.\nLe throughput est limité à 1 MB/s ou 1000 messages/s par partition.\nLes records ne peuvent pas dépasser 1 MB.\nLa période de rétention maximale est d'une semaine.\nContrairement à Kinesis Data Streams qui stocke les offsets des consumers dans DynamoDB, ou à Kafka qui le stocke dans un topic interne, Event Hubs laisse cette responsabilité aux consumers.\nAzure Stream Analytics est l'outil de processing real-time.\nIl ne propose qu'une API SQL, avec des fonctionnalités avancées de type windowing, recherche dans des dictionnaires etc.\nSi on veut plus de flexibilité, on peut utiliser Spark à travers Azure Databricks, mais il s'agira de micro-batching et non pas de vrai streaming.\nIl ne fournit pas de fonctionnalités de déduplication.","7---metadata-layer-architecture#7 - Metadata layer architecture":"Il existe deux types de metadata dans le cadre de la data.\n1 - La business metadata permet de donner du contexte à la donnée.\nCa peut être par exemple : la source, le propriétaire de la donnée, la date de sa création, la taille de la donnée, le but de la donnée, son niveau de qualité etc.\nÇa aide notamment à trouver la donnée qu'on cherche.\nOn appelle souvent l'outillage autour de la business metadata le data catalog.\nLes cloud vendors ont chacun leur outil : Google Cloud Data Catalog, Azure Data Catalog, AWS Glue Data Catalog.\n2 - La data platform metadata (ou pipeline metadata) permet de rassembler des informations sur les pipelines de données.\nÇa peut être des informations sur les sources, sur le succès ou l'échec de runs de pipelines, les erreurs qui ont eu lieu etc.\nÇa permet notamment le monitoring et la configuration des pipelines.\nCette metadata est plus alignée avec la responsabilité des data engineers, et c'est sur elle que se concentre ce livre.\nUne seule pipeline simple peut être gérée avec du code, mais dès que le système de pipelines se complexifie, il faut gérer cette complexité.\nOn a le choix de :\n1 - Dupliquer le code des pipelines pour les rendre simples, mais alors il faudra refaire des modifications à plusieurs endroits à chaque fois qu'on voudra changer quelque chose qui concerne plusieurs pipelines.\n2 - Mettre du code en commun pour éviter de réécrire trop de choses, mais alors la codebase se complexifie, et l'investigation des problèmes aussi.\nLes auteurs du livre conseillent de mettre le code en commun, et de rendre les pipelines configurables pour éviter l'explosion de complexité.\nOn pourra par exemple mettre en commun l'ingestion de sources de type RDBMS, et celles de type file. Ou encore mettre en commun des jobs de data quality check.\nSi la configuration se trouve dans un endroit séparé, il devient facile de la changer sans avoir à toucher au code.\nParmi les éléments de configuration, il peut y avoir par exemple : l'endroit d'où on récupère la donnée, l'endroit où on l'envoie, les checks de qualité et transformations qu'il faut faire sur chaque donnée etc.\nLa data platform metadata a 3 fonctions :\n1 - Stocker les configurations des pipelines.\nPar exemple, si un path d'input sur un serveur FTP change, il suffira d'aller changer la configuration de la pipeline dans le metadata layer, sans toucher au code.\nPour connaître les inputs et outputs d'une pipeline, il suffira aussi de regarder sa configuration.\n2 - Monitorer l'exécution et le statut des pipelines.\nPar exemple, en cas d'erreur sur un pipeline, il suffira d'aller regarder dans le metadata layer pour avoir un statut détaillé de la pipeline, avec des statistiques d'échec, de nombre de duplicatas etc.\n3 - Servir de schema repository.\nCette partie sera plus développée dans le chapitre 8.\nIl n'existe pas vraiment de standard concernant le modèle d'un metadata layer.\nLes auteurs du livre en proposent un centré autour de 4 domaines, contenant les aspects qu'ils pensent être suffisamment universels.\n1 - La Pipeline Metadata contient les informations d'input, output et transformations de chaque pipeline.\nL'objet Namespace se trouve au plus haut niveau, et permet de séparer des groupes de pipelines.\nIl s'agit par exemple de pouvoir appliquer des droits d'accès différents à des ensembles de pipelines.\nOn pourra l'utiliser pour nommer les folders, ou les topics de notre système de slow et fast storage.\nSa structure est :\nID\nName\nDescription\nCreated At\nUpdated At\nL'objet Pipeline décrit un ensemble de jobs qui prend un ou plusieurs inputs, et écrit dans une ou plusieurs destinations, avec d'éventuelles transformations.\nLes pipelines seront souvent liées : par exemple une pipeline d'ingestion qui écrit dans le data lake, puis une autre qui lit cette donnée, la combine avec une autre, et écrit à nouveau dans le data lake.\nSa structure est :\nID\nName\nDescription\nType : indique par exemple si c'est une pipeline d'ingestion ou de transformation.\nVelocity : batch ou real-time.\nSources and Destinations : liste les identifiants d'objets Source desquels la pipeline lit, et Destination vers lesquels la pipeline écrit.\nEn général une pipeline d'ingestion aura une source et une destination, et une pipeline de transformation aura plusieurs sources et une destination.\nData Quality Checks IDs : une liste d'identifiants de checks de qualité à appliquer à l'ensemble des sources et destinations de la pipeline.\nCreated At\nUpdated At\nConnectivity Details : pour les pipelines d'ingestion, il s'agit d'avoir des informations sur les sources. Par exemple des URLs, adresses IP etc.\nAttention à ne pas stocker de username / mots de passe dans ce layer. Il vaut mieux les mettre dans des outils sécurisés comme Azure Key Vault, AWS Secrets Manager ou Google Cloud Secrets Manager.\nL'objet Source décrit un endroit dont on veut aller chercher de la donnée en entrée d'une pipeline.\nSa structure est :\nID\nName\nSchema ID : un lien vers le schema registry qui contient le schéma de cette source.\nData Quality Checks IDs : les checks de qualité à appliquer à chaque fois que cette source est utilisée.\nType : le type de source, par exemple “file”, “real-time topic”, “table”.\nCreated At\nUpdated At\nL'objet Destination est similaire à l'objet Source, mais les types peuvent être différents. Par exemple, on peut vouloir aussi mettre dans un key/value store.\nSa structure est :\nID\nName\nSchema ID\nData Quality Checks IDs\nType\nCreated At\nUpdated At\n2 - Les Data Quality Checks permettent d'identifier les données qui posent problème.\nIls s'appliquent à des pipelines et sources ou destinations sans êtres spécifiques à un namespace.\nIl existe deux types de data quality checks :\nLes proactive checks sont faits pour contrôler la donnée une par une, et s'assurer que la donnée de mauvaise qualité ne rentre pas.\nOn va souvent vérifier le format de la donnée, ou le fait que certaines valeurs soient cohérentes. Par exemple 24h dans un jour, pas de dates négatives etc.\nCes checks ne peuvent pas être trop lourds pour ne pas bloquer la pipeline trop longtemps.\nLes retrospective checks sont schédulés régulièrement, et opèrent sur de plus grandes quantités de données, pour s'assurer qu'on garde une certaine consistance sur l'ensemble.\nCa peut par exemple être de faire une jointure sur deux jeux de données de départements et d'employés, pour s'assurer qu'aucun département n'est sans employé.\nIls produisent des rapports réguliers pour donner lieu à d'éventuelles actions pour améliorer la qualité de la donnée.\nL'élément principal du data quality check est la règle à faire respecter. Il existe de nombreuses options sur la manière de l'implémenter.\nÇa peut être une requête SQL, ou encore un Domain Specific Language (DSL).\nLeur structure est :\nID\nName\nSeverity : la gravité du problème si la règle n'est pas respectée.\ninfo indique qu'on laisse passer la donnée, qu'on log le problème dans l'activity metadata, mais qu'on ne crée pas d'alerte.\nwarning indique qu'on laisse passer la donnée, et qu'on crée une alerte pour avertir un data engineer.\ncritical indique qu'on ne laisse pas passer la donnée et qu'on la met en quarantaine, avec aussi une alerte.\nRule : en fonction de la manière dont on gère nos règles, cet attribut contiendra quelque chose de différent.\nCreated At\nUpdated At\n3 - Les Pipeline Activities contiennent des informations de succès, échecs, statistiques etc. sur l'exécution régulière des pipelines.\nOn enregistre les informations de chaque pipeline qui tourne, et on ne supprime jamais ces données, pour pouvoir ensuite investiguer, ou faire des analyses dessus.\nOn pourra par exemple répondre à des questions comme :\nQuelle est la durée moyenne d'une pipeline ?\nCombien de rows lit en moyenne une pipeline ?\nCombien de données on collecte en moyenne depuis une source donnée ?\nParmi les éléments de structure que les auteurs ont trouvé utiles dans la plupart des contextes :\nActivity ID\nPipeline ID\nStart time, Stop time : début et fin de l'exécution de la pipeline.\nStatus : succès / échec.\nError Message : en cas d'échec, mettre l'erreur dans ce champ fait gagner beaucoup de temps de recherche dans les logs.\nSource and Destination Ids : la liste précise des sources et destinations qui ont été utilisées par la pipeline.\nRows Read : nombre de rows qui ont été lues, dans le cas de fichiers ça permet notamment de s'assurer qu'on a lu le fichier entier.\nRows Written\nBytes Read\nBytes Written : on peut l'utiliser pour du monitoring, par exemple pour s'assurer que la valeur ne vaut pas 0 si Bytes Read ne vaut pas 0.\nExtra : des infos additionnelles comme le path où le fichier a été écrit sur le storage, le nom du topic et le window dans le cas de real-time.\nDans le cas de real-time processing, c'est une bonne idée d'aligner le time window avec la fréquence d'écriture des messages dans le slow storage.\n4 - Schema Registry contient l'ensemble des versions des schémas des données entrantes. Il est détaillé au chapitre suivant.\nSelon l'expérience des auteurs, il n'y a pas d'outil open source ou commercial qui permette de mettre en œuvre le metadata layer de manière satisfaisante. Ils conseillent donc de le coder soi-même.\n1 - Une première solution simple est d'implémenter le metadata layer avec des fichiers.\nIl s'agit de la solution la plus simple, quand on a peu de sources et de pipelines.\nLa pipeline metadata peut être implémentée avec des fichiers de configuration de type JSON ou YAML par exemple.\nIl s'agit d'avoir par exemple un fichier pour les namespaces, un pour les pipelines etc.\nLes IDs doivent être assignés à la main.\nIl s'agira de les mettre dans le gestionnaire de version avec le reste du code, et de les déployer à chaque fois avec la pipeline de CI/CD.\nLes pipeline activities metadata sont l'équivalent de fichiers logs où la donnée afflue régulièrement.\nPour pouvoir chercher dedans, il faut un outil spécialisé qui permette de le faire, il s'agit des Cloud Log Aggregation Services : Azure Monitor avec Log Analytics sur Azure, Cloud Logging sur GCP, et Elasticsearch sur AWS.\n2 - Un cran de complexité au-dessus, on a l'utilisation d'une base de données pour stocker les fichiers de configuration (la pipeline metadata).\nLes fichiers de configuration sont toujours dans le gestionnaire de version, et servent de source de vérité pour la configuration du metadata layer. C'est nécessaire pour avoir un historique des changements.\nA chaque fois qu'un changement est fait dans ces fichiers, une migration sera faite sur la metadata database.\nL'avantage d'avoir cette DB, c'est qu'on va pouvoir faire des requêtes pour obtenir des informations spécifiques qui existent à travers les fichiers de config. Par exemple : “Je veux voir toutes les sources qui utilisent ce data quality check”.\nLa DB peut être une DB relationnelle ou une DB de document qui permettra plus de flexibilité sur l'évolution du schéma.\nDes exemples typiques peuvent être Google Cloud Datastore, Azure Cosmos DB et AWS DynamoDB.\n3 - Quand on a plusieurs équipes en charge des pipelines, il faut une solution qui puisse abstraire les détails d'implémentation exposés par la DB : on peut utiliser une metadata API.\nL'idée c'est que le changement dans la structure de la DB n'impactera pas de nombreux outils maintenus par plusieurs équipes différentes. On pourra par exemple faire plusieurs versions de l'API.\nLa metadata API est en général faite selon les principes REST.\nPour plus d'infos sur comment designer une API REST, il y a The Design of Web APIs d'Arnaud Lauret.\nIl faudra que l'ensemble des outils qui utilisaient la DB, y compris les pipelines, utilisent maintenant l'API pour accéder aux configurations.\nLes auteurs conseillent de commencer par implémenter la solution la plus simple qui satisfait les besoins actuels de la data platform, avec la possibilité de passer à la version un cran plus complexe dès que le besoin sera là.\nChaque solution se base sur la précédente en lui ajoutant quelque chose, donc ça devrait être relativement facile de migrer.\nParmi les outils qu'on peut trouver chez les cloud vendors, qui se rapprochent le plus de ce qu'on recherche avec notre metadata layer, il y a :\nAWS Glue Data Catalog stocke des informations à propos des sources et destinations, et des statistiques sur les runs des pipelines, ce qui fait de cet outil le plus proche de ce qu'on recherche.\nLe désavantage majeur c'est sa flexibilité : il faut implémenter les pipelines avec AWS Glue ETL, ce qui veut dire n'avoir que des batch jobs, et qui soient compatibles avec Glue (donc pas de source REST par exemple).\nAzure Data Catalog et Google Cloud Data Catalog sont plus orientées business metadata, et fournissent surtout de la data discovery : permettre aux utilisateurs de la donnée de faire une recherche dans une UI pour trouver la table qui les intéresse.\nParmi les outils open source, qui se rapprochent le plus de ce qu'on recherche avec notre metadata layer, il y a :\nApache Atlas permet de faire de la data discovery, mais aussi de gérer la configuration de pipelines de manière flexible : on peut utiliser les Types qu'il propose pour créer la configuration des namespaces, des pipelines, sources, destinations etc. avec des liens entre les objets.\nSon inconvénient principal est qu'il a été créé pour l'écosystème de Hadoop, et possède de nombreuses fonctionnalités qui lui sont dédiées.\nUn autre inconvénient est que c'est un outil open source qui nécessite de faire tourner d'autres outils open sources difficiles à administrer : HBase et Solr.\nDataHub est similaire à Atlas, dans la mesure où il est suffisamment flexible pour permettre d'implémenter le modèle décrit dans ce chapitre, et permet aussi la data discovery.\nIl a aussi l'inconvénient de nécessiter de faire tourner des outils open source difficiles à administrer : Kafka, MySQL, Elasticsearch et Neo4j.\nMarquez permet principalement de mettre à disposition des informations de data lineage, c'est-à-dire des informations sur l'origine des données.\nIl n'est pas assez flexible pour implémenter le modèle présenté dans ce chapitre.\nIl a l'avantage de ne nécessiter que PostgreSQL comme dépendance à faire tourner, et on peut le faire comme service managé.","8---schema-management#8 - Schema management":"Certaines organisations ont une approche proactive, et planifient les conséquences des changements dans les DBs opérationnelles sur les équipes data.\nD'autres ont une approche ”do nothing and wait for things to break”, et attendent simplement que la pipeline ETL casse pour que l'équipe data la répare en prenant en compte le changement de schéma.\nDans les architectures data traditionnelles basées sur le data warehouse, les données arrivent dans une landing table qui reproduit exactement leur schéma, et donc quand elles changent, l'ingestion casse.\nIl existe une approche alternative schema-on-read où il s'agit d'ingérer la donnée telle quelle dans un système de fichiers distribués, et dans ce cas on repousse le problème au processing layer.\nCoupler le schema-on-read avec une approche ”do nothing and wait for things to break” est plutôt une mauvaise idée selon les auteurs. Comme alternatives, on a :\n1 - Le schema as a contract où il s'agit pour l'équipe de développeurs d'enregistrer le schéma de leur source de donnée dans le schema repository, et d'en être responsables.\nIls doivent alors ne faire que des changements backward-compatibles dans leur DB. Par exemple ajouter des colonnes mais pas en renommer.\nPour que ça marche, il faut deux choses :\nUn grand niveau de maturité dans les process de développement, notamment d'un point de vue automatisation de du check de rétrocompatibilité dans la pipeline de CI.\nUn owner pour chaque source de donnée externe à l'organisation.\nDe l'expérience des auteurs, les organisations n'ont en général pas la maturité technique suffisante, et le besoin de schéma versionné venant après coup, il est difficile de convaincre les équipes opérationnelles de mettre en place le schema as a contract.\nNDLR : il s'agit de l'approche mise en avant par le Data Mesh.\n2 - La gestion du schéma dans la data platform. Dans ce cas, la responsabilité se trouve du côté de l'équipe qui gère la data platform.\nLes auteurs trouvent que cette solution marche bien dans pas mal de contextes. Elle a l'avantage de permettre de centraliser au même endroit les schémas des données qui viennent des équipes internes et ceux qui viennent de l'extérieur.\nCette centralisation permet ensuite d'avoir un catalogue de données dans lequel on peut fouiller.\nCa permet d'avoir un historique des schémas pour pouvoir utiliser n'importe quelle donnée archivée, ou faire du debugging.\nCa peut aussi permettre de détecter et ajuster les changements de schémas avant que la pipeline n'échoue.\nUne autre solution peut être de laisser aux équipes internes la responsabilité du schéma de leurs données, et de centraliser les schémas des sources externes chez l'équipe responsable de la data platform.\nDans le cas où la gestion des schémas se fait dans la data platform, elle doit être ajoutée en tant que 1ère étape du common data processing.\nLe module de schema-management va d'abord vérifier si un schéma existe déjà pour cette source.\nS'il n'existe pas, le module va inférer un schéma depuis les données, puis enregistrer ce schéma dans le schéma registry en tant que V1.\nS'il existe, le module va récupérer la dernière version depuis le schema registry, puis inférer le schéma depuis les données, créer un nouveau schéma compatible avec les deux et l'enregistrer en tant que version actuelle.\nL'inférence de schéma dont on est en train de parler se base sur Apache Spark.\nSpark est capable d'inférer le schéma de fichiers CSV, JSON, y compris s'il y a plusieurs records dedans.\nIl utilise un sample de records pour faire l'inférence, par défaut 1000, et ce nombre est configurable.\nS'il est trop faible on risque d'avoir une inférence faussée qui ne permet pas de parser l'ensemble des données. Et s'il est trop grand on risque d'avoir des problèmes de performance.\nPour une table d'une DB relationnelle par exemple, le nombre pourra être bas parce que la schéma est garanti par la DB.\nDans le cas où la donnée est différente entre deux records, Spark essayera de trouver un type qui englobe les deux. Par exemple, un nombre et un string vont donner un string.\nDans le cas où un type commun n'est pas possible, les données minoritaires seront placées dans le champ _corrupt_record.\nLe schéma inféré par Spark va d'abord être converti en schéma Avro avant d'être mis dans le schema registry.\nSi on utilise un outil qui ne supporte pas l'inférence de schéma, comme par exemple Google Cloud Dataflow basé sur Apache Beam, alors il faudra gérer les schémas à la main.\nDans le cas d'une real-time pipeline, on ne peut pas utiliser l'inférence à cause du problème de performance et de la quantité de schémas qui seraient générés.\nDans ce cas, les auteurs conseillent de laisser les développeurs qui génèrent les données de streaming maintenir le schéma.\nPour pouvoir avoir du monitoring sur les changements de schémas, le module de schema-management peut créer un log dans la partie Pipeline Activities du metadata layer à chaque fois qu'il trouve des données avec un schéma qui a changé.\nMême si l'ingestion et les common data processing steps peuvent se “réparer” automatiquement, la suite du processing peut ne pas donner le résultat voulu. Par exemple un rapport qui n'a plus les valeurs d'une colonne qui a été enlevée par la source.\nIl vaut mieux être alerté du changement de schéma, et prévenir les équipes qui utilisent les données de cette source, avant qu'ils ne s'aperçoivent du problème par eux-mêmes.\nCôté implémentation du schema registry :\nApache Avro est l'option conseillée par les auteurs pour servir de format de base pour l'ensemble des données de la data platform.\nSon schéma peut être écrit et maintenu à la main.\nSpark peut aussi transformer son schéma inféré en schéma Avro automatiquement.\nCes schémas peuvent être représentés par du simple JSON, et donc n'importe quelle DB qui supporte ça peut les héberger.\nAvro a un très bon système de gestion des versions des schémas.\nLes solutions cloud-natives de type data catalog permettent d'implémenter un schema registry, mais ont des limitations.\nLa plupart sont surtout orientés data discovery, et manquent de fonctionnalités concernant la gestion des versions des schémas et le support d'Avro.\nConfluent Schema Registry offre les fonctionnalités de gestion de version des schémas et un bon support d'Avro, mais il nécessite d'utiliser Kafka, ou un outil compatible avec Kafka.\nDonc si on utilise par exemple Kinesis, ou bien si on ne fait pas de real-time, on ne pourra pas utiliser leur schema registry.\nLa solution maison proposée par les auteurs consiste à avoir soit une DB, soit une API avec une DB derrière.\nLe solution pure texte stockée dans le gestionnaire de version, similaire au reste de la configuration du metadata layer, ne peut pas marcher pour le schema registry parce qu'il faut pouvoir le mettre à jour automatiquement.\nComme DB, on peut utiliser les mêmes Cosmos DB, Datastore et DynamoDB.\nLa structure des objets de schéma sera :\nID\nVersion : l'ID et la Version forment ensemble une clé unique. L'ID en elle-même n'est donc pas unique pour éviter d'avoir à mettre à jour en permanence les configurations des sources et destinations.\nSchema : le champ qui stocke le schéma Avro au format texte.\nCreated At\nUpdated At\nConcernant la stratégie de gestion de version des schémas.\nIl existe deux types de compatibilité entre les schémas :\n1 - Backward-compatible : la dernière version du schéma doit permettre de lire l'ensemble des données existantes, y compris produites par un ancien schéma.\nAvro impose des règles précises pour garder la backward-compatibility. Par exemple ajouter une colonne le permet, dans ce cas la lecture d'une donnée ancienne par un schéma récent donnera lieu à l'usage de la valeur par défaut pour la colonne manquante.\n2 - Forward-compatible : une version plus ancienne du schéma doit permettre de lire les données produites par une version plus récente.\nSi on reprend l'exemple de l'ajout de colonne, Avro permet une forward-compatibility : l'ancien schéma ignorera la nouvelle colonne au moment de la lecture de la nouvelle donnée.\nLe renommage de colonne est l'équivalent d'une création de colonne, et d'une suppression de colonne. Donc si on a des valeurs par défaut dans le schéma, elle sera à la fois backward-compatible et forward-compatible.\nAvro supporte aussi automatiquement certains changements de types, par exemple un entier 32 bits vers 64 bits. On peut aussi soi-même implémenter d'autres règles de conversion, mais les auteurs le déconseillent pour garder la complexité des pipelines faible.\nLes règles d'évolution de schéma d'Avro sont disponibles dans leur doc.\nLes common data transformation pipelines ne vont en général pas avoir besoin de la présence de colonnes spécifiques, et donc vont être résilientes aux changements de schémas.\nLes business processing pipelines en revanche vont y être beaucoup plus sensibles.\nLes auteurs conseillent d'utiliser les anciens schémas dans les pipelines, et de passer aux nouveaux quand les changements de code ont été faits. Ca veut dire s'efforcer à faire des changements de schémas forward-compatibles.\nQuelle que soit la stratégie, même si la pipeline ne casse pas grâce aux règles de backward / forward compatibility, il est possible qu'on se retrouve avec des erreurs logiques dans nos transformations.\nPar exemple, une colonne indiquant le nombre de ventes est renommée, et peut continuer à être lue de manière forward compatible avec la valeur par défaut NULL. Mais le dashboard se mettra à montrer une absence de ventes.\nIl n'y a pas de solution simple à ce problème. Il faut avoir un système de monitoring et d'alerting efficaces, et prévenir les clients en amont que leurs dashboards risquent d'avoir des incohérences le temps de mettre à jour le code.\nAlors que les fichiers peuvent avoir chacun leur version de schéma associée, la donnée qui se trouve dans une table du data warehouse ne peut pas avoir plusieurs schémas en même temps.\nOn ne peut pas simplement utiliser le schema registry pour mettre à jour la table du data warehouse, il va falloir le faire avec du code dans le module de schema-management, qui fait partie des common data transformations.\nÇa veut dire que les changements de schéma se feront quand même de manière automatique, avec des règles pré-établies où on va générer le bon SQL pour restructurer la table, en fonction de chaque changement de schéma Avro.\nOn ne peut pas non plus appliquer les mêmes règles qu'avec les transformations de schémas entre fichiers : dans le cas de suppression d'une colonne (ou de renommage, qui implique une suppression de fait), on va garder l'ancienne colonne quand même pour garder la donnée historique.\nParfois, quand les données ne sont pas trop grosses, il pourra être préférable de supprimer la colonne et de la recréer avec les données historiques et les nouvelles données dedans.\nCôté data warehouses des cloud vendors :\nAWS Redshift et Azure Synapse ont une approche similaire :\nIls sont ancrés dans le monde du relationnel, et nécessitent la définition du schéma avant de charger de la donnée.\nIls supportent ALTER TABLE pour faire des changements sur les tables.\nRedshift supporte Avro mais sans inférence à partir du schéma, alors que Synapse supporte seulement CSV, ORC et Parquet.\nGoogle BigQuery a une approche moins relationnelle, et permet d'inférer le schéma à partir de la donnée qu'on lui donne.\nIl va aussi ajouter des colonnes au schéma automatiquement en inférant le type, si on lui présente de la donnée qui a des colonnes en plus. Il le supporte pour Avro, JSON et Parquet.\nEn revanche, il ne permet pas de modifier les tables après coup, sauf en ajoutant ou supprimant des colonnes, ce qui peut prendre du temps et coûter cher.","9---data-access-and-security#9 - Data access and security":"Les données d'analytics sont utilisées par de plus en plus de personnes au sein des entreprises, et par des moyens variés.\n1 - Il y a les utilisateurs humains qui utilisent en général des outils BI ou veulent exécuter des requêtes SQL, et parfois des data scientists qui veulent accéder à la raw data.\n2 - Et il y a les applications qui utilisent la donnée par exemple pour des applications ML de recommandation ou de prédiction. Le data warehouse ne suffit pas pour ces cas d'usage.\nLe data warehouse reste quand même l'outil le plus commun pour accéder à la donnée d'analytics, du fait de la compatibilité avec les outils BI et du support du SQL.\nAWS Redshift.\nIl s'agit d'un data warehouse distributé, c'est-à-dire qu'il répartit la donnée sur plusieurs machines.\nUn nœud leader reçoit les requêtes et répartit le travail à faire et les données sur les autres nœuds.\nLes autres nœuds eux-mêmes sont subdivisés en slices. Ces slices peuvent être déplacés de nœud en nœud, pour équilibrer la capacité par du rebalancing.\nQuand on crée une table, on peut indiquer sa propriété DISTSTYLE pour choisir la manière dont ses données seront distribuées sur les nœuds. C'est le réglage de performance le plus impactant.\nALL : une copie de la table est créée sur chaque nœud. On ne peut le faire qu'avec les petites tables qui sont souvent l'objet de jointures.\nEVEN : les rows de la table sont répartis de manière équitable sur les nœuds.\nKEY : permet d'indiquer une colonne dont les valeurs identiques donneront lieu à ce que la donnée soit stockée sur la même machine.\nAUTO : vaut ALL au début, et passe à EVEN quand la table grandit.\nIl est basé sur PostgreSQL et présente les caractéristiques des DB relationnelles.\nIl ne supporte que les types “primitifs”, c'est-à-dire pas les tableaux ou les objets imbriqués. Il est donc peu adapté à de la donnée JSON, avec laquelle les optimisations d'encodage ou de distribution dans les nœuds par clé ne pourront pas servir.\nOn peut optimiser la taille des données en choisissant le type d'encodage pour les colonnes : par exemple dans le cas où une colonne peut avoir seulement quelques valeurs possibles, l'encodage byte-dictionary permet de limiter la taille de ces données.\nIl possède une fonctionnalité appelée Spectrum, qui permet de créer une table dans Redshift, dont les données sont sur S3.\nCa permet d'éviter d'utiliser des ressources CPU et de l'espace sur le data warehouse, pour des données qu'on veut juste explorer par exemple.\nLes performances seront du coup moins bonnes que les données qui sont sur les nœuds Redshift.\nLes auteurs recommandent de créer une DB dédiée sur Redshift pour regrouper ces tables qui pointent vers ailleurs.\nAzure Synapse.\nC'est une DB distribuée comme Redshift, avec un control node principal qui reçoit les requêtes, et qui fait appel aux autres nœuds.\nIl y a une séparation storage / compute. Les données sont séparées en 60 distributions, et sont associées à des compute nodes.\nIl n'est pas complètement élastique, puisque pour redimensionner le cluster, il faut tout arrêter, et ça peut prendre du temps.\nLes tables peuvent être configurées pour la répartition de leurs données, de la même manière que Redshift.\nREPLICATE : l'équivalent de ALL, c'est-à-dire copier sur chaque nœud.\nROUND ROBIN : l'équivalent de EVEN, c'est-à-dire répartir entre les nœuds.\nHASH : l'équivalent de KEY, c'est-à-dire spécifier une colonne dont les valeurs permettront de répartir les données.\nIl présente des caractéristiques relationnelles.\nIl supporte seulement les types primitifs, et fournit des fonctions de parsing pour JSON, mais au prix de nombreuses optimisations perdues.\nIl a une fonctionnalité similaire à Spectrum, configurable par la notion de pools.\nSQL pool représente l'utilisation normale du data warehouse.\nSQL on-demand pool permet de faire des requêtes sur des données sur Azur Blob Storage au format Parquet, CSV ou JSON.\nSpark pool permet de faire des requêtes avec Spark, sur des données qui sont dans Azur Blob Storage. Ils permettent l'auto-scaling, mais nécessitent que 3 nœuds tournent en permanence.\nGoogle BigQuery.\nBigQuery est un peu plus “managé” que les deux autres, dans la mesure où il n'y a pas de besoin de planifier la capacité dont on aura besoin à l'avance.\nLa puissance de calcul est “provisionnée” à chaque requête, grâce à des groupes de dizaines de milliers de nœuds qui tournent en permanence dans l'infra de Google.\nComme il est plus managé, on peut aussi moins facilement contrôler la manière dont les données d'une table sont réparties au sein des nœuds.\nOn a quand même la notion de partitioning qui permet de répartir les données selon les valeurs d'une colonne.\nEt de clustering qui permet d'organiser physiquement les données de manière à rendre les requêtes qu'on fait le plus souvent plus efficaces.\nLe pricing se fait aussi sur la quantité de données traitée, ce qui peut être avantageux quand on a de petits besoins, mais rend les coûts difficilement prédictibles.\nLes nœuds de calcul sont sur des machines différentes des nœuds de stockage : on n'a pas de data locality.\nC'est moins rapide que si la donnée était locale, mais ça évite d'avoir à recopier la donnée à chaque rebalancing. La donnée est accédée via le réseau local de Google qui est suffisamment performant pour que ça passe.\nBigQuery vient initialement plutôt d'un système permettant de traiter des fichiers de log, et non pas un système relationnel comme les deux autres.\nIl a un support natif des structures imbriquées, et peut traiter le JSON comme une structure et pas juste du texte, avec la possibilité d'appliquer des traitements sur les attributs.\nIl est du coup moins facilement compatible avec les outils BI, il faudra passer par une API REST.\nLes grandes organisations peuvent tirer parti de l'utilisation de plusieurs cloud providers, mais pour les petites le coût opérationnel n'en vaut pas la peine. Le choix du data warehouse dépendra donc en général du choix du cloud provider pour le reste de l'infra.\nLes applications utilisent de plus en plus la data dans des systèmes customer-facing, par exemple dans des systèmes de recommandation.\nDonner à l'application un accès au data warehouse serait une mauvaise idée pour plusieurs raisons :\nLes data warehouses ne sont pas conçues pour offrir des latences se comptant en millisecondes, mais en secondes voire minutes sur de grandes quantités de données.\nIls ne sont pas conçus pour supporter un trop grand nombre de transactions en même temps (par exemple des dizaines ou centaines de milliers) comme pourrait le nécessiter une application.\nSi l'application est compromise, l'ensemble du contenu du data warehouse pourrait fuiter, alors que si l'application a seulement accès à une DB qui a ce dont elle a besoin, on aura une meilleure sécurité.\n1 - Cloud relational databases.\nChaque cloud provider a ses services de DBs managées, qui tiennent sans problèmes jusqu'à 1 TB. Au-delà de ça, ou si on a besoin de situer les machines géographiquement, il faut une DB distribuée.\nAWS propose Relational Database Service (RDS) pour PostgreSQL, MySQL, MariaDB, Oracle et SQL Server.\nIl propose Aurora comme DB distribuée, compatible avec MySQL et PostgreSQL.\nGCP propose Google Cloud SQL, qui supporte MySQL, PostgreSQL et SQL Server.\nIl propose Google Cloud Spanner pour la version distribuée.\nAzure propose Azure SQL Database, qui supporte MySQL, PostgreSQL et SQL Server.\nIl propose HyperScale pour la version distribuée, disponible seulement pour SQL Server.\n2 - Cloud key / value data stores.\nLes services key/value offrent une faible latence pour insérer et retrouver des valeurs par leur clé.\nIls sont souvent utilisés par les nouveaux projets pour pouvoir itérer vite sans avoir de migration à faire.\nLes cloud providers proposent soit une version pay per use plus avantageuse en cas de faible utilisation, et une version pay per provisioned capacity plus avantageuse en cas de grosse utilisation.\nAWS propose DynamoDB, qui reste performant quel que soit le scale, et offre les deux types de facturation.\nGCP propose Datastore qui est similaire à DynamoDB et qui propose du pay per use, et Cloud Bigtable qui ne permet pas de mettre de contrainte de types sur les données, et supporte le price per provisioned capacity.\nCloud Bigtable est d'ailleurs compatible avec HBase.\nAzure propose CosmosDB, qui a la particularité de supporter les API clientes de MongoDB, Cassandra, SQL et de graph API, ce qui rend le portage depuis ces technos facile.\n3 - Full-text search services.\nDans le cas où la fonctionnalité de notre application est de permettre une recherche dans la donnée, il existe Solr et Elasticsearch, tous deux basés sur Lucene.\nPar exemple, si on veut chercher quelque chose de similaire à ce qui est tapé par l'utilisateur.\nAWS propose CloudSearch, Azure propose Azure Search, et GCP ne propose rien de managé au moment de l'écriture du livre.\n4 - In-memory cache.\nLes caches permettent des temps d'accès inférieurs à la milliseconde grâce au stockage en RAM. Ils doivent être liés à une DB persistante pour pouvoir être reconstruits.\nAWS propose ElasticCache, qui supporte Memcached et Redis, GCP propose Memorystore qui supporte Memcached, et Azure propose Azure Cache qui supporte Redis.\nLes modèles de machine learning nécessitent l'accès à une grande quantité de données variée, une grande puissance de calcul, et l'accès à des outils spécifiques. La cloud data platform est parfaitement adaptée à ça.\nDans les plateformes traditionnelles, les data scientists passent 80% de leur temps à récupérer la donnée sur leur machine, et la nettoyer et la transformer pour qu'elle puisse être interprétée par leurs outils.\nIls vont ensuite faire des tests exploratoires pour comprendre ce qu'ils peuvent faire ce cette donnée.\nPuis ils séparent la donnée en deux : la donnée d'entraînement et la donnée de validation.\nIls vont faire un cycle entraînement / validation où ils vont plusieurs fois améliorer le modèle puis le tester contre la donnée de validation.\nCette validation permet d'éviter l'overfitting où le modèle ne serait bon que sur les données avec lesquelles il s'est entraîné.\nFaire l'entraînement sur leur machine locale leur prend beaucoup de temps.\nUne fois que le modèle est fonctionnel, il faut le rendre production-ready pour le déployer, en ajoutant du logging, de la gestion d'erreurs etc. ce qui est souvent difficile.\nLa cloud data platform aide au développement de modèles ML.\nUne bonne partie de la mise en forme et de la validation des données est faite dans l'ingestion layer et dans le processing layer avec les common data transformation steps.\nLes data scientists peuvent copier la donnée comme ils veulent dans le storage de la cloud data platform, et faire de l'exploration ou du processing sans télécharger les données en local.\nIls peuvent collaborer sur un même jeu de données puisqu'il est dans le cloud, et peuvent avoir accès à de la donnée de production en grande quantité.\nChacun des cloud vendors fournit un service de ML permettant de gérer un projet ML de bout en bout, et de mieux collaborer entre data scientists : SageMaker chez AWS, AI Platform chez GCP, et Azure ML chez Azure.\nLa business intelligence et le reporting sont en général le premier usage de la donnée de type analytics.\nCes outils nécessitent souvent que la donnée soit relationnelle, c'est-à-dire que chaque donnée soit dans sa colonne avec la table “à plat” reliée à d'autres tables par des clés étrangères, plutôt que d'avoir des données imbriquées comme dans du JSON.\nBigQuery commence à être supporté par des outils comme Tableau, mais tous ne le supportent pas correctement.\nBien que de nombreux outils BI supportent Spark SQL, et pourraient se brancher directement sur le data lake, les auteurs le déconseillent parce que ça rendrait l'interface de ces outils peu interactive et lente. Se brancher sur le data warehouse est bien plus adapté pour cette raison.\nExcel peut se brancher sur le data warehouse grâce à son API JDBC/ODBC, mais c'est un outil qui tourne sur une machine locale, donc il sera limité sur la quantité de données, et télécharger les données sur sa machine locale pose des problèmes de performance.\nOn voit souvent des outils externes, par exemple chez d'autres cloud providers, accéder à la donnée de la cloud data platform.\nIl faut faire attention aux coûts de sortie des données (data egress costs), que chaque cloud provider applique.\nChaque cloud provider a sa solution BI : Azure Power BI qui est très connu, AWS QuickSight, et DataStudio et Looker BI pour GCP.\nLa sécurité est essentielle pour une plateforme data.\nIl vaut mieux éviter les accès ad hoc dès qu'il y a un besoin, mais plutôt utiliser les concepts de Users, Groups et Roles fournis par les cloud providers.\nLes groupes facilitent grandement la gestion des permissions, il vaut mieux les configurer à ce niveau là dans la mesure du possible.\nUne bonne pratique est de ne fournir que les permissions nécessaires à chaque type d'utilisateur (principle of least privilege).\nIl existe des outils cloud-native pour l'authentification, à la place des mots de passe, par exemple Azure Active Directory. Les auteurs conseillent de les utiliser quand c'est possible.\nCertaines configurations permettent de rendre des services accessibles publiquement. Pour limiter le risque, on peut faire diverses choses comme des audits, ou l'utilisation du principe infrastructure-as-code.\nDans le cas où on a des données sensibles, il ne faut pas hésiter à chiffrer des colonnes particulières.\nUne autre solution peut être de limiter l'accès réseau à la donnée, dans le cas où les utilisateurs seraient sur un réseau particulier.","10---fueling-business-value-with-data-platforms#10 - Fueling business value with data platforms":"La data platform doit être organisée autour d'une data strategy, c'est-à-dire être au service des objectifs business.\nParmi les grands objectifs business, on trouve :\nAugmenter les revenus.\nAugmenter l'efficacité opérationnelle.\nAméliorer l'expérience utilisateur.\nPermettre l'innovation.\nAméliorer la conformité.\nExemples :\nDans le cas d'une entreprise de jeux vidéo qui veut maximiser les achats ou la publicité in-game, la stratégie peut être d'optimiser la plateforme data pour du real-time processing des événements du jeu.\nDans le cas d'une entreprise minière qui veut réduire ses coûts opérationnels, la stratégie peut être d'optimiser la plateforme pour ingérer la donnée des capteurs des engins miniers, et prédire quand faire la maintenance.\nLa maturité data d'une organisation passe par 4 étapes.\n1 - See : le business veut voir des rapports et des dashboards pour mieux comprendre ce qui se passe par rapport à ce qui s'est passé dans le passé.\nTraditionnellement les rapports sont créés par des personnes spécialistes de ces outils, à la demande du business.\nDans les plateformes modernes, on applique le principe Bring Your Own Analytics (BYOA), où les personnes du business utilisent leurs propres outils qu'ils branchent sur la data platform, pour créer leurs rapports.\nCes outils sont branchés sur le data warehouse.\n2 - Predict : une fois qu'on a ce qui s'est passé et se passe, on veut prédire ce qui va se passer, par exemple avec du ML.\nIl faut que la plateforme puisse proposer une grande quantité de données.\nLes données brutes vont être plutôt sur le data lake, et les données raffinées sur le data warehouse.\n3 - Do : on va donner le résultat des deux premières étapes à des systèmes pour déclencher des actions.\nÇa peut être du ML avec par exemple des systèmes de recommandation, ou même simplement de la donnée qui est déplacée vers le système opérationnel pour servir les clients.\nLe fait de déplacer des données du monde analytics au monde opérationnel s'appelle l'orchestration.\nÇa implique que le système qui utilise cette donnée soit disponible et réponde aux exigences d'un système de production.\n4 - Create : la donnée initialement collectée comme analytics devient la source pour un nouveau produit.\nPar exemple, une banque qui a collecté des données pour améliorer l'expérience utilisateur en aidant les agents à anticiper les réactions des clients, s'est rendue compte qu'elle pouvait l'utiliser aussi pour améliorer l'app mobile.\nAutre exemple, une entreprise de sécurité s'est servie des dashboards construits pour visualiser les intrusions, pour montrer aux clients en quoi elle leur apportait de la valeur avec tous les risques qu'elle a évités.\nCertains challenges non techniques peuvent faire échouer la cloud data platform.\nDélivrer de valeur business rapidement : le business a besoin d'itérations qui résolvent de vrais besoins au bout de quelques mois maximum.\nLes auteurs conseillent de partir d'un use-case pas trop complexe lié à la data, et de l'implémenter en faisant avancer la plateforme. Et on passe comme ça de use-cases en use-cases.\nL'alternative moins intéressante c'est d'ingérer toutes les sources possibles, pour finir par trouver des cas d'usage avec les sources qu'on supporte déjà.\nFaire adopter la plateforme par les utilisateurs : les utilisateurs ont peut-être déjà leur manière de travailler avec les analytics, en particulier la production de rapport traditionnelle plutôt que la data self-service.\nIl y a les early adopters qui supportent la nouvelle plateforme, les blockers qui montrent leur scepticisme publiquement, les chickens qui ont peur de tout ce qui est nouveau, et les avoiders qui ne veulent pas toucher à ce qui est nouveau.\nQuelques conseils pour avoir une meilleure adoption :\nS'assurer que les premiers utilisateurs soient à la fois des early adopters et des employés influents.\nAprès les early adopters, on peut aider un blocker pour tenter de le retourner. Si ça marche c'est excellent pour le projet.\nLes chickens ont besoin de beaucoup de formation.\nLes avoiders mettront plus de temps, mais c'est OK.\nCe serait bien d'avoir un sponsor C-level qui soutient le projet, et crée de la visibilité pour lui.\nExemple : Disney avait lancé un concours interne où les utilisateurs data pouvaient montrer leurs résultats avec la plateforme et être récompensés.\nObtenir et garder la confiance des utilisateurs : il faut que la qualité de la donnée soit suffisamment bonne pour que les utilisateurs aient confiance en elle.\nPrendre en compte l'utilisateur de la donnée et ses besoins rendre dans le cadre de la data governance.\nParmi les métriques de qualité à surveiller, il peut y avoir le pourcentage de données correctes, les champs obligatoires remplis, la précision, la consistance, l'intégrité de la donnée etc.\nQuand la qualité qu'on s'est fixée n'est plus respectée, il faut :\n1 - prévenir les consommateurs de la donnée.\n2 - Mettre l'équipe sur le coup pour régler le problème au plus vite.\nÉviter la formation de d'un silo autour de la data platform : la responsabilité de la donnée, des règles de qualité et la mesure de la qualité, les SLA etc. doivent être drivées par le business.\nLa donnée part des sources potentiellement extérieures, et irrigue l'organisation à travers divers systèmes. On ne peut pas considérer que la responsabilité de l'équipe technique s'arrête au moment où la donnée sort de la plateforme.\nIl faut constituer des équipes pluridisciplinaires capables de prendre en charge la responsabilité du système de bout en bout : le fonctionnement de la plateforme et l'utilisation de la donnée.\nPrendre en compte les coûts : pour que la plateforme soit un succès, il faut adopter le point de vue de l'entreprise.\nIl faut s'intéresser aux manières d'optimiser les coûts des services cloud (FinOps), et comprendre les trade-offs qui y sont liés.\nEt à l'inverse s'intéresser à ce que la plateforme permet de rapporter d'un point de vue business.\nExemple : une grande entreprise de télécommunications recueille des données IoT.\nLa bonne pratique est de faire le processing dans le data lake avec Spark, mais il se trouve que le business avait un deal avec GCP pour une utilisation illimitée de BigQuery à prix fixe.\nDans ce cas, la bonne chose à faire sera sans doute de faire des concessions sur le design, et faire le processing dans le data warehouse."}},"/books/designing-data-intensive-applications":{"title":"Designing Data-Intensive Applications","data":{"1---reliable-scalable-and-maintainable-applications#1 - Reliable, scalable and maintainable applications":"Data-intensive désigne le fait que les données soient le bottleneck, par opposition à compute-intensive qui fait référence au CPU.\nLes frontières entre les différentes catégories (base de données, cache, système de queuing etc.) deviennent parfois floues. Par ex : Redis est un cache utilisé comme système de queuing, ou encore Kafka qui est un système de queuing avec une garantie de persistance comme une BDD.\nIl y a 3 enjeux principaux auxquels on répond quand on conçoit un système de données :\nLa fiabilité (reliability) consiste à fonctionner correctement malgré les fautes matérielles, logicielles, ou humaines.\nLes disques durs sont connus pour faire des fautes tous les 10 à 50 ans, ce qui veut dire que sur un parc de 10 000 disques, il y en a un qui saute tous les jours. On peut prévenir ce genre de problème par de la redondance (RAID par ex).\nLes fautes logicielles sont beaucoup plus insidieuses, et peuvent causer des dégâts en chaîne. Pour les prévenir on peut mettre en place du monitoring, prévoir des restarts de processus en cas de crash etc. Mais ça reste bien maigre en soi.\nLes fautes humaines sont inévitables, il faut concevoir les systèmes de manière à décourager les actions problématiques, faire beaucoup de tests automatisés, rendre facile le fallback etc.\nLa scalabilité consiste à accompagner le système dans sa montée en charge en termes de données, de trafic ou de complexité.\nParler de scalabilité tout court n'a pas vraiment de sens, il faut préciser sur quel aspect on scale.\nIl faut d'abord décrire le load sur lequel on veut scaler. Par ex (page 11) : pour twitter le load clé c'est le nombre de followers par personne :\nla 1ère solution consiste à recréer la timeline de tweets de chaque utilisateur depuis la base de données\nla 2ème à constituer des timelines à jour dans un cache, et de mettre à jour les timelines des followers à chaque tweet. Du coup avec la solution 2 tout dépend du nombre de followers.\nTwitter a fini par adopter une solution hybride : la 2ème solution par défaut, et la 1ère pour les comptes avec énormément de followers. Par défaut la timeline est dans le cache, mais si une célébrité est suivie, une requête sera faite pour récupérer les tweets.\nEnsuite il faut décrire la métrique de performance. Il s'agit d'augmenter le load qu'on a décrit pour voir jusqu'où on tient.\nSi notre métrique concerne un service en ligne, on va en général prendre le temps de réponse.\n(Le temps de réponse et la latence sont différents : la latence concerne le temps pendant lequel la requête est latente, c'est-à-dire qu'elle attend d'être traitée. Le temps de réponse est plus long que ça.)\nIl faut reproduire la requête un grand nombre de fois, et prendre la médiane pour avoir une idée du temps que ça prend. Dans la même idée on peut prendre les percentiles pour voir par ex. si on arrive à rester sous un certain seuil pour 99.9% de nos requêtes (appelé p999).\nPour répondre aux problématiques de scalabilité :\nUne réponse à un certain load ne marchera pas pour un load beaucoup plus important : il faut repenser régulièrement son architecture si on scale vraiment.\nIl y a le scale vertical (machine plus puissante) et le scale horizontal (plus de machines, qu'on appelle aussi shared-nothing architecture).\nEn réalité, on utilise souvent un mix des deux : des machines puissantes pour certaines tâches, et du scaling horizontal pour d'autres.\nLa création de machines supplémentaires peut être manuelle ou “élastique”. La version élastique permet d'adapter aux grandes variations mais est plus complexe aussi.\nHabituellement, avoir une application stateful qui est sur plusieurs machines est difficile à gérer, donc on essaye de garder la BDD sur une seule machine jusqu'à ce que ce ne soit plus possible. Avec l'évolution des outils, ceci sera sans doute amené à changer.\nIl n'y a pas de magic scaling sauce : chaque application de grande échelle a ses propres contraintes, ses propres bottlenecks, et donc sa propre architecture.\nQuand on crée un produit, il vaut au début passer surtout du temps à développer les fonctionnalités qu'à penser son hypothétique scaling.\nLa maintenabilité consiste à pouvoir à la fois perpétuer le système et le faire évoluer en un temps de travail raisonnable.\nPour qu'un système soit maintenable dans le temps, il faut travailler sur ces aspects :\noperability : la facilité pour les ops de faire tourner le système.\nIl faut faciliter la vie au maximum pour les ops. Ex : fournir un bon monitoring, permettre d'éteindre une machine individuellement sans affecter le reste, avoir de bonnes valeurs par défaut et un comportement auto-réparateur, tout en permettant aux ops de prendre la main.\nsimplicity : que le système soit le moins complexe possible pour le comprendre rapidement et pouvoir travailler dessus.\nOn peut par exemple réduire la complexité accidentelle, c'est-à-dire la complexité non nécessaire liée seulement à l'implémentation mauvaise.\nSinon globalement une bonne chose à faire c'est d'introduire des abstractions pour appréhender le système plus facilement. Par ex. les langages haut niveau sont des abstractions de ce qui se passe dans la machine.\nevolvability : la facilité à changer ou ajouter des fonctionnalités au système.\nIl s'agit ici de l'agilité mais appliquée à tout un système, et pas à de petites fonctionnalités.","2---data-models-and-query-languages#2 - Data Models and Query Languages":"Le modèle de données relationnel a dominé le stockage depuis les années 70, en apportant de l'abstraction autour de la manière dont les données étaient structurées, contrairement aux autres alternatives.\nToutes les tentatives de détrôner SQL ont échoué, la hype est retombée.\nNoSQL arrive dans les années 2010 et regroupe tout un ensemble de technologies qui permettent de pallier aux problématiques de scalabilité, et d'offrir une plus grande flexibilité que les BDD relationnelles\nParmi elles, il y a notamment les BDD basées sur le modèle de document.\nIl est probable que les BDD relationnelles et NoSQL soient utilisées conjointement dans le futur.\nIl y a un décalage entre la POO et le format de BDD relationnel, qui oblige à une forme de conversion. Pour certaines données on pourrait utiliser une structure en document comme JSON par exemple au lieu du relationnel. Par ex pour le cas des infos d'un CV, on pourrait la ville d'un job autant de fois qu'elle apparaît.\nOn répète alors éventuellement plusieurs fois certaines informations dans les entrées, ou alors on les met dans une table à part mais on fait les jointures à la main depuis le code applicatif.\nEn réalité, ce problème est apparu dès les années 70. Le modèle hiérarchique (proche du modèle sous forme de document qui a fait résurgence récemment donc) faisait face à 2 autres modèles : le modèle relationnel et le modèle en réseau (network model) qui a fini par être abandonné.\nLe modèle en réseau consistait à avoir un modèle hiérarchique mais avec la possibilité pour chaque donnée d'avoir plusieurs parents. Mais ça rendait le code applicatif difficile à maintenir.\nLa normalisation consiste justement dans les BDD relationnelles à trouver ce genre de répétition, et à les factoriser en une nouvelle table. Le but est d'éviter la duplication, et donc de renforcer la consistance des données. Ça permet aussi de les modifier facilement en un seul endroit.\nComparaison aujourd'hui du modèle relationnel et du modèle de document :\nSimplicité du code applicatif :\nLe modèle de document mène à un code applicatif plus simple dans le cas où il y a peu de relations many to many ou many to one (pour les one to many c'est ok puisqu'on répète de toute façon la donnée dans la table du modèle de document).\nDans le cas contraire il faudrait faire les jointures à la main donc le modèle relationnel serait meilleur (code applicatif plus simple et jointures par la BDD plus efficaces).\nDans le cas où il y a une forte interconnexion entre les données (de nombreuses relations many to many), c'est alors le modèle en graphe qui serait le plus pertinent.\nFlexibilité du schéma de données :\nC'est un peu comme la différence entre le typage statique et dynamique des langages de programmation : le modèle relationnel force à déclarer un type de données et à s'y conformer ou faire une migration. Le modèle de document permet de changer de type de données en cours de route et donc la gestion des données est entièrement confiée à l'application, qui gagne en liberté et du coup en responsabilité.\nLe modèle de document est vraiment meilleur quand les données sont de type hétérogène, ou encore si elles sont déterminées par un système extérieur sur lequel la BDD n'a pas le contrôle.\nLocalité des données :\nVu que dans le modèle de document les données sont copiées dans chaque entrée, elles sont locales à celles-ci. On peut donc les avoir avec juste une requête, et on utilise moins le disque dur qu'avec le modèle relationnel. En revanche on va chercher le document entier, donc si on a souvent besoin d'un tout petit morceau ça n'en vaut peut être pas le coup.\nCertaines BDD relationnelles permettent aussi de localiser des tables vis-à-vis d'autres (ex : Spanner database de Google, Oracle, ou encore Bigtable data model (utilisé dans Cassandra et Hbase).\nLes différentes implémentations de BDD ont tendance à converger : la plupart des BDD relationnelles supportent les opérations dans du contenu XML ou JSON, et RethinkDB et MongoDB permettent de faire une forme de jointure automatique, même si moins efficace.\nLe modèle relationnel offre un langage déclaratif, alors que le modèle hiérarchique n'offre qu'un langage impératif. L'avantage du déclaratif c'est que ça abstrait des détails qui peuvent être laissés à la discrétion de l'outil de BDD qu'on utilise pour faire des optimisations.\nMapReduce, qui est un modèle popularisé par Google et disponible dans MongoDB, CouchDB et Hadoop est entre le déclaratif et l'impératif. Il abstrait certaines opérations mais permet aussi d'ajouter du code en plein milieu d'une requête qui aurait été atomique en SQL.\nDans les bases de données de graphes, les données sont représentées sous forme d'entités reliés par des traits.\nEx : Facebook utilise un graphe géant où sont présentes des entités variées (personne, lieu, commentaire), reliés entre eux avec des types de liens différents.\nModèle property graph (implémenté par Neo4j, Titan, InfiniteGraph) :\nIl y a deux tables : les entités (vertices) et les traits (edges) avec chacun leurs propriétés, et pour les edges la liste des couples d'entités reliés par son biais.\nOn peut facilement créer de nouveaux types de liens, sans avoir besoin de vraiment modifier la structure de la BDD.\nLe langage Cypher est un langage déclaratif inventé pour Neo4j.\nL'avantage c'est que le langage de graphe permet de trouver des données en parcourant un nombre indéterminé de chemins, et donc de faire un nombre non connu à l'avance de jointures. C'est possible en SQL mais avec une syntaxe beaucoup plus longue.\nModèle triple-store (implémenté par Datomic, AllegroGraph) :\nIl s'agit de la même chose que le property graph, mais présenté différemment : on a un groupe de 3 données qui sont (sujet, prédicat, objet).\nTurtle et SPARQL sont des langages qui permettent d'utiliser le triple-store.","3---storage-and-retrieval#3 - Storage and retrieval":"Un des moyens d'organiser les données dans une BDD est d'utiliser un système de log : l'ajout de données est fait en ajoutant le contenu à la fin d'un fichier (ce qui est très rapide), et la lecture est faite en parcourant l'ensemble des données (ce qui est très lent O(n)).\nPour accélérer la lecture, on peut créer des index sur les champs dont on estime qu'ils vont souvent servir à faire des recherches. Ça accélère la lecture, mais ça ralentit l'écriture puisqu'il faudra mettre à jour l'index à chaque fois.\nOn peut utiliser des Hash index tels que implémentés dans Bitcast, le moteur de stockage de Riak. Il s'agit d'avoir une structure associant une clé à un offset en mémoire vive. A chaque recherche on n'a qu'à trouver la clé et on peut directement lire la donnée sur disque.\nPour des raisons pratiques (consistance des données, performance grâce aux opérations séquentielles et non pas random), les fichiers de BDD ne sont jamais modifiés. On écrit les nouvelles données séquentiellement (donc pas de concurrence pour l'écriture) toujours à la fin du fichier, et on fait du ménage dans le fichier dans un nouveau fichier de BDD régulièrement. Pareil pour supprimer une donnée : on insère une commande dans le fichier et ce sera supprimé à la prochaine copie / optimisation du fichier de BDD.\nLes limitations c'est qu'il faut que les clés tiennent en mémoire vive sinon c'est foutu, et que les recherches de “ranges” de clés ne sont pas efficaces, ça revient à chercher les clés une par une.\nOn peut aussi stocker les données sous forme triée dès le début. On a alors les Sorted String Table (SSTable). Ca consiste à avoir une structure d'arbre triée en mémoire où vont les nouvelles données (qu'on va appeler la memtable). Et tous les quelques Mo on écrit ça sur DD. Puis régulièrement on va faire des opérations en tâche de fond pour grouper les arbres triés en un seul. Lors d'une recherche, on va d'abord chercher dans le bloc le plus récent, puis de moins en moins récent, jusqu'à arriver au gros bloc, sachant que tous les blocs sont déjà triés.\nUn des avantages c'est qu'on n'a plus à faire entrer toutes les clés en RAM. On peut avoir en mémoire un nombre de clés beaucoup plus épars qui indique les offsets.\nAutre avantage aussi qui répond au problème du hash index : on peut faire des recherches de “range” d'index, vu que tout est déjà trié.\nEt aussi, comme tout est déjà trié et qu'on a les offsets des données groupe par groupe, on peut compresser des groupes de données ensemble.\nCe mécanisme est aussi appelé Log Structure Merge Tree (LSM Tree) en référence à un papier décrivant le mécanisme.\nDe nombreux moteurs de BDD utilisent ce principe :\nLevelDB (peut être utilisé dans Riak) et RocksDB\nCassandra et HBase, inspirés du papier Bigtable et Google.\nLucene (moteur d'Elasticsearch et de Solr) utilise un mécanisme similaire pour indexer le texte.\nEn terme d'optimisations :\nLa recherche peut être lente : on cherche dans la structure en mémoire, puis dans le premier bloc en BDD et ainsi de suite tant qu'on ne trouve pas, jusqu'à avoir cherché dans le bloc déjà compacté. Pour remédier à ça on peut approximer la recherche avec des structures efficaces appelées Bloom Filters.\nIl y a 2 types de stratégies de compaction :\nsize-tiered : les nouveaux et petits blocs sont régulièrement fusionnés avec les anciens et plus gros blocs.\nHBase utilise cette technique, alors que Cassandra supporte les deux.\nleveled : les blocs sont plus petits et la compaction se fait de manière plus incrémentale, utilisant moins d'espace disque.\nLevelDB tient son nom du fait qu'il utilise cette technique. On a aussi RocksDB ici.\nLa structure de BDD la plus utilisée et depuis longtemps est le B-Tree. La plupart des BDD relationnelles l'utilisent, mais aussi une bonne partie des BDD NoSQL.\nIl s'agit d'avoir des pages de taille fixe (en général 4 ko), organisés en couches (rarement plus de 4 couches). Chaque page contient des clés et des références vers des zones physiques de disque pour aller chercher les clés entre deux clés indiquées (sorte de dichotomie donc). On descend de couche en couche jusqu'à arriver à une page qui contient des données et pas de références vers d'autres pages.\nComme avec les LSM-Tree, pour que les B-Tree survivent à un crash sans perte de données, on va écrire toutes les opérations dans un fichier de log avant de modifier la BDD elle-même. Ensuite on peut détruire ce fichier de log.\nIl peut y avoir des problèmes de concurrence avec les B-Tree, on va alors utiliser des locks locaux pour bloquer correctement une partie de la BDD pour le thread qui écrit dedans. Ce problème n'existe pas avec les LSM-Tree puisque les opérations de restructuration sont faites en arrière plan.\nComparaison B-Tree / LSM-Tree :\nChacun a des avantages et inconvénients, le mieux selon Kleppmann c'est de tester empiriquement dans notre cas particulier lequel a la meilleure performance.\nA priori, la plupart du temps l'écriture serait plus rapide sur les LSM-Tree (à priori parce qu'il y aurait souvent une write amplification moins importante), alors que la lecture serait plus rapide sur les B-Tree (parce que les LSM-Tree ont besoin de lire plusieurs groupes de données triées jusqu'à ce que la compaction soit faite en arrière-plan).\nLes LSM-Tree sont meilleurs en particulier sur les disques durs mécaniques étant donné qu'ils organisent leurs données séquentiellement et ne font pas d'accès random.\nLes LSM-Tree stockent leurs données sur moins d'espace grâce à la compression, mais en même temps au moment où les données arrivent, ils les stockent dans un autre fichier que la BDD principale. Jusqu'à ce que les opérations d'arrière-plan soient exécutées, il y a des copies plus ou moins récentes des données qui coexistent sur le disque.\nLes B-Tree offrent plus de prédictibilité. Même si une opération d'écriture peut prendre plus de temps, on reste constant et évite des pics dans les hauts percentiles, qui peuvent arriver avec les LSM-Tree dans le cas où le disque serait par exemple surchargé et que les opérations d'arrière plan prendraient du retard.\nEn plus des index primaires il est possible de faire des index secondaires, qui vont indexer en fonction d'une autre colonne dont on estime qu'elle sera utile pour la recherche de données. La différence avec l'index primaire c'est qu'on n'a pas besoin d'avoir une unicité sur les données de la colonne indexée.\nCet index peut soit contenir une référence vers l'endroit où est stockée la donnée (qu'on appelle heap file), soit une version dupliquée de la donnée elle-même (on parle de clustered index). Il y a des avantages et inconvénients évidents à le faire et ne pas le faire (rapidité de recherche vs temps d'écriture et consistance).\nOn peut aussi faire des index multi-colonnes. Ça permet de chercher par plusieurs champs en même temps.\nLe plus connu est l'index concaténé, qui consiste à accoler plusieurs champs ensemble dans l'index, par ex “NomPrénom”, qui permet de chercher par “Nom”, ou par “NomPrénom”, mais pas par “Prénom”.\nIl y a aussi les index multi-dimensionnels, qui permettent de pouvoir chercher avec plusieurs colonnes indépendamment, utile par exemple pour la recherche de coordonnées géospatiales longitude / latitude.\nC'est implémenté par ex par PostGIS dans PostgreSQL, qui utilise des R-trees en interne.\nLucene permet de faire des recherches de termes avec des distances (une distance de 1 signifie qu'avec une lettre différente dans le mot, il sera retenu) grâce à sa structure de clés en mémoire particulière.\nLa RAM étant de moins en moins chère, on peut imaginer des BDD entièrement en RAM.\nPour pallier le risque de perte de données, on peut écrire sur disque en parallèle, avoir de la RAM avec batterie, ou encore faire des réplications en mémoire.\nPlusieurs moteurs de BDD fonctionnent comme ça :\nVoltDB, MemSQL et Oracle TimesTen, ainsi que RAMCloud qui est open source.\nRedis et Couchbase offrent une durabilité faible en écrivant sur disque de manière asynchrone.\nContrairement à ce qu'on pourrait penser, le gain de performance d'utiliser des BDD in-memory ne vient pas forcément de l'écriture/lecture sur DD en elle-même, puisque l'OS met de toute façon les données récemment manipulées en cache dans la RAM. En fait, le gain vient surtout du temps de conversion des données dans un format qu'on peut écrire sur DD.\nCe qu'on appelle transaction n'a pas forcément besoin d'être ACID (atomic, consistant, isolé, durable). Il s'agit simplement d'un terme désignant des lectures/écritures avec faible latence, par opposition à batch, qui lui désigne les jobs faits périodiquement dans le temps.\nLa transaction classique est appelée OLTP (OnLine Transaction Processing). Il existe un autre type de transaction : OLAP (OnLine Analytic Processing) qui consiste à agir sur peu de colonnes mais un très grand nombre d'entrées, pour faire des analyses de données (par exemple des comptages, statistiques etc.).\nDepuis les années 80 les grandes entreprises stockent une copie de leur BDD dans un Data Warehouse : une base de données structurée de manière à optimiser les requêtes d'analyse, et ne risquant pas d'affecter la prod.\nSQL permet d'être performant sur l'OLTP comme sur l'OLAP, globalement c'est ça qu'on va utiliser sur les data warehouses. Par contre les BDD sont structurées bien différemment pour optimiser l'analyse.\nUn certain nombre d'acteurs proposent des data warehouses avec des licences commerciales onéreuses : Teradata, Vertica, SAP HANA, ParAccel (ainsi que Amazon RedShift qui est une version hostée de ParAccel)\nD'autres acteurs open source de type SQL-on-Hadoop concurrencent les premiers : Apache Hive, Spark SQL, Cloudera Impala, Facebook Presto, Apache Tajo, Apache Drill.\nDe nombreux data warehouses sont organisés selon un star schema. On a la fact table au centre avec en général des dizaines voir centaines de champs, et représentant les événements étudiés. Et autour on a les dimension tables, répondant aux questions who, what, where, when, how, why et liées à la fact table par des foreign keys, ils représentent en quelques sortes les metadata.\nUne variation du star model s'appelle snowflakes, il s'agit d'une version plus normalisée, où on va davantage découper les dimention tables en sous-tables.\nLa plupart du temps, les data warehouses utilisent un column-oriented storage plutôt qu'un row-oriented. Il s'agit de stocker les données des colonnes physiquement côte à côte, parce que les requêtes vont avoir besoin de lire en général quelques colonnes entières.\nLa plupart du temps les bases en colonne sont relationnelles, mais il y a par ex Parquet, basé sur Google Dremel, qui est orienté colonne mais non-relationnel.\nCassandra et HBase ont un concept de column families, mais ça consiste seulement à stocker toutes les colonnes d'une entrée ensemble, elles sont en réalité essentiellement row oriented.\nDans le cas où les valeurs dans les colonnes se répètent (en particulier s'il y a beaucoup plus de valeurs que de valeurs possibles), on peut faire une compression sur les colonnes. Par exemple une compression de type bitmap encoding\nUn des avantages des column oriented storages c'est que ça se prête bien à un traitement optimal entre la RAM et le cache du CPU, avec de petits cycles de traitement de données compressées provenant de la même colonne.\nOn peut profiter du mécanisme des LSM-Trees avec les données en mémoire et le reste de la BDD sur disque, pour trier les entrées d'une façon particulière. Par exemple, on peut choisir la colonne qui est souvent la plus recherchée, et trier les entrées de manière à avoir toutes les entrées avec la même valeur dans cette colonne côte à côte. Et ainsi de suite pour les colonnes secondaires. Ça permet une meilleure recherche mais aussi une meilleure compression pour ces colonnes-là.\nOn peut également choisir de trier différemment chaque copie de la BDD qu'on possède, pour choisir celle qui nous arrange le plus au moment de faire une requête. C-Store et Vertica font ça.\nPour optimiser les requêtes dans les column oriented databases, à la place d'un inde on peut mettre en place une materialized view, qui consiste à ajouter une valeur ou une colonne de valeurs contenant des calculs (MIN, MAX, SUM etc.).\nUn cas particulier s'appelle le data cube, il s'agit de prendre deux colonnes comme composant les deux dimensions d'une valeur qu'on cherche à analyser, et d'ajouter une colonne représentant un agrégat (par ex la somme des valeurs sur une des dimensions).\nCette pratique permet d'accélérer les requêtes parce que certaines choses sont pré-calculées, mais ça offre aussi moins de flexibilité. Donc en général on s'en sert comme boost de performance tout en laissant aux data analyst la possibilité de faire les requêtes qu'ils veulent.","4---encoding-and-evolution#4 - Encoding and evolution":"Quand on change les fonctionnalités, y compris la BDD, il est utile de pouvoir faire une rolling upgrade (ou staged rollout). Il s'agit de déployer le code sur certains nœuds, s'assurer que tout va bien, puis déployer progressivement sur les autres.\nCela implique que le code et la BDD doivent être backward-compatibles (supporter les fonctionnalités du code précédent) mais aussi forward-compatibles (que le code précédent ignore les nouvelles fonctionnalités).\nLes données ont besoin d'au moins 2 représentations : une en mémoire avec des pointeurs vers des zones mémoire, et une quand on veut transmettre la donnée sur disque ou à travers le réseau. Il faut alors que tout soit contenu dans le bloc de données. La conversion de la mémoire vers la version transportable s'appelle encoding (ou serialization ou marshalling).\nIl existe des formats liés à des langages, comme pickle pour python, mais ces formats ne sont ni performants, ni ne gèrent bien la rétro (et forward) compatibility. Et le format de données est trop centré sur un langage particulier.\nOn a les formats plus standards comme XML, JSON et CSV.\nXML et CSV ne distinguent pas les nombres des strings, alors qu'en JSON on distingue les nombres mais pas les flottants des entiers par exemple.\nXML et JSON ont la possibilité d'avoir des schémas associés mais ceux-ci ne font pas consensus.\nGlobalement ces formats sont suffisants pour une communication entre organisations, tant que celles-ci s'accordent sur des conventions. En réalité, la plus grande difficulté est que des organisations différentes s'accordent sur quoique ce soit.\nOn a enfin les formats binaires non spécifiques à un langage et conçus pour la performance.\nIl existe des versions binaires (ne faisant pas consensus) pour JSON et XML. Par exemple pour JSON il y a MessagePack, BSON, BJSON, UBJSON, BISON et Smile. Le souci de ces formats c'est qu'ils sont assez peu compacts parce qu'ils embarquent le nom des champs répété à chaque entrée de donnée.\nApache Thrift, développé par Facebook, et Protocol Buffers (ou protobuf), développé par Google sont deux formats binaires apparus en open source en 2007/2008. Leur particularité est qu'ils ont besoin d'un schéma, et qu'ils ne répètent pas le nom des champs pour gagner de la place. Ils ont tous les deux des adaptations dans la plupart des langages pour sérialiser / désérialiser des données dans ce format à partir des structures du langage.\nA propos des formats :\nConcernant Thrift : Il a deux formats différents :\nBinaryProtocol qui remplace le nom des champs par des chiffres faisant référence aux champs du schéma.\nCompactProtocol qui possède des optimisations supplémentaires pour gagner de la place en encodant le numéro du champ et le type de champ sur un seul byte, et en utilisant par ex des entiers de longueur variable.\nConcernant Protobuf : il est globalement assez similaire au CompactProtocol de Thrift, avec des petites différences dans la manière d'encoder les bytes.\nA propos de l'évolution des schémas (Protobuf et Thrift) :\nAjout de champ : comme les champs sont représentés par un numéro reporté dans le schéma, on peut facilement ajouter un champ. Ce sera backward-compatible puisque le nouveau code pourra toujours lire les données qui n'ont pas les nouveaux champs, et ce sera forward-compatible parce que l'ancien code pourra juste ignorer les champs ayant un numéro qu'il ne connaît pas.\nOn ne peut juste pas ajouter une donnée obligatoire après une donnée optionnelle, parce que le nouveau code ne pourrait plus lire les données anciennes qui n'auraient pas ce champ obligatoire.\nSuppression de champ : c'est possible à condition qu'ils soient optionnels (les obligatoires ne pourront jamais être enlevés).\nModification de type de données : c'est parfois possible, mais il y a parfois le désavantage que notre donnée peut être tronquée par le code ancien qui ne lit pas toute la longueur prise par la donnée.\nProtobuf ne possède pas de tableaux mais demande à ajouter un même champ plusieurs fois si on le veut dans un tableau. Cela permet de pouvoir transformer un champ unique en tableau du même type et inversement.\nThrift ne fournit pas cette flexibilité de transformation puisqu'il a un type pour le tableau, mais il supporte les tableaux imbriqués.\nApache Avro a été développé en 2009 pour Hadoop.\nIl est similaire à Thrift et Protobuf, et a deux schémas : un (Avro VDL) lisible par les humains, et un autre plus pratique pour les machines.\nPour aller chercher un format encore plus compact, Avro ne mentionne pas de numéros pour les champs, il les met simplement les uns à la suite des autres dans le bon ordre, avec juste leur type et le contenu.\nLe support de l'évolution du schéma dans Avro se fait en considérant que la machine qui a écrit la donnée a son schéma, et la machine qui lit a le sien. A partir de ces 2 schémas, et à condition qu'ils soient compatibles, Avro calcule exactement la conversion nécessaire pour que les données lues soient correctement interprétées.\nTous les champs avec une valeur par défaut dans le schéma peuvent être ajoutés, supprimés ou changés d'ordre d'apparition.\nOn peut modifier les types de champs, avec les mêmes problématiques que pour Protobuf et Thrift.\nL'information du schéma de celui qui a écrit la donnée étant centrale dans l'encodage / décodage, elle doit être fournie avec la donnée.\nPour un grand fichier contenant plein de données, on met le schéma au début du fichier et il concerne toutes les données.\nPour une base de données qui a potentiellement des données avec des schémas différents, on peut ajouter un nombre faisant référence au schéma à chaque entrée de donnée, et avoir une table avec tous les schémas. C'est ce que fait Espresso (la base de données de document de Linkedin, qui utilise Avro)\nPour une connexion par réseau, les deux entités connectées peuvent se communiquer le schéma au début de la connexion et le garder tout au long de celle-ci. C'est ce qui se fait pour le Avro RPC protocol.\nUn de principaux avantages d'Avro sur Thrift et Protobuf est que comme le numéro des champs n'est pas dans les données, on peut facilement générer des données organisées dans n'importe quel ordre, ou avec des champs en plus etc. les mettre au format Avro avec un schéma associé, et ils pourront être lus sans problèmes. Avro a été conçu pour gérer des données générées dynamiquement, ce qui n'est pas le cas de Thrift et Protobuf.\nIl est intéressant de noter que les données associées à des schémas sont pratiques à bien des égards, que ce soit pour la flexibilité, le faible espace occupé, la documentation vivante que ça fournit. Et ces données se couplent bien avec les bases de données “schemaless” (les non relationnelles principalement donc) qui permettent de gérer les schémas au niveau de l'application.\nQuand on passe les données d'un processus à un autre il faut s'assurer que la donnée est bien comprise malgré les versions des programmes tournant sur ces processus. Il y a 3 manières de passer les données encodées d'un processus à un autre :\n1- Dataflow through databases\nDans le cas des bases de données, le processus qui écrit encode la donnée, et le processus qui lit la décode. Ces processus peuvent embarquer des versions différentes du code, et donc il faut une backward compatibility pour pouvoir lire les données avec l'ancien schéma, et éventuellement une forward compatibility dans le cas où un noeud avec le nouveau code aurait écrit des données, et que ces données doivent être lues avec un noeud dont le code est ancien.\nIl y a aussi une autre chose à laquelle il faut penser dans le code applicatif : pour le cas de la forward compatibility, si un vieux code traite des données possédant de nouveaux champs, il pourra ignorer ceux-ci, mais il faut absolument qu'il pense à les garder s'il veut mettre à jour ces données, sinon elles pourraient être supprimées sans le vouloir.\nContrairement au code qui finit par être chargé à la version la plus récente sur tous les nœuds, la base de données a en général diverses versions des données, certaines de plusieurs années. Dans la mesure du possible on ne remplit pas le contenu des nouveaux champs dans les anciennes entrées, mais on met juste null dedans. Le format Avro fournit une bonne manière de travailler avec des données nouvelles et anciennes de manière transparente.\n2- Dataflow through services\nLa communication par réseau se fait souvent avec une architecture client / serveur. Par exemple, le navigateur est client et le serveur fournit une API sur laquelle le navigateur va faire des demandes.\nOn a le même principe côté serveur avec l'architecture orientée services (SOA) (contrairement au nom SOAP n'est pas spécifiquement lié à SOA) ou plus récemment avec quelques changements ce qu'on appelle l'architecture microservices. Il s'agit d'avoir des entités indépendantes qui communiquent entre-elles via messages, et qui peuvent être mises à jour de manière indépendante, tout en communiquant avec le même format de données qui assure leur compatibilité.\nQuand des services utilisent le protocole HTTP, on appelle ça des web services. On peut les trouver entre les utilisateurs et les organisations, entre deux services d'une même organisation, ou entre deux organisations avec par exemple les systèmes de carte de crédit ou le protocole OAuth pour l'authentification.\nIl y a 2 approches populaires pour les web services : REST et SOAP (et GraphQL qui a été open sourcé en 2015 et ne figure donc pas dans le livre ?).\nREST : principes de design généraux utilisant à fond les fonctionnalités du protocole HTTP et collant à son fonctionnement (par exemple pour le contrôle du cache, pour le fait d'identifier les ressources avec des URLs).\nOpenAPI (Swagger) permet de documenter convenablement les API REST.\nSOAP : protocole basé sur XML appelé Web Service Description Language (WSDL). SOAP se base beaucoup sur la génération de code et les outils. Les messages sont en eux-mêmes difficiles à lire par un être humain. Malgré les efforts ostentatoires, l'interopérabilité n'est pas très bonne entre les diverses implémentations de SOAP. SOAP est surtout utilisé dans les grandes entreprises parce qu'il est plus ancien.\nEn plus des web services, il y a un autre groupe de protocoles de communication à travers le réseau appelé Remote Procedure Call (RPC). Il s'agit en RPC d'appeler des méthodes sur des objets, et d'attendre une réponse de ces appels.\nIl y a d'anciens protocoles spécifiques à un langage ou super complexes comme EJB (Java), DCOM (Microsoft), COBRA (trop complexe et non backward compatible). Mais il y a aussi de nouveau protocoles comme gRPC utilisant Protobuf, Finagle qui utilise Thrift, Rest.li qui utilise JSON sur HTTP, et Avro et Thrift qui ont leur propres implémentations de RPC.\nLes protocoles RPC essayent de faire passer les appels réseau pour des appels à des méthodes, mais ces choses sont de nature complètement différente : un appel réseau est imprédictible, il peut prendre un temps variable, il peut finir en timeout, il peut réussir tout en n'envoyant pas de réponse, il ne peut pas stocker de valeurs en mémoire liés par des pointeurs etc. REST quant à lui assume que les appels réseau sont de nature bien différente en les présentant comme tels.\nCeci est à tempérer un peu avec les implémentations récentes de RPC qui sont plus explicites sur la nature différente en fournissant par exemple des promesses pour encapsuler les appels asynchrones.\nBien que les protocoles RPC avec un encodage binaire permettent une plus grande performance que du JSON par dessus REST, REST bénéficie d'un débuggage plus facile avec la possibilité de tester à travers les navigateurs, il est supporté partout, et il est compatible avec un large panel d'outils construits autour (serveurs, caches, proxies etc.). Pour toutes ces raisons, le RPC est utilisé seulement au sein d'une même organisation, typiquement dans un même datacenter.\n3- Message-passing dataflow\nIl existe une manière de transmettre des données entre 2 processus qui se situe entre les appels RPC et les messages passés par une base de données : il s'agit de la transmission de messages asynchrone. On ne passe pas par le réseau mais par un message broker (ou message queue).\nLes avantages de cette approche comparé à RPC sont :\nLe broker peut faire buffer le temps que le(s) consommateur soit disponible.\nLe message peut être redélivré en cas d'échec ou de crash.\nCelui qui envoie et qui reçoit ne se connaissent pas, il y a un découplage à ce niveau.\nIl peut y avoir plusieurs consommateurs d'une même queue.\nUn des inconvénients potentiels c'est que le receveur n'est pas censé répondre. L'envoyeur envoie puis oublie.\nLes message brokers sont historiquement des logiciels propriétaires comme TIBCO, IBM WebSphere, et webMethods. Plus récemment on a des brokers open source comme RabbitMQ, ActiveMQ, HornetQ, NATS et Apache Kafka.\nLes message brokers n'imposent en général pas de format de données, donc on peut très bien utiliser les formats Avro / Thrive / Protobuf, et profiter de leur flexibilité pour pouvoir déployer indépendamment les producteurs et consommateurs.\nL'actor model consiste à se débarrasser de la problématique de concurrence avec la gestion de threads et de ressources partagées en créant des actors indépendants, ayant chacun leurs états encapsulés, et communiquant avec les autres actors via messages asynchrones.\nDans la version distribuée, ces interlocuteurs peuvent alors être sur le même nœud ou sur un nœud différent, auquel cas le message sera sérialisé pour être transmis via le réseau de manière transparente.\nIl y a une plus grande transparence vis-à-vis du fait que les messages peuvent être perdus qu'avec RPC.\nUn framework actor distribué inclut un broker pour transmettre les messages. Il y en a 3 qui sont populaires :\nAkka qui utilise la sérialisation de Java, mais peut être utilisé avec par exemple Protobuf pour permettre une meilleure backward/forward compatibilité.\nOrleans supporte les rolling upgrades avec son propre système de versionning.\nErlang OTP supporte les rolling upgrades mais il faut les planifier avec attention.","5---replication#5 - Replication":"Il y a 3 raisons pour vouloir faire de la réplication :\nGarder une copie des données proche des utilisateurs et donc avoir une faible latence.\nPermettre au système de fonctionner même si certains composants sont foutus.\nPour scaler le nombre de machines et donc le nombre de requêtes qu'on peut traiter.\nTout l'enjeu de la réplication réside dans le fait de propager les changements dans tous les réplicats. Il y a 3 algorithmes pour ce faire : single-leader, multi-leader, et leaderless.\nLa réplication des BDD distribuées a été étudiée depuis les années 70 et n'a pas changé depuis parce que la nature des réseaux n'a pas changé. En revanche, l'utilisation industrielle de ces techniques est quant à elle récente.\nLa réplication la plus évidente est la leader-based replication. Pour écrire une donnée il faut le faire auprès du nœud leader, qui va mettre à jour sa copie de la BDD et envoyer un log (ou stream) de mise à jour à tous les nœuds suiveurs qui vont l'appliquer.\nCe type de réplication est intégré au sein :\ndes BDD relationnelles suivantes : PostgreSQL, MySQL, Oracle Data Guard, SQL Server's AlwaysOn Availability Groups.\ndes BDD non relationnelles suivantes : MongoDB, RethinkDB, Espresso.\nEt même au sein de message brokers distribués comme : Kafka et RabbitMQ.\nLa réplication peut être synchrone ou asynchrone. Si elle est synchrone, alors le nœud leader doit attendre que tous les suiveurs aient répondu “ok” de leur côté pour répondre à son tour que la transaction s'est bien passée. Si elle est asynchrone, il répond tout de suite même s' il y a eu un problème du côté des followers.\nDans la pratique on choisit rarement le mode synchrone parce que n'importe lequel des nœuds pourrait mettre plusieurs minutes à répondre à cause de problèmes réseau.\nOn choisit parfois une réplication semi-synchrone, qui consiste à avoir un seul nœud suiveur synchrone, et le reste asynchrones. De cette manière on est assurés d'avoir les données à jour sur au moins 2 nœuds. Et si le nœud suiveur synchrone ne répond plus, on promeut un autre nœud suiveur comme synchrone pour le remplacer.\nLa réplication asynchrone est également souvent choisie, surtout si les suiveurs sont nombreux ou distribués géographiquement.\nL'ajout d'un noeud suiveur supplémentaire se fait en faisant un snapshot de la base de données du noeud leader, en copiant ça sur le nouveau noeud, puis en demandant au leader tous les logs de mise à jour (toutes les transactions) qui ont eu lieu depuis le snapshot. Le nouveau nœud peut alors rattraper son retard et devenir un nœud suiveur normal.\nEn cas d'échec :\nd'un nœud suiveur : le nœud sait à quel log il s'est arrêté, donc dès qu'il va mieux, il peut demander au nœud leader l'ensemble des transactions qu'il a ratées, et se remettre à jour. Ça s'appelle le catch-up recovery.\nd'un nœud leader : c'est plus compliqué à gérer. Il faut un timeout pour déterminer qu'un nœud leader est en échec, et passé ce timeout on entame un processus de failover c'est-à-dire de remplacement du leader.\nLa procédure peut être automatique ou manuelle. Un timeout trop long peut mener à une interruption du service trop longue, et un timeout trop court dans un contexte de surcharge peut mener à gérer encore moins bien la charge. Pour cette raison, certaines organisations préfèrent la méthode manuelle.\nLe choix du nouveau leader est un problème de consensus, discuté plus tard dans le livre. A priori le nœud le plus à jour serait le meilleur choix.\nIl est possible que l'ancien leader revienne et pense qu'il est toujours le leader en acceptant les opérations en écriture. C'est une situation dangereuse qu'il faut prévoir correctement.\nDans le cas de réplication asynchrone, certaines transactions peuvent ne pas avoir été passées aux suiveurs. Si l'ancien leader revient en tant que suiveur ensuite, que faire de ces transactions ? En général on les supprime, mais c'est pas trop trop.\nEt ça peut être même problématique si les transactions sont en lien avec d'autres outils. Par exemple chez Github, un suiveur asynchrone MySQL était devenu leader avec des transactions manquantes. Il se trouve que la clé primaire était aussi utilisée dans un cache Redis qui lui avait les nouvelles transactions. Comme les nouvelles entrées ont été assignées à des valeurs de la clé primaire qui avaient existé auparavant, des utilisateurs ont pu avoir accès à des clés privées d'autres utilisateurs, issus du cache.\nFonctionnement de la réplication au niveau des messages (logs) :\nStatement-based replication : il s'agit de faire suivre toutes les instructions de base de données aux suiveurs. Par exemple un INSERT, un UPDATE, un DELETE etc.\nDans le cas d'instructions non déterministes comme RAND(), on va se retrouver avec des valeurs différentes dans les suiveurs.\nPour les champs auto-incrémentés, il faut absolument que l'ordre des requêtes soit exactement le même, ce qui limite les transactions concurrentes.\nIl est possible de travailler à rendre déterministe toutes les instructions qui pourraient poser problème, en envoyant une valeur plutôt qu'une instruction dans ces cas-là, mais il y a plein d'edge cases à traiter.\nEn général cette approche n'est pas très utilisée pour cette raison-là. MySQL l'utilisait jusqu'à une certaine version, mais utilise l'approche row-based replication depuis. VoltDB utilise en revanche cette approche.\nWrite-ahead log (WAL) shipping : il s'agit d'envoyer aux suiveurs le log des messages bas niveau (tel qu'il est utilisé par les BDD LSM-Tree, ou tel qu'il est utilisé par les B-Tree le temps que l'opération se fasse, et pour pouvoir la refaire à partir de ce log en cas d'échec).\nL'avantage c'est que c'est déterministe, mais l'inconvénient c'est que les messages sont couplés à une implémentation bas niveau de la BDD. Ce qui veut dire qu'on ne peut pas faire tourner une version différente entre le leader et les followers. Et donc exit les zero-downtime rolling updates : il faut une période de downtime.\nCe mécanisme est utilisé par PostgreSQL et Oracle.\nLogical (row-based) log replication : il s'agit de faire un peu la même chose qu'avec le WAL, mais on utilise un format de log indépendant de la BDD, avec les fonctionnalités minimales pour pouvoir mettre à jour correctement la BDD.\nOn est donc découplé du format bas niveau utilisé par la BDD, et on peut faire du zero-downtime.\nÇa permet aussi d'envoyer les logs à une autre base de données de type data warehouse en temps réel.\nMySQL binlog peut être configuré pour utiliser ce mécanisme.\nTrigger-based replication : dans le cas où on recherche plus de flexibilité, plutôt que d'utiliser les mécanismes built-in des BDD, on peut bouger la logique de réplication au niveau applicatif.\nOracle GoldenGate permet de mettre à disposition les logs de la BDD pour le code applicatif et implémenter ce mécanisme.\nUn autre moyen de l'implémenter est d'utiliser les triggers et stored procedures qui existent dans la plupart des BDD relationnelles. On peut grâce à ça exécuter du code applicatif à chaque transaction. Le résultat est placé dans une table à part et lu par un processus à part.\nDatabus for Oracle et Bucardo for Postgres font ça par exemple.\nCe mécanisme arrive avec son lot de bugs, et est moins performant. Mais il offre de la flexibilité.\nDans le cas où on choisit la leader-based replication avec des followers asynchrones parce qu'on cherche à scaler en lecture en ayant plein de followers, les followers vont se retrouver régulièrement en retard. En général c'est une fraction de seconde, mais dans la montée en charge ou avec des problèmes de réseau ça peut devenir des minutes. Ce retard s'appelle le replication lag. Et on parle d'eventual consistency pour désigner ce problème de consistance momentané des données.\nParmi les problèmes survenant il y a :\nread-after-write consistency : le fait, pour un utilisateur, de pouvoir lire ses propres writes juste après : s'il écrit un message et recharge la page, et qu'il ne voit pas son message, il pourrait se mettre à paniquer.\nOn peut lire toute donnée qui a potentiellement été modifiée par l'utilisateur depuis le leader, et les autres depuis les suiveurs. Par exemple, le profil d'un utilisateur ne peut être modifié que par lui, donc on le lit depuis le leader.\nDans le cas où la plupart des données sont potentiellement modifiables par l'utilisateur, on perdrait l'intérêt du scaling à tout lire depuis le leader. On peut alors par exemple ne lire depuis le leader que les données qui ont été modifiées dans les dernières minutes, ou encore monitorer le replication lag pour ne pas lire depuis les suiveurs qui sont trop en retard.\nLe client peut retenir le timestamp (temps ou donnée d'ordre logique) de la dernière écriture, et l'envoyer avec la requête. Le serveur peut alors n'utiliser que les suiveurs qui sont à jour jusqu'à ce timestamp, ou attendre qu'ils le soient avant de répondre.\nDifficulté supplémentaire dans le cas de dispersion géographique : toute requête envoyée au leader ne sera pas forcément proche de l'utilisateur.\nAutre problématique : si on veut que l'utilisateur puisse voir ses écritures depuis tous ses outils (navigateur, mobile etc.).\nÇa rend inopérant la technique de se souvenir de la dernière modification côté client puisqu'il y a alors plusieurs clients.\nOn a aussi des problèmes supplémentaires dans le cas où il y a plusieurs datacenters. Les deux appareils pourraient être dirigés vers des datacenters différents.\nmonotonic reads : un utilisateur pourrait obtenir des données récentes depuis un replica à jour, puis recharger la page et obtenir des données anciennes depuis un replica moins à jour. Ça donne l'impression d'aller dans le passé.\nPour éviter ce phénomène on peut servir un même utilisateur toujours avec le même nœud suiveur tant que celui-ci est vivant.\nConsistent prefix reads : il s'agit ici de respecter l'ordre causal des choses. Il faut que les données écrites en BDD le soient toujours dans le bon ordre. C'est un problème qui survient quand on partitionne la BDD (on en parlera au chapitre suivant).\nOn peut alors essayer de mettre les informations liées entre-elles dans la même partition.\nOn peut aussi utiliser des algorithmes qui empêchent les données d'être dans un ordre non causal.\nConcernant le replication lag et l'eventual consistency qui en résulte, une des solutions pour y répondre s'appelle les transactions. Leur but est d'abstraire tout l'aspect distribué du code applicatif, et de s'occuper de répondre aux problèmes décrits ici (read-after etc.). Certaines personnes disent d'abandonner les transactions qui seraient trop coûteuses, mais on nuancera ça par la suite.\nOn a ensuite la multi-leader replication. On a plusieurs leaders qui mettent à jour des suiveurs, et qui se mettent aussi à jour entre eux.\nEn général la complexité supplémentaire induite par le fait d'avoir plusieurs leaders n'en vaut pas la peine si on n'a qu'un seul datacenter.\nLes avantages du multi-leader dans un environnement multi datacenter :\nOn peut par exemple avoir un leader par datacenter, ce qui permet d'éviter de traverser la terre pour faire des requêtes d'écriture, donnant une perception de performance aux utilisateurs.\nUn datacenter entier et son leader peut avoir un problème, puis rattraper son retard sur les autres datacenters dès que c'est bon.\nLes erreurs réseau hors du datacenter impactent moins ce qui se passe dans le datacenter, étant donné qu'on n'est pas obligé d'aller chercher un nœud leader d'un autre datacenter pour écrire.\nCertaines BDD supportent le multi-leader nativement, mais en général il faut un outil externe. C'est le cas pour Tungsten Replicator pour MySQL, BDR pour PostgreSQL et GoldenGate pour Oracle.\nIl y a également CouchDB qui a été conçu pour permettre de résoudre facilement les situations de multi-leader.\nCes deux exemples illustrent le même principe que la réplication multi-leader :\nUne application de calendrier pour mobile, desktop etc. pourrait fonctionner en maintenant une copie de la BDD dans chaque client, les laissant ajouter des événements même en étant hors ligne, et synchroniser les BDD quand les clients sont à nouveau en ligne. On a bien plusieurs leaders qui peuvent écrire, une possibilité d'eventual consistency le temps que le réseau revienne, et un travail de résolution des conflits à faire.\nLes applications d'édition collaborative de texte comme Etherpad ou Google Docs fonctionnent comme si plusieurs leaders pouvaient faire des changements sur leur propre version locale, et propager ces changements de manière asynchrone. Ca aurait pu être du single-leader si chaque personne prenait un lock avant de faire un changement (un peu comme dans dropbox), mais là chaque changement est ajouté à un niveau vraiment atomique au document.\nLe problème principal de la multi-leader replication c'est les conflits entre leaders ayant eu chacun une transaction en écriture sur une même donnée.\nOn pourrait demander aux leaders d'attendre que l'autre leader ait fini sa transaction avant d'en accepter une, mais alors on reviendrait à la position de single-leader, on perdrait l'avantage d'avoir plusieurs leaders acceptant des connexions en même temps.\nDans la mesure du possible, vu que la résolution de conflit est complexe et en général mal gérée, il vaut mieux éviter les conflits. On peut par exemple rediriger les requêtes d'un même utilisateur toujours vers le même datacenter.\nParfois un datacenter est hors d'usage, ou un utilisateur peut se déplacer et se rapprocher d'un autre datacenter, et on va vouloir le rediriger vers un autre leader. Il faut alors faire converger le conflit vers un état consistent :\nOn peut donner un identifiant à chaque transaction, basé sur un timestamp, un nombre aléatoire etc. et se dire que le plus grand nombre gagnera lors du conflit pour faire valider sa transaction, annulant l'autre. Dans le cas du timestamp c'est du last write wins (LWW). C'est très populaire mais on perd des transactions.\nOn peut donner un identifiant à chaque leader, et se dire que celui qui a le plus grand gagne toujours la résolution de conflit. Mais c'est pareil qu'avec l'identifiant de transaction : on va perdre des données.\nOn peut choisir une stratégie de fusion des données des 2 requêtes, par exemple ordonner le texte alphabétiquement et le concaténer.\nBucardo par exemple permet d'écrire un bout de code en perl pour choisir quoi faire des requêtes en conflit dès que le conflit apparaît au moment de l'écriture.\nEnregistrer le conflit avec les 2 données quelque part, et laisser le code applicatif gérer ça par exemple en demandant à l'utilisateur quoi faire pour ce conflit.\nCouchDB fonctionne de cette manière-là : il stocke les 2 données, puis à la lecture les envoie toutes les deux à l'application..\nUn conflit peut être de manière évidente la modification d'un même champ, mais ça peut aussi être plus subtile et difficile à détecter. Par exemple une vérification au niveau applicatif qu'une chambre d'hôtel ne peut être réservée et donc mentionnée que par une seule réservation. Si le code applicatif a validé la requête, mais qu'on en l'a faite une dans chaque leader, on va réserver deux fois.\nQuand on a 2 leaders, ils vont forcément s'envoyer chacun des updates. Mais si on en a plus, alors on peut avoir diverses topologies de propagation des mises à jour entre leaders :\nLa star topology consiste à avoir un leader au centre qui va mettre à jour tous les autres, et prendre des mises à jours d'eux.\nLa circular topology consiste à ce que chaque leader mette à jour son voisin, et finissant en boucle. Une information au niveau de la requête permet alors de savoir si elle a déjà été traitée par le nœud courant pour arrêter la boucle.\nLa plus générale est le all-to-all topology, où tous les leaders mettent à jour tous les autres.\nUn des avantages du all-to-all est que si un nœud ne fonctionne plus, c'est transparent. Pour le circular et star il pourrait bloquer l'information et il faut alors reconfigurer la topologie.\nL'inconvénient des all-to-all est que certaines connexions peuvent être plus rapides que d'autres, et alors si on se basait sur des timestamp pour l'ordre des transactions par exemple, cet ordre pourrait ne pas être bon.\nPour pouvoir faire quand même respecter la causalité dans ce cas, on peut utiliser la technique des version vectors.\nGlobalement la résolution de conflits est plutôt mal gérée dans les solutions existantes : par exemple PostgreSQL BDR ne fournit pas de garantie causale des écritures, et Tungsten Replicator pour MySQL ne détecte même pas les conflits.\nOn a enfin la leaderless replication, qui consiste à ce que tous les nœuds puissent accepter les requêtes en lecture et écriture.\nCette idée était tombée dans l'oubli depuis longtemps et a été remise au goût du jour quand Amazon l'a implémentée dans sa base de données Dynamo system. Elle est depuis utilisée dans les BDD open source Riak, Cassandra et Voldemort. Elles sont connues pour être les “BDD Dynamo-style”.\nEn cas de problème dans un des nœuds, celui-ci va rater des requêtes. Et quand il reviendra, ses données seront anciennes et le client risque d'obtenir des données pas à jour en lisant depuis ce nœud.\nPour résoudre le problème chez le client, le client peut envoyer la requête à tous les nœuds, et à la réception utiliser la version la plus à jour parmi ceux reçus, grâce à des numéros de version dans ces messages.\nPour s'assurer que le noeud se remet à jour il y a 2 moyens implémentés dans les systèmes Dynamo-style :\nRead repair : Quand le client lit une valeur en parallèle depuis tous les réplicas, s'il constate une différence chez l'un d'entre eux qui aurait une version de transaction plus ancienne, il le met à jour avec une requête d'écriture. Ceci permet de mettre à jour les valeurs souvent lues.\nAnti-entropy process : Pour les valeurs peu lues, on peut avoir des tâches de fond qui tournent, et dont le but est de repérer les différences entre nœuds, et mettre à jour ceux qui sont en retard.\nPour savoir si une requête a réussi, on peut utiliser le quorum consistency.\nSoit :\nn le nombre de nœuds à qui on envoie les reads et writes.\nw le nombre de nœuds qui doivent confirmer un write pour le considérer comme réussi.\nr le nombre de nœuds qui doivent confirmer un read pour qu'il soit considéré comme réussi.\nAlors pour respecter le principe du quorum il faut que w + r > n.\nTypiquement on choisit n impair, et w = r = (n + 1) / 2 (arrondi au supérieur).\nSi on a beaucoup de lectures et peu d'écritures, on pourra mettre r = 1, comme ça dès qu'un seul nœud valide la lecture alors la transaction est validée. Les lectures sont alors plus rapides mais un seul nœud qui est down empêche alors l'écriture en BDD pour respecter la formule w + r > n).\nAvantages et inconvénients du quorum :\nL'intérêt de ce quorum c'est que les reads et writes se chevauchent, et donc qu'il y ait forcément au moins un nœud qui soit complètement à jour, pour être sûr que la donnée lue qui sera gardée sera complètement à jour.\nOn peut très bien choisir de ne pas respecter la formule du quorum et avoir moins de reads et writes nécessaires pour la validation. On aura alors une plus faible latence, une plus grande availability, mais une moins bonne consistance (on aura régulièrement des lectures renvoyant des données pas tout à fait à jour).\nMême avec le quorum, on peut se retrouver avec des données pas à jour dans certains cas :\nSi on utilise le sloppy quorum, on peut se retrouver avec les writes sur d'autres nœuds que les reads, et donc le chevauchement n'est plus garanti.\nIl s'agit d'une option activable sur les BDD qui permet, dans le cas où une large partie de noeuds est momentanément non disponible, de choisir de prendre quand même les writes sur d'autres noeuds partitionnés qui ne font pas habituellement partie de n pour ces valeurs-là. Et quand les nœuds sont de retour, on leur donne ces valeurs (hinted handoff). Le problème c'est que pendant le temps où ils n'étaient pas là, ils avaient peut être certaines valeurs plus à jours qu'eux seuls avaient, et les reads ont pu être servis avec des valeurs pas à jour.\nDans le cas de writes concurrents, on est en présence d'un conflit qu'il faut résoudre comme discuté précédemment. Si on choisit de résoudre en annulant une des requêtes, alors on perd des données.\nSi un read se fait en concurrence avec un read, le write pourrait être effectif chez certains replicas, et on ne sait pas ce que retournera alors le read.\nSi un write a réussi sur certains réplicas mais pas tous, et que la transaction est en voie d'annulation, les réplicas où ça a réussi peuvent renvoyer cette valeur qui sera fausse.\nSi le nœud à jour échoue, le nombre de nœuds en écriture tombe en dessous de w, et on peut n'avoir aucun nœud qui a la version à jour par rapport aux écritures déjà validées au moment de répondre.\nDes problèmes de timing dont on parlera plus tard peuvent aussi survenir.\nOn voit bien que les Dynamo-style databases ne garantissent qu'une eventual consistency, même en respectant le quorum. Pour avoir des garanties plus fortes comme le “read your writes”, “monotonic reads” etc. il faudra faire appel aux transactions et au consensus.\nMalgré l'eventual consistency de la leaderless replication, il peut être important de quantifier à quel point les données sont peu à jour dans les divers nœuds. Il faudrait mettre en place du monitoring mais c'est beaucoup moins simple que pour le leader-based où on peut facilement observer le replication lag du leader vers les followers. Là on peut avoir des valeurs peu lues très anciennes.\nLa leaderless replication est tout à fait aussi adaptée au multi-datacenter :\nCassandra et Voldemort traitent les nœuds dans les divers datacenters comme des nœuds normaux, avec le n global et un n configurable pour chaque datacenter. En général les clients n'attendent que le quorum du datacenter le plus proche pour maximiser le temps de réponse.\nRiak ne fait du leaderless classique qu'au sein des datacenters, la synchronisation cross-datacenter se fait de manière asynchrone, un peu à la manière du multi-leader replication.\nA propos de la gestion des écritures concurrentes :\nIl faut noter que malheureusement ça ne se fera pas automatiquement par les implémentations des BDD qui sont relativement mauvaises. En tant que développeur, il faut connaître ces problèmes et implémenter des solutions nous-mêmes.\nVoici quelques éléments de réflexion :\nLast write wins (LWW) : on en avait parlé, il s'agit d'éliminer une des deux transactions concurrentes en déterminant par une méthode arbitraire laquelle est la dernière dans le cas où il n'y a pas de relation de causalité. C'est arbitraire parce que la causalité entre des événements qui ne se connaissent pas n'a pas de sens. C'est ça qu'on appelle des transactions concurrentes.\nC'est problématique parce que même après avoir dit au client que la transaction s'est bien passée, elle peut être annulée en arrière-plan de manière silencieuse.\nLWW est la seule méthode de résolution de conflit supportée par Cassandra, et une feature optionnelle dans Riak. Dans Cassandra il est recommandé d'utiliser un UUID comme clé pour éviter autant que possible des écritures concurrentes.\nCe qu'il nous faut donc c'est pouvoir distinguer deux événements concurrents de deux événements causaux. Dans le cas où c'est causal on pourra faire respecter l'ordre. C'est seulement dans le cas de la concurrence qu'on est condamné à perdre des données, fusionner les données ou avertir l'utilisateur.\nPour fusionner les données, on peut utiliser des structures spéciales qui le permettent facilement comme les structures CRDT supportés par Riak.\nEn interne il s'agit de fusionner les éléments dans une liste en cas d'ajout, et de poser des tombstones dans le cas d'une suppression plutôt que de supprimer directement. Cela permet de mieux gérer la suppression au niveau de plusieurs nœuds qui en prennent connaissance au fur et à mesure, et ont besoin d'effectuer l'opération eux-aussi.\nPour distinguer les événements causaux des concurrents, on peut utiliser les version vectors. Chaque replica a sa version qu'il incrémente à chaque traitement, et l'ensemble de ces versions sont appelées version vector. Ces valeurs sont utilisées par chaque réplica pour déterminer s'il y a de la causalité ou si on garde les deux versions concurrentes.\nLes version vectors sont disponibles dans Riak 2.0, et sont appelés causal context. Le version vector est envoyé aux clients quand les valeurs sont lues, et renvoyé par les clients quand une valeur est écrite.","6---partitioning#6 - Partitioning":"Les partitions sont appelées :\nshard dans MongoDB, Elasticsearch et SolrCloud\nregion dans HBase\ntablet dans Bigtable\nvnode dans Cassandra et Riak\nvBucket dans Couchbase\nUn cluster shared-nothing signifie qu'il s'agit de plusieurs machines distinctes, par opposition au scaling vertical où c'est la même machine qui partage le processeur, la RAM etc. là on ne partage rien à part à travers le réseau.\nLes partitions existent depuis les années 80, et ont été redécouvertes par les BDD NoSQL et les Data warehouses Hadoop-based.\nVis-à-vis de la réplication, la notion de partition vient s'y superposer. On peut par exemple avoir des nœuds (ordinateurs) avec plusieurs partitions, et chacun d'entre eux peut être soit leader soit follower pour telle ou telle copie de telle ou telle partition.\nLa raison principale de vouloir des partitions est la scalabilité. Avec la réplication on pouvait scaler pour lectures, mais le partitionnement permet de scaler aussi en écriture.\nCependant, pour que le scaling fonctionne bien, il faut que la charge soit équitablement répartie entre les nœuds. Pour ce faire, on peut par exemple répartir aléatoirement les données dans les partitions (mais ça nécessiterait de demander à tous les nœuds en parallèle à chaque recherche).\nQuand la charge est mal répartie on appelle ça des partitions skewed (biaisées). Et quand un seul nœud se retrouve à tout gérer on l'appelle le hot spot.\nParmi les types de partitions on a :\nLa partition par key range. On va attribuer un range de clés à chaque nœud, et y stocker ces données-là. Si on connaît ce range à l'avance, on pourra même directement demander au nœud concerné pour notre recherche.\nOn pourra par exemple avoir le 1er nœud qui a les clés commençant par A et B, et le dernier les clés commençant par W, X, Y et Z.\nBigtable et son équivalent open source HBase, ainsi que RethinkDB et MongoDB jusqu'à la version 2.4 utilisent cette technique.\nDans chaque partition, on peut garder les entrées triées de la même manière que les LSM-Tree.\nOn a un risque de hot spot, par exemple dans le cas où on recherche par la clé qui serait le timestamp, et que les partitions sont groupées par journée. La partition du jour courant risque de devenir un hot spot. Dans ce cas on peut préfixer la clé par un nom ou autre chose, pour constituer un index concaténé par exemple.\nLa partition par hash of key. On a la même manière de stocker par clé qu'avant, sauf qu'on va hasher la clé avec une fonction de hash simple (mais qui ne donne pas de duplicata). Et on va assigner des ranges de hashs aux partitions. Ceci fait que les données seront aléatoirement réparties.\nMongoDB, Cassandra et Voldemort utilisent ce mécanisme.\nLe désavantage de hasher la clé c'est qu'on ne peut plus faire facilement de recherche par range.\nDans MongoDB, si on a activé les clés hashées, il faut envoyer les queries de range à toutes les partitions.\nRiak, Couchbase et Voldemort ne supportent pas du tout les queries de range.\nCassandra utilise un compromis entre les deux stratégies (hash et clé normale) : on a une clé composée avec une première partie hashée déterminant la partition, et ensuite une 2ème partie permettant de faire une recherche, y compris de range, dans la SSTable. On doit donc d'abord fixer la partition et ensuite on peut chercher ce qu'on veut efficacement.\nHasher la clé peut parfois ne pas suffire à éliminer les hot spot : dans le cas spécifique où on a une donnée qui est accédée / écrite de manière massive (par exemple une célébrité qui est fortement suivie qui s'exprime), il faut diviser cette entrée-là en plusieurs entrées sur plusieurs machines. On peut par exemple préfixer le hash d'un nombre et le répartir sur 100 machines différentes. Mais alors les lectures devront à chaque fois faire appel à toutes ces partitions et reconstruire la bonne donnée.\nPour le moment les BDD ne gèrent pas automatiquement ce genre de fonctionnalité, donc il faut le faire à la main.\nIl existe un concept appelé consistent hashing, mais il est surtout utilisé pour les caches distribués à travers le monde (type CDN) pour éviter le besoin d’entité centrale, et n’est pas efficace avec les BDD. Certaines docs de BDD l'invoquent par erreur, mais pour éviter la confusion il vaut mieux qu’on parle de hash partitioning.\nLes indexes secondaires sont extrêmement pratiques pour faire des recherches dans la BDD, mais elles introduisent une complexité supplémentaire.\nPar rapport à leur support :\nHBase et Voldemort ont évité de les supporter pour éviter la complexité de l'implémentation.\nRiak a commencé leur support.\nPour Elasticsearch et Solr, ils sont leur raison d'être.\nOn a 2 manières de les implémenter avec le partitionnement :\nDocument-based partitioning : on va créer un index local à la partition. Toutes les entrées de la partition seront indexées pour la colonne choisie, mais l'index n'aura aucune idée de ce qui est indexé sur une autre partition.\nLe problème c'est que quand on veut faire une recherche par index secondaire, on va alors devoir faire une requête auprès de toutes les partitions, puisque les partitions sont séparées par clé primaire, pas par l'index secondaire. On appelle ça le scatter / gather. Ceci fait que la requête va coûter cher, et devoir attendre que tous les nœuds répondent (donc on est soumis au problème des hauts percentiles qui nous ralentissent potentiellement beaucoup).\nCette approche est utilisée quand même dans MongoDB, Riak, Cassandra, Elasticsearch, SolrCloud et VoltDB.\nTerm-based partitioning : on crée un index global. Mais bien entendu il est hors de question de le mettre sur un seul nœud, au risque que ça devienne un bottleneck. On va le partitionner de même qu'on a partitionné l'index primaire : les premières clés de l'index secondaires seront dans la partition 1, celles juste après dans la partition 2 etc.\nCette technique rend la recherche rapide : puisqu'on sait quel nœud contient l'index qu'on veut, on lui envoie la requête directement. Par contre l'écriture est plus lente puisqu'elle va impliquer des modifications dans plusieurs partitions (celle de la donnée et de l'index primaire, et celle de l'index secondaire pour le mettre à jour).\nEn pratique, la mise à jour de l'index secondaire avec le term-partitioning se fait de manière asynchrone, et tant pis si une recherche avec l'index secondaire immédiatement après une écriture ne fonctionne pas.\nParmi les implémentations :\nAmazon DynamoDB met à jour son index term-partitioned de manière asynchrone.\nRiak et Oracle data warehouse permettent de choisir la technique de partitionnement de l'index secondaire.\nRégulièrement, pour augmenter les capacités ou remplacer une machine malade, on doit rediriger les requêtes et déplacer les données d'une machine à l'autre. On appelle ça le rebalancing entre partitions. Il y a plusieurs stratégies pour l'implémenter :\nUne stratégie à ne pas faire : hash mod N. Si on décidait de faire le modulo du hash de nos transactions pour les répartir dans les noeuds (par exemple le hash % 12 si on a 12 noeuds), alors à chaque fois que le nombre de noeuds changerait, on devrait faire du rebalancing, ce qui est beaucoup trop coûteux.\nFixed number of partitions. On va choisir un grand nombre de partitions, plus grand que le nombre de nœuds qu'on imagine qu'on va avoir, et on va attribuer plusieurs partitions par nœud (par exemple 100 par nœud). De cette manière, dès qu'on ajoute ou supprime un nœud, on peut déplacer quelques partitions ici et là pour équilibrer le tout.\nIl faut bien choisir le bon nombre de partitions, s'il y en a trop ça crée un manque de performance du fait de chercher dans trop de partitions, s'il n'y en a pas assez on va déplacer de trop gros blocs au moment du rebalancing. Ça peut être difficile à trouver si notre charge varie beaucoup.\nCette approche est utilisée par Riak, Elasticsearch, Couchbase et Voldemort.\nDynamic partitioning. Pour les BDD utilisant le partitionnement de type key range (et pas hash range), avoir un nombre de partitions fixe peut être problématique par rapport au skewing, et choisir à la main combien en mettre par nœud est fastidieux. On va donc vouloir un système qui répartir dynamiquement les partitions, par rapport à la quantité de données présente dans chaque partition.\nQuand une partition est jugée dynamiquement trop grosse, elle est coupée en 2 et une moitié est éventuellement déplacée sur un autre nœud.\nHBase et RethinkDB par exemple utilisent le partitionnement dynamique (puisqu'ils utilisent aussi le key range partitioning).\nPour le key range c'est obligatoire, mais le dynamic partitioning peut aussi être utilisé avec le hash range partitioning. MongoDB par exemple donne le choix de key range ou hash range, et dans les deux cas fait le rebalancing de manière dynamique.\nPartitioning proportionally to nodes. Le nombre fixe de partitions et le nombre dynamique de partitions est basé sur la taille des partitions. On peut choisir plutôt de se baser sur le nombre de partitions par nœud indépendamment de leur taille. On fixe un nombre de partitions par nœud et on répartit les données dedans. Si on ajoute un nœud, les partitions existantes maigrissent pour transférer une partie de leur données dans les partitions du nouveau nœud.\nCassandra et Ketama utilisent cette méthode.\nOn a un peu évoqué l'aspect manuel / automatique, mais plus concrètement :\nCouchbase, Voldemort et Riak créent des suggestions de rebalancing automatiquement, mais demandent la validation d'un administrateur humain pour opérer le rebalancing.\nLe rebalancing complètement automatique peut être tentant, mais il faut bien voir que c'est une opération longue et coûteuse, et que faire un mauvais rebalancing dans certaines conditions peut créer une cascade d'échec, le système croyant à tort que certains noeuds surchargés sont morts ou ce genre de chose. Globalement avoir un humain dans la boucle du rebalancing est une bonne idée.\nA propos de la question du routing de la requête, comment le client va savoir à quel nœud envoyer sa requête ?\nIl existe plusieurs solutions open source. Globalement 3 possibilités se dégagent :\nLe client envoie à un nœud en mode round robin (chacun son tour), et ce nœud qui connaît le bon nœud va faire lui-même la demande, va réceptionner la réponse, et la retransférer au client.\nLe client envoie la requête à un routing tier qui connaît le partitionnement actuel, et va pouvoir envoyer la requête au bon nœud.\nLe client connaît déjà le bon nœud, et va directement lui envoyer la requête.\nDans tous les cas, il y a le problème de savoir comment l'entité qui connaît le partitionnement actuel reste à jour malgré les rebalancing ? C'est un problème difficile.\nIl y a des protocoles pour atteindre un consensus dans les systèmes distribués, mais ils sont compliqués. On en parlera au chapitre 9.\nDe nombreux systèmes utilisent un service dédié au mapping entre partition / nœud et adresse ip.\nZooKeeper est l'un d'entre eux : tous les nœuds s'enregistrent auprès de ZooKeeper et lui notifient les rebalancings. C'est lui qui fait autorité en matière de routing. Et il notifie les entités qui en ont besoin (par exemple le routing tier) de l'état du réseau de nœuds / partitions.\nEspresso de Linkedin utilise Helix, qui lui-même utilise ZooKeeper.\nHBase, SolrCloud et Kafka utilisent aussi ZooKeeper.\nMongoDB utilise son outil maison et mongos daemons comme routing tier.\nCassandra et Riak utilisent un gossip protocol pour que les nodes s'échangent leurs changements de topologie. La requête peut alors arriver sur n'importe quel nœud qui la redirigera correctement vers le bon. Ça met plus de complexité sur les nœuds, mais ça évite la dépendance à un outil externe comme ZooKeeper.\nCouchbase ne fait pas de rebalancing automatique. Et il est couplé en général avec moxi, qui est un routing tier écoutant les changements venant des nœuds.\nEnfin concernant l'accès au routing tier par le client, son adresse ip en changeant que rarement, une configuration de nom via DNS est suffisante pour y accéder.","7---transactions#7 - Transactions":"Les transactions sont des unités logiques regroupant plusieurs lectures / écritures. Soit elles réussissent, soit elles échouent et alors le client peut réessayer en toute sécurité. Il s'agit d'abstraire tout un pan d'échecs partiels qu'il faut gérer sinon à la main.\nPresque toutes les BDD relationnelles, et certaines non relationnelles utilisent les transactions pour encapsuler les requêtes. Cependant avec la hype récente du NoSQL, on a un certain nombre de BDD qui arrivent avec l'idée que pour la scalabilité et la high availability, les transactions doivent être abandonnées ou donner des garanties beaucoup plus faibles.\nACID signifie Atomicity, Consistency, Isolation and Durability. Malheureusement il y a de l'ambiguïté sur chacun des termes, surtout sur l'isolation.\nAtomicity aurait pu être appelé abortability, parce qu'il s'agit d'annuler une partie des requêtes d'une même transaction si la partie suivante échoue. Comme ça on peut recommencer la transaction entière sans soucis.\nConsistency est ici entendu comme étant la cohérence des données du point de vue applicatif. Contrairement aux 3 autres termes, la consistency relève bien de la responsabilité du code applicatif. Il s'agit de règles liées au domaine en question, par exemple les débits et les crédits doivent s'annuler.\nIsolation consiste à gérer les transactions concurrentes : chaque transaction doit pouvoir s'exécuter sans être parasitée par d'autres transactions en plein milieu. On parle aussi de serializability, pour dire qu'il faut la même garantie que si les transactions étaient exécutées en série les unes à la suite des autres. La plupart des BDD ne fournissent cependant pas ce niveau de garantie.\nDurability veut dire qu'une fois la transaction commitée, elle ne peut pas disparaître toute seule mais reste dans la BDD. Ca implique par exemple la technique du log write-ahead pour les B-Tree ou LSM-Tree, pour ne pas perdre les données. Cela implique aussi la réplication dans le cas de systèmes distribués.\nL'atomicité et l'isolation concernent les transactions avec plusieurs écritures (plusieurs objets), mais aussi les “transactions” avec une seule écriture. Si un problème survient en plein milieu de l'écriture, il faut s'assurer que la base de données ne se retrouve pas dans un état inconsistant.\nOn dit parfois qu'on supporte les transaction (et même qu'on est ACID) quand on assure l'intégrité pour une seule écriture, mais c'est une erreur, la transaction désigne principalement le groupe de plusieurs écritures.\nLa garantie pour les écritures sur un seul objet est parfois suffisante, mais dans pas mal de cas il faut une garantie sur plusieurs objets :\nDans les BDD relationnelles (ou de graphe), les clés étrangères (ou les edges) doivent être mises à jour en même temps que l'objet change.\nDans les BDD de document, les données à mettre à jour sont en général dans le même document, donc pas de besoin de multi-object transaction de ce côté. Cependant les BDD de document encouragent aussi la dénormalisation à la place des jointures, et dans ce cas les données doivent être mises à jour conjointement dans plusieurs endroits pour ne pas que la BDD devienne inconsistante.\nQuand on a des index secondaires, alors il faut mettre à jour aussi cet index, et ces index sont des objets différents du point de vue de la BDD, donc on doit bien avoir des transaction multi-objets.\nConcernant l'annulation des transactions, c'est dans cette philosophie qu'est construite la notion d'ACID : si ça échoue on recommence la transaction.\nCertaines BDD ne sont pas du tout dans cette philosophie : les BDD répliquées en mode leaderless sont plutôt sur du “best effort”. La BDD exécute ce qu'elle peut, et si on est dans un état inconsistant, c'est à l'application de gérer les erreurs.\nCertains ORM comme celui de Rails et Django ne réessayent pas les transactions automatiquement, alors que c'est là le but même de l'ACIDité de celles-ci.\nCertains problèmes peuvent quand même survenir quand une transaction est abandonnée :\nIl se peut qu'elle ait fonctionné mais qu'on ne reçoive pas la réponse.\nSi l'erreur est due à une surcharge de requêtes, réessayer la transaction n'arrangera pas les choses, au contraire.\nIl ne faut pas réessayer si l'erreur est de nature permanente (par exemple une violation de contraintes, ie. une transaction qui fait quelque chose d'interdit), mais seulement si l'erreur est de nature temporaire (réseau, crash d'un node, etc.).\nSi la transaction a d'autres side-effects que sur la BDD (par exemple l'envoi d'un email), alors réessayer juste après peut refaire les side-effects. On parlera des Atomic commit et Two-phase commit plus tard.\nSi en réessayant à nouveau on échoue quand même, la requête pourrait être complètement perdue.\nL'isolation au sens strict de transactions sérialisables est quelque chose de coûteux que les BDD ne veulent souvent pas implémenter. On a donc seulement des weak isolation levels qui ne répondent pas à tous les problèmes posés par les transactions concurrentes. Il faut bien comprendre chaque problème et chaque solution proposée pour choisir ceux qu'on a besoin pour notre application.\nRead commited est le niveau d'isolation le plus basique.\nCa garantit :\nQu'il n'y aura pas de dirty reads : si au cours d'une transaction non terminée une écriture a été faite, une autre transaction au cours de la lecture ne doit pas pouvoir lire ce qui a été écrit.\nQu'il n'y aura pas de dirty writes : si au cours d'une transaction non terminée une écriture a été faite mais pas encore commitée, et au cours d'une autre transaction l'écriture est écrasée, alors il on peut se retrouver avec des données inconsistantes.\nRead commited est l'isolation par défaut dans de nombreuses bases de données, parmi elles : Oracle 11g, PostgreSQL, SQL Server 2012, MemSQL.\nCôté implémentation :\nPour les dirty reads, l'objet tout entier est bloqué avec un lock par la transaction, jusqu'à ce qu'elle soit commitée ou abandonnée.\nPour les dirty rights, on pourrait aussi mettre un lock, mais c'est perdre beaucoup en efficacité parce que certaines requêtes lentes vont empêcher de simples lectures. Alors la plupart du temps 2 valeurs sont conservées : l'ancienne valeur de l'objet qu'on donne aux nouveaux lecteurs, et la nouvelle valeur qui sera la valeur finale quand la transaction en cours sera terminée.\nSnapshot isolation and repeatable read. Le read committed garantit que sur une même donnée il n'y aura pas des lectures / écritures de transactions différentes, mais ça ne garantit pas que différents objets de la base de données resteront cohérents entre eux au cours d'une même transaction.\nProblèmes :\nOn peut par exemple lire une donnée, puis le temps qu'on lise la suivante celle-ci a été modifiée, et la combinaison des deux lectures donne quelque chose d'incohérent. En général il suffit de refaire la 1ère lecture et on a quelque chose de cohérent à nouveau.\nPlus grave : une copie de BDD peut prendre plusieurs heures, et le temps de la copie des changements peuvent être faits, de manière à ce qu'au final on ait copié au fur et à mesure quelque chose d'incohérent. Même chose avec une requête d'analyse énorme qui met beaucoup de temps à lire un grand nombre de données : si elles sont modifiées en cours de route.\nLa snapshot isolation est supportée par PostgreSQL, MySQL avec InnoDB, Oracle, SQL Server et d'autres.\nCôté implémentation :\nEn général pour les writes on a un write lock qui bloque les autres writes sur un même objet.\nEn revanche les reads n'utilisent pas de locks, et le principe c'est que les writes ne bloquent pas les reads et les reads ne bloquent pas les writes.\nChaque transaction va avoir son snapshot de données en fonction des données sur lesquelles il opère, et ces données ne seront pas changées de toute la transaction. On appelle ça le multi-version concurrency control (MVVC).\nLa snapshot isolation est appelée de différentes manières en fonction des BDD :\nDans Oracle elle est appelée serializable.\nDans MySQL et PostgreSQL c'est appelé repeatable read.\nCe terme repeatable read vient du standard SQL qui ne contient pas la notion de snapshot isolation, vu qu'elle n'existait pas à l'époque de System R (sur lequel est basée la norme SQL).\nEt pour compliquer le tout, IBM DB2 utilise le terme de repeatable read pour désigner la serializability, ce qui fait qu'il n'a plus vraiment de sens.\nPreventing lost updates. Jusqu'ici on s'est intéressé aux problèmes de lecture dans un contexte d'écritures dans d'autres transactions. Mais il y a également des problèmes survenant lors d'écritures concurrentes entre-elles. Les dirty writes en sont un exemple, et les lost updates un autre.\nSi deux transactions modifient une même valeur de manière concurrente, la dernière transaction écrasera la valeur écrite dans la première. On dit aussi qu'elle va la clobber.\nExemples : un compteur incrémenté deux fois mais qui se retrouve finalement incrémenté de 1, ou encore deux utilisateurs modifiant la même page wiki en envoyant la page entière, le dernier écrasant les modifications de l'autre.\nCe problème courant a de nombreuses solutions :\nAtomic write operations : vu que le problème des lost updates vient du fait qu'on lit d'abord la valeur avant de la mettre à jour, certaines BDD donnent la possibilité de faire une lecture suivie d'un update avec une atomicité garantie.\nMongoDB fournit aussi la possibilité de faire des modifications locales à un document JSON de manière atomique.\nRedis permet de modifier par exemple des priority queues de manière atomique.\nEn général les BDD le font en donnant un lock sur l'objet concerné par l'écriture.\nExplicit locking : on peut, en pleine requête SQL, indiquer qu'on prend un lock manuellement sur le résultat d'une partie de la requête, pour le réutiliser dans une écriture juste après.\nOn peut facilement oublier de le faire ou mal prendre en compte la logique applicative.\nAutomatically detect lost updates : de nombreuses BDD permettent de vérifier la présence de lost updates, et en cas de détection d'annuler la requête et de la retenter juste après.\nL'avantage aussi c'est qu'on peut le faire avec la même fonctionnalité que le snapshot isolation. PostgreSQL, Oracle et SQL Server le font de cette manière. MySQL / InnoDB en revanche ne supportent pas cette fonctionnalité.\nCompare-and-set : certaines bases de données qui ne fournissent pas de transactions permettent des opérations compare-and-set qui consistent à exécuter un changement seulement si la donnée n'a pas été modifiée depuis la dernière fois qu'on l'a lue, ce qui permet normalement d'éviter les lost updates.\nDans le cas des BDD avec réplication : quand on a de la réplication les locks ne servent à rien, et le compare-and-set non plus. La meilleure solution est d'exécuter les deux requêtes et de garder une copie des deux résultats, puis de faire appel à du code applicatif ou d'utiliser des structures spéciales de fusion pour résoudre le conflit.\nRiak 2.0 fournit des structures qui permettent d'éviter les lost updates à travers les réplicas.\nMalheureusement la plupart des BDD ont par défaut une stratégie last write wins (LWW) qui est provoque des lost updates.\nWrite skews and phantoms : on généralise ici le cas des dirty writes et des lost updates dans la mesure où on va écrire sur des objets différents. Chaque requête concurrente lit les données, puis écrit dans un objet différent, mais comme ils le font indépendamment, le code applicatif ne se rend pas compte qu'ils cassent une contrainte applicative qui devait être garantie par le code applicatif. On appelle ça des write skew.\nExemple : il faut au moins un docteur on-call, il en reste deux et les deux décident de cliquer sur le bouton pour se désister. Les deux transactions se font en parallèle et modifient des objets différents liés au profil de chaque docteur.\nLes solutions sont moins nombreuses :\nLes BDD ne fournissent pas de moyen de mettre des contraintes sur des objets différents. On peut en revanche utiliser du code custom avec les triggers ou les materialized views si c'est supporté.\nOn peut locker les objets concernés par notre logique métier à la main au moment de faire la requête.\nCette solution marche si on a déjà les objets dont on veut que la valeur ne change pas. Mais si dans notre cas la condition c'est qu'une entrée avec une certaine caractéristique n'existe pas pour pouvoir faire quelque chose (par ex insérer un nom d'utilisateur s'il n'est pas déjà pris), alors on ne peut pas locker à la main une absence d'objet.\nDans ce cas où le write skew est causé par une écriture dans une transaction, qui change le résultat d'une recherche dans une autre transaction, le phénomène est appelé un phantom.\nUne solution (peu élégante) peut consister à matérialiser les phantoms en créant une table spéciale avec un champ pour chaque élément possible, et demander au code applicatif de faire un lock manuel sur l'élément matérialisé correspondant à chaque write. Dans la plupart des cas, il vaut cependant mieux privilégier la serializability.\nMalheureusement la snapshot isolation ne suffit pas, il faut une vraie serializability dont on va parler un peu plus loin.\nSerializability : il y a un niveau au-dessus de tous les autres, qui permet de garantir que les transactions vont s'exécuter avec le même niveau de garantie vis-à-vis des race conditions que s'ils étaient exécutés les uns à la suite des autres, sans parallélisme du tout. Il y a 3 techniques pour l'implémenter dans un contexte non distribué :\nActual serial execution : on va exécuter les transactions vraiment les uns à la suite des autres, sur un seul thread.\nCette option est envisagée maintenant alors qu'elle était rejetée auparavant parce que la RAM est peu chère et on peut mettre l'essentiel de la BDD dedans, ce qui permet de rendre les transactions très rapides. Et aussi parce que les transactions OLTP sont courtes et impliquent peu de requêtes, alors que les OLAP sont certes longues mais sont read-only donc peuvent se faire hors de l'execution loop.\nCette approche est utilisée dans VoltDB / H-Store, Redis et Datomic.\nPour que ce soit possible sur un seul thread, il faut qu'il ne soit pas bloqué pendant qu'on demande à l'utilisateur la suite en plein milieu de la transaction. Il faut donc collecter les données qu'il faut pour toute la transaction, et faire la transaction entière en une fois. Pour ce faire, on utilise les stored procedures.\nCes procédures permettent d'exécuter du code écrit dans un langage spécifique : pour Oracle PL/SQL, pour SQL Server T-SQL, pour PostgreSQL PL/pgSQL, mais ces langages sont vieux, peu testables, et n'ont pas beaucoup de fonctionnalités.\nDes BDD modernes permettent cependant d'utiliser des langages modernes pour les stored procedures : VoltDB utilise Java et Groovy, Datomic utilise Java et Clojure, Redis utilise Lua.\nPour la réplication, VoltDB permet d'exécuter les stored procedures sur chaque machine. Il faut alors que ces procédures soient déterministes.\nDans le cas où on veut scaler en écriture on a besoin de partitionnement. On peut alors créer autant de partitions que de coeurs de processeur sur la machine, et assigner un thread par partition. Chaque partition exécutera bien les transactions de manière séquentielle.\nAttention par contre aux requêtes qui ont besoin d'effectuer des opérations à travers plusieurs partitions (à peu près tout sauf les données key/value), ça provoque des ralentissement de plusieurs ordres de grandeur.\nDonc les contraintes pour utiliser l'exécution en série :\nChaque transaction doit être petite et rapide.\nLa BDD doit entrer en RAM. Une partie peu utilisée de la BDD peut rester sur disque, mais si on doit aller la chercher dans le thread unique c'est chaud au niveau perf. Une solution pourrait être d'abandonner la transaction, mettre la donnée dont on a besoin en RAM, et la retenter.\nLa charge en écriture doit être assez faible pour être traitée par une machine, ou alors il faut un partitionnement sans requêtes qui s'exécutent sur plusieurs partitions.\nTwo-Phase Locking (2PL) : c'est l'algorithme qui a été utilisé pendant 30 ans. Il s'agit de mettre un lock sur la donnée dès lors qu'on est en présence d'une transaction qui fait un write, même vis-à-vis de transactions qui ne font que des reads. En revanche s'il n'y a que des transactions qui font des reads, pas besoin de lock.\nComparé au snapshot isolation où les writes ne bloquaient pas les reads, et les reads ne bloquaient pas les writes, ici les writes bloquent aussi les reads.\n2PL est utilisé dans MySQL (InnoDB), SQL Server et DB2.\nFonctionnement : il y a les shared locks et les exclusive locks. A chaque fois qu'un read est fait sur un objet, la transaction prend un shared lock, qui permet de la faire attendre au cas où l'exclusive lock serait pris. Si une transaction veut faire un write, alors elle prend l'exclusive lock dès qu'elle peut, et tout le monde doit attendre pour accéder à cet objet que sa transaction entière soit terminée (d'où le 2-phase : on prend le lock, puis on termine le reste de la transaction de manière exclusive).\nPour être vraiment comme des transactions sérialisées, il faut aussi résoudre le problème des phantoms (un write qui modifie le résultat d'une recherche). On le fait en créant des locks sur des prédicats : si une transaction a besoin de faire une query pour chercher quelque chose, alors elle déclare un shared lock sur un prédicat, et si un write modifie le résultat correspondant à ce prédicat, alors ils se bloqueront mutuellement.\nLe lock sur des prédicats étant très mauvais d'un point de vue performance, on approxime souvent les prédicats sous forme de lock d'index, en s'assurant qu'on lock éventuellement plus d'objets, et pas moins pour respecter la sérialisabilité.\nLe souci de cette méthode c'est la performance, en partie du fait de nombreux locks, mais surtout du fait que n'importe quelle transaction peut faire attendre toutes les autres. Donc on a un flow assez imprédictible, et des high percentiles mauvais.\nLes deadlocks sont détectés et résolus en annulant l'une des transactions, mais s'ils sont nombreux, ça fait d'autant moins de performance.\nSerializable Snapshot Isolation (SSI) : il s'agit d'un algorithme très prometteur qui fournit la sérialisabilité, et en même temps n'a que très peu de différence de performance avec la snapshot isolation.\nLa SSI est à la fois utilisée par les BDD single node (PostgreSQL depuis la version 9.1) et distribuées (FoundationDB).\nFonctionnement : contrairement à l'idée de faire des locks pour protéger la transaction d'un conflit éventuel, qui est une approche dite pessimiste, ici on adopte une approche optimiste et on réalise toutes les transactions dans un snapshot à part. Au moment du commit on vérifie qu'il n'y a pas eu de conflits. S'ils ont eu lieu, on annule la transaction et on laisse l'application recommencer.\nIl y a une difficulté vis-à-vis du fait de détecter si une transaction avec lecture initiale suivie d'une écriture devient invalide parce que la donnée lue est modifiée par une autre transaction. Il y a 2 solutions pour régler ça :\nDétecter les lectures faites sur le MVCC (multi version concurrency control) qui ne sont plus à jour au moment où la transaction veut être commitée. Si on détecte, on annule la transaction.\nDétecter les writes qui affectent les reads d'une autre transaction en plaçant une balise sur l'index concerné pour indiquer que plusieurs transactions utilisent la donnée. Au moment de commiter, la BDD vérifie qu'il n'y a pas de conflit par rapport au write fait par la transaction qui avait été marquée. Si oui on annule la dernière qui veut commiter. Le marquage peut être enlevé quand la situation de concurrence est résolue.\nAu niveau de la performance, plus la BDD est précise sur quelle transaction doit être annulée, et plus ça lui prend du temps. D'un autre côté si elle en annule trop ça fait plus de transactions annulées.\nComparé au 2PL on a quelque chose de plus performant mais aussi de plus prédictible, vu que les requêtes n'ont pas à attendre qu'une longue requête ait terminé. Et si on a une forte charge de lectures c'est parfait aussi puisqu'elles ne sont jamais bloquées.\nComparé à l'exécution vraiment en série, on n'est pas limité au CPU d'une seule machine, FoundationDB distribue la détection des conflits sur plusieurs machines.\nGlobalement, vu qu'une transaction peut vite voir ses prémisses invalidées par d'autres, pour qu'on n'ait pas beaucoup d'annulation de transactions, il faut que celles-ci soient assez courtes et rapides. Mais d'un autre côté, 2PL et l'exécution sériale ne font pas mieux avec les transactions longues.","8---the-trouble-with-distributed-systems#8 - The trouble with distributed systems":"Les fautes partielles :\nLe souci avec les systèmes distribués c'est qu'ils peuvent agir de manière non déterministe, et qu'une partie du système peut être en échec alors que le reste fonctionne. C'est une chose dont on n'a pas l'habitude dans un seul ordinateur.\nLes superordinateurs choisissent en général d'écrire des checkpoints en DD, et d'arrêter tout le système pour réparer le composant problématique en cas de panne, pour ensuite reprendre là où ça en était à partir du checkpoint.\nLes systèmes distribués de type “cloud” ou “web” sont à l'opposé :\nils sont trop gros pour tolérer d'éteindre à chaque panne, et ils ne peuvent de toute façon pas tolérer d'arrêter le service\nils utilisent du matériel bon marché pour scaler\nils sont répartis à travers le globe, utilisant le réseau internet qui est très peu fiable comparé à un réseau local.\nIl faut que la gestion des problèmes matériels fasse partie du design de notre système.\nLe réseau :\nLe réseau internet (IP) est construit de manière à être peu fiable de par sa nature asynchrone. Un paquet peut à tout moment être perdu, corrompu, mettre beaucoup plus de temps à arriver etc. pour diverses raisons, parce qu'il passe par des dizaines de nœuds divers et variés qui peuvent être surchargés, débranchés, mal configurés etc.\nOn a des protocoles comme TCP construits par dessus pour corriger ça et renvoyer les paquets perdus ou corrompus.\nQuand on envoie un paquet, on ne sait pas s'il a été reçu ou pas. Au mieux on peut demander au destinataire de répondre, mais s'il ne répond pas on ne sait pas ce qu'il s'est passé. Tout ce qu'on peut faire c'est avoir un timeout, et considérer l'échec après le timeout.\nC'est le cas d'internet qui est peu fiable, mais le réseau ethernet local est également asynchrone. Donc les messages échangés entre les ordinateurs d'un même datacenter sont aussi prompts aux corruptions et pertes.\nUne étude a trouvé qu'il y a 12 fautes réseau par mois dans un datacenter moyen.\nAjouter de la redondance ne règle pas autant de problèmes qu'on le croit puisqu'il y a aussi les erreurs humaines des ops qui sont nombreuses\nLa détection des machines en état de faute est difficile, mais il y a des moyens :\nSi le processus applicatif est mort mais que l'OS tourne, la machine répondra peut-être par un message TCP indiquant qu'elle refuse les connexions.\nDans le même cas, la machine peut aussi avertir les autres nœuds que son processus applicatif est mort. HBase fait ça.\nDans le cas spécifique d'un datacenter, on peut avoir accès aux switches réseaux pour avoir certaines informations sur l'état connu de certaines machines qui ne répondent plus depuis un certain temps.\nDe même avec les routeurs qui peuvent immédiatement répondre que telle ou telle machine est injoignable si on les interroge.\nLa question de la valeur du timeout est une question particulièrement épineuse et pas simple. Une des manières est de tester en environnement réel et d'ajuster en fonction des performances.\nCet ajustement peut être automatique, Akka et Cassandra font ça.\nLa congestion du réseau est souvent causée par des problèmes de queuing diverses :\nau niveau des switchs\nau niveau des machines si tous les CPU sont occupés\nTCP qui fait du queuing pour éviter la corruption de paquets, et qui retente l'envoie du paquet de manière transparente (ce qui prend du temps)\nNe pourrait-on pas rendre la communication fiable du point de vue matériel ?\nPour ce faire, il faudrait qu'elle soit synchrone. C'est le cas du réseau téléphonique à commutation de circuit, qui alloue une ligne permettant d'envoyer une quantité fixe de données de manière régulière. Les divers switch et autres éléments réseaux qui établissent cette communication allouent cette quantité pour que le transfert puisse se faire.\nDans le cas des communications autres que stream audio / vidéo, on ne sait pas à l'avance quelle quantité de données on voudra, ni quand on voudra faire le transfert. La commutation par paquets permet de ne rien envoyer quand il n'y a pas besoin, et d'envoyer des paquets de taille variable quand c'est nécessaire. Le prix c'est que le réseau n'est pas en train de nous allouer de la place en permanence, et qu'il y a du queuing.\nC'est donc bien un choix d'allocation dynamique et non pas de réservation statique des ressources réseaux qui fait qu'on utilise toutes nos ressources disponibles mais avec des délais variables. On fait ce genre de choix aussi pour l'allocation dynamique des CPU vis à vis des threads.\nLes clocks : les clocks des ordinateurs sont globalement peu fiables, et d'autant moins dans un contexte d'ordinateurs distribués.\nIl y a 2 types de clocks sur un ordinateur :\nLes time-of-the-day clocks : ils renvoient le temps courant, en général sous forme d'entier depuis l'epoch (1er janvier 1970).\nVu qu'ils sont synchronisés par NTP (network time protocol), on peut régulièrement avoir des sauts dans le temps, et donc pour mesurer des durées c'est pas le top.\nLes monotonic clocks : ils renvoient une valeur arbitraire, mais garantissent qu'après un certain temps, la valeur renvoyée sera l'ancienne + le temps écoulé\nA propos de la précision :\nGoogle suppose que les clocks de ses machines se décalent de l'équivalent de 17 secondes pour un clock resynchronisé une fois par jour.\nLe protocole de mise à jour des clocks NTP ne peut pas être plus précis que le temps de latence d'envoi/réception des messages (une expérimentation a montré un minimum de 35 ms pour une synchronisation via internet).. Et en cas de congestion du réseau c'est pire.\nDans les machines virtuelles le CPU est partagé, donc on peut se retrouver avec des sauts bizarres dans le clock à cause de ça.\nEn cas de besoin, on peut mettre en place des infrastructures de haute précision qui se mettent à jour par GPS, mais c'est coûteux. C'est ce qui est fait sur les machines de trading à haute fréquence.\nEn fait, il faudrait voir le clock plutôt comme un intervalle que comme un temps. Malheureusement la plupart des API ne le présentent pas comme ça.\nUne exception est constituée par l'API TrueTime de Google Spanner, qui renvoie un groupe de 2 valeurs : [earliest, latest].\nDans le cas particulier de Google, en partant du principe que les intervalles de confiance sont fiables, si deux intervalles pour deux requêtes ne se chevauchent pas, alors on est sûrs que la requête avec l'intervalle plus récent a eu lieu après l'autre. Google utilise ça pour faire de la snapshot isolation dans un environnement distribué, mais pour ça il équipe chaque datacenter d'une réception GPS ou d'une horloge atomique, sans quoi les intervalles seraient trop grands. En dehors de Google cette solution basée sur le temps n'est pour le moment pas viable.\nContrairement à un CPU ou une carte réseau, quand un clock est défectueux la machine peut quand même donner l'impression que tout va bien, et faire des erreurs qui se voient beaucoup plus difficilement.\nC'est en particulier problématique si on se sert des clocks pour faire des timestamps pour vérifier quelle transaction a eu lieu la 1ère dans un système distribué. Et c'est encore plus problématique avec du LWW (last write wins) : si on noeud a son clock qui retarde, tous ses messages finiront par être rejetés en faveur de ceux des autres nœuds parce que considérés comme anciens.\nPlutôt que les clocks physiques, il faut utiliser des clocks logiques, c'est-à-dire des techniques pour détecter l'ordre des choses plutôt que le moment où elles ont eu lieu.\nUn thread peut se mettre en pause pendant un temps indéterminé pour des raisons très variées : le garbage collector du langage, la machine virtuelle, l'OS qui a besoin de le mettre en pause pour faire autre chose etc. Dans un système distribué “shared nothing” il n'y a pas de mémoire partagée, donc il faut partir du principe qu'un nœud peut se retrouver arrêté pendant que le monde autour de lui aura continué.\nIl existe des systèmes appelés temps réel (real time, ou hard real time pour bien insister sur l'aspect contrainte de temps à respecter absolument). Ces systèmes sont pensés et testés sous tous les angles pour respecter un certain nombre de contraintes de temps de réponse. On les utilise principalement dans les machines où le temps est crucial (par exemple le déclenchement d'un airbag).\nPour le problème spécifique du garbage collector, certains systèmes demandent à leur nœud de prévenir quand il y a un besoin de garbage collection, et au besoin redirigent le trafic vers d'autres nœuds en attendant que ce soit fait. Ça permet de réduire pas mal les problèmes de pause non voulue de l'application.\nSavoir, vérité et mensonge :\nDans des conditions aussi difficiles que les systèmes distribués où on ne peut rien savoir de certain sauf à travers les messages qu'on reçoit ou ne reçoit pas, on peut quand même créer des systèmes qui fonctionnent : il est possible d'avoir quelque chose de fiable construit sur des bases offrant peu de garanties, à conditions que le modèle de système qu'on a choisi convienne.\nLa vérité dans un contexte distribué est déterminée par la majorité. Pour éviter la dépendance à un noeud particulier, et étant donné qu'un noeud, quel qu'il soit, ne peut pas faire confiance à sa propre horloge vu qu'il peut entrer en pause à tout moment sans le savoir, on décide de mettre en place des quorums pour qu'une majorité de noeuds décident par exemple si un noeud est mort ou non.\nIl faut bien s'assurer que and on noeud pense être doté d'une responsabilité (il est le leader, il a le lock sur un objet etc.), il se fie quand même à ce que disent la majorité des noeuds : s' ils lui disent qu'il n'a plus la responsabilité en question, alors il faut qu'il accepte de se comporter comme tel, sous peine d'inconsistances dans le système.\nPour garantir qu'un lock soit bien respecté, on peut utiliser un lock service qui fournit un token incrémental à chaque lock. Si le nœud a son temps alloué qui a expiré, et qu'il essaye d'écrire alors qu'un autre a déjà écrit à sa place, son token sera rejeté par le lock service. On parle de fencing token.\nZooKeeper permet de fournir ce genre de fencing token s'il est utilisé comme lock service.\nJusqu'ici on est parti du principe que les noeuds peuvent de plus répondre, ne pas savoir qu'ils n'ont plus une certaine responsabilité, ou échouer. Mais qu'ils restent “honnêtes” au sens où ils ne vont pas dire qu'ils ont reçu un message alors qu'ils ne l'ont pas reçu, ou encore falsifier un fencing token. De tels cas de corruption s'appellent une Byzantine fault.\nÇa vient du Byzantine Generals Problem où on imagine dans la ville antique de Byzance, des généraux de guerre essayent de se mettre d'accord, et communiquant par messager, mais où certains généraux mentent sans se faire découvrir.\nOn imagine donc que certains nœuds peuvent être complètement corrompus jusqu'à ne plus suivre le protocole attendu du tout, par exemple dans le cas d'un logiciel dans un contexte aérospatial soumis à des radiations.\nOu alors se mettent carrément à tricher intentionnellement, soit à cause d'un piratage, soit plus classiquement un contexte de communication inter-organisations, où les organisations ne se font pas confiance.\nC'est le cas par exemple pour la blockchain où les participants ne se font pas confiance puisque n'importe lequel pourrait essayer de tricher.\nDans notre cas habituel de serveurs web, on part du principe que le client final derrière son navigateur pourrait être malicieux, mais sinon les serveurs de l'organisation sont fiables. Et on ne met pas en place de mécanismes contre les problèmes de fautes byzantines, parce que c'est trop compliqué.\nLa plupart des algorithmes contre les fautes byzantines comptent sur le fait que la majorité des nœuds ne vont pas être infectés par le problème, et donc pourront garder le contrôle contre la minorité du réseau corrompue. Donc ça peut être utile dans un contexte d'application peer-to-peer, mais si on charge notre version du logiciel dans tous les nœuds, ça ne nous protégera pas des bugs. Et de même si un hacker prend le contrôle d'un nœud, on imagine qu'il pourra aussi prendre le contrôle des autres nœuds.\nOn peut néanmoins se prémunir contre des formes modérées de mensonges avec quelques astuces :\nFaire un checksum des paquets pour vérifier qu'ils n'aient pas été corrompus. TCP/UDP le font mais parfois laissent passer.\nVérifier la validité de toutes les données entrées par l'utilisateur.\nDans le cas de la mise à jour depuis des serveurs NTP, faire des requêtes auprès de plusieurs serveurs, pour que ceux qui n'ont pas une bonne valeur soient rejetés.\nNotre système doit prendre en compte les problèmes matériels qu'on a décrits, mais il ne doit pas non plus être complètement dépendant du matériel exact sur lequel il tourne pour pouvoir changer le matériel. On va donc créer une abstraction qui est le system model.\nConcernant les considérations liées au temps, on en a 3 :\nSynchronous model : on part du principe que les erreurs réseau, de clock ou les pauses de processus sont limités à certaines valeurs définies; En pratique la réalité ne colle pas à de modèle.\nPartially synchronous model : on part du principe que le système se comporte de manière synchrone la plupart du temps, sauf parfois où il déborde. Ce modèle correspond beaucoup mieux à nos systèmes web distribués.\nAsynchronous model : on est beaucoup plus restrictif puisqu'on considère qu'aucune notion de temps ne peut être fiable. Et donc on ne peut pas utiliser de timeouts non plus.\nConcernant les considérations liées aux échecs de noeuds, il y en a aussi 3 :\nCrash-stop faults : on considère que si un nœud fait une faute, on l'arrête et c'en est fini de lui.\nCrash-recovery faults : les nœuds peuvent être en faute, puis revenir en état correct un peu plus tard. C'est ce modèle qui nous est en général le plus utile pour nos systèmes web.\nByzantine (arbitrary) faults : les nœuds peuvent faire absolument n'importe quoi.\nPour définir qu'un algorithme d'un system model distribué est correct, il peut avoir deux types de propriétés :\nsafety : il s'agit d'une propriété qui dit que rien de mal ne doit se passer. Par exemple, la uniqueness de quelque chose. Si une telle propriété est rompue, c'est parce que chose a été violée et qu'il y a eu un dommage non réparable.\nliveness : il s'agit d'une propriété qui dit qu'une chose attendue doit arriver. Par exemple l'availability, le fait de recevoir une réponse. Si une telle propriété est rompue, c'est que ce qui était attendu n'a pas eu lieu, mais pourrait avoir lieu plus tard\nIl est courant de demander à ce que les caractéristiques de safety soient respectées dans tous les cas, même si tous les nœuds crashent, un mauvais résultat ne doit pas être retourné. Pour les caractéristiques de liveness, on peut demander à ce qu'elles soient respectées seulement dans certains cas, par exemple si un nombre suffisant de nœuds est encore en vie.\nEnfin, il faut bien garder en tête qu'un system model n'est qu'un modèle. Dans la réalité, on sera amené à rencontrer des erreurs non prévues. Et à l'inverse, sans raisonnement théorique, on pourrait avoir des erreurs dans nos systèmes pendant longtemps sans s'en rendre compte. Les deux sont aussi importants l'un que l'autre.\nC'est la différence entre le computer science (théorique), et le software engineering (pratique).","9---consistency-and-consensus#9 - Consistency and Consensus":"Pour rendre un système tolérant aux fautes, il faut introduire des abstractions. C'est ce qu'on a fait avec les transactions par exemple en partant du principe qu'une transaction est atomique. Une autre abstraction intéressante est le consensus : faire en sorte que les nœuds se mettent d'accord.\nLa consistency est une question importante à laquelle on peut apporter différents niveaux de garantie. Comme avec l'isolation où il s'agissait de traiter la concurrence entre deux transactions, avec la consistance il s'agit de coordonner l'état des réplicas vis-à-vis des délais (replication lag) et des fautes.\nLa linearizability consiste en une abstraction qui donne l'illusion que le replication lag n'existe pas, qu'il n'y a en fait qu'une seule copie des données : dès qu'une copie a été faite, le système doit se comporter comme si cette donnée la plus récente était lisible depuis partout.\nUne des conséquences c'est qu'il faut que quand une lecture a été faite avec une valeur, ce soit cette valeur qui soit retournée par tous les réplicas à partir de ce moment. Si une écriture a lieu entre temps ça peut être cette nouvelle valeur écrite, mais certainement pas une valeur plus ancienne.\nOn doit pouvoir éviter le cas où une personne recharge la page et voit que le match a été gagné par telle équipe, et juste après une autre personne affiche la page, et voit que le match est toujours en cours.\nEn revanche, il n'y a pas de contraintes de délais : si l'écriture prend du temps c'est pas grave. Et si deux transactions sont concurrentes et que l'une arrive avant l'autre c'est pas grave non plus.\nLinearizability vs serializability : la serializability est une notion d'isolation pour pouvoir garantir la manipulation de plusieurs objets au sein d'une même transaction, sans être gêné par les autres transactions. La linearizability consiste à renvoyer systématiquement le résultat le plus récent à chaque lecture une fois que celui-ci a été lu au moins une fois.\nLe 2-phase locking et l'actual serialization garantissent aussi la linearizability. En revanche la Serializable snapshot isolation ne la garantit pas puisqu'elle va créer des snapshots pour les transactions, et ne pas inclure les writes récents dans ces snapshots (ce qui peut facilement résulter à ce que certaines transactions aient un write et d'autres pas).\nParmi les applications de la linearizability :\nL'élection d'un nouveau leader est un problème où dès que le lock a été pris, il faut que personne d'autre ne puisse le prendre.\nApache ZooKeeper et etcd sont souvent utilisés comme système de lock pour implémenter l'élection de leader.\nApache Curator ajoute des choses par-dessus ZooKeeper.\nLe fait de garantir qu'un nom d'utilisateur ne sera pas pris deux fois, ou encore qu'un compte en banque ne va pas en dessous de 0.\nDans le cas où on a 2 canaux de communication, l'un des canaux peut être plus rapide que l'autre : par exemple si on écrit une image, et qu'on enqueue un message pour qu'une version thumbnail de l'image soit générée. Si le traitement du message dans la queue est plus rapide que le temps qu'on met à écrire l'image entière, la thumbnail risque d'être faite à partir d'un fichier partiel. Il faut donc s'assurer de l'ordre de ce qui est fait dans ces 2 canaux.\nLa linearizability parmi les systèmes de réplication connus :\nSingle-leader replication : ça pourrait être linearizable si la BDD n'utilise pas de snapshot isolation. Mais il reste le problème de savoir qui est le leader, et dans le cas de réplication asynchrone on peut perdre des données au failover.\nConsensus algorithms : ces algorithmes permettent d'implémenter la linearizability en répondant aux problèmes soulevés dans la single-based replication. On va y revenir.\nC'est comme ça que fonctionnent ZooKeeper et etcd.\nMulti-leader replication : ces systèmes ne sont pas linearizable puisqu'il y a des écritures concurrentes qui sont résolues après coup.\nLeaderless replication : certains affirment qu'en respectant la règle du quorum consistency, on peut obtenir une linearizability sur des BDD Dynamo-style. Mais ce n'est en général pas vrai.\nRiak ne fait pas de read repair à cause du manque de performance de cette technique, et il la faudrait pour la linearizability.\nCassandra fait le read repair, mais il perd la linearizability à cause de son algo last write wins qui cause des pertes de données.\nSi on part de l'exemple de la multi-leader replication, on constate que c'est pratique parce que si la connexion est rompue entre deux datacenters, les deux peuvent continuer indépendamment, et se resynchroniser dès que la connexion est rétablie. On a alors une grande availability du système, mais on ne respectera pas la linearizability. A l'inverse si on reste en single-leader, le datacenter déconnecté du datacenter leader se verra inopérant jusqu'à rétablissement du réseau. Mais on garde la linearizability.\nLe CAP theorem décrit cette problématique et a permis en son temps d'ouvrir la discussion, mais il est fondamentalement inutile de nos jours.\nDans la pratique, de nombreuses BDD n'implémentent pas la linearizability parce que ça coûte trop cher en performance. Il n'y a malheureusement pas d'algorithme qui permette d'avoir de la linearizability sans ce problème de performance qui est d'autant plus grand qu'il y a beaucoup de délais dans le réseau.\nGaranties d'ordre d'exécution :\nLa causal consistency (causalité) est au cœur des problématiques des systèmes distribués faisant fonctionner des applications qui ont du sens.\nRespecter la causalité n'implique pas forcément un total order (ordonnancement total) de tous les éléments, mais uniquement de ceux liés entre eux par une relation cause / conséquence.\nLa linearizability quant à elle, implique un total order. Elle est donc une contrainte plus forte que la causal consistency.\nC'est trop récent à l'époque du livre pour être dans des systèmes en production, mais il y a de la recherche sur des techniques permettant de détecter la causalité sans total order. Par exemple une généralisation des version vectors.\nLa causal consistency coûte quand même cher s'il faut traquer toutes les transactions et leurs relations. On peut sinon utiliser des sequence numbers pour créer un clock logique permettant de définir un ordre total.\nSur une configuration single-leader, il suffit d'incrémenter un compteur à chaque opération au niveau du leader.\nDans le cas où il y a plusieurs leaders, on a d'autres solutions:\nGénérer des sequence numbers différents pour chaque nœud (par exemple pair pour l'un, impairs pour l'autre).\nUtiliser un clock physique.\nAllouer des plages à chaque nœud, par exemple 0 à 1000 pour l'un, 1000 à 2000 pour l'autre etc.\nMalheureusement certains nœuds peuvent aller plus vite que d'autres, et ces techniques ne garantissent pas la causalité dans le système.\nLa causalité peut être assurée dans un environnement multi-leader grâce aux Lamport timestamps. Il s'agit d'une idée de Leslie Lamport dans un des papiers les plus cités des systèmes distribués.\nLe principe est d'avoir un compteur normal par nœud, et pour le rendre unique on l'associe à un chiffre représentant le nœud lui-même. Et l'astuce de la technique consiste à ce que chaque nœud et chaque client garde en mémoire la valeur la plus élevée de compteur qu'il connaisse. Et quand il a connaissance de la valeur d'un autre compteur plus élevé que celui qu'il connaissait au détour d'une opération, il met immédiatement à jour le compteur du nœud sur lequel il fera la prochaine opération avec cette valeur-là.\nCette technique permet de respecter la causalité, mais aussi un total ordering.\nMalheureusement ça ne règle pas tous nos problèmes : même avec un total order, on ne peut pas savoir sur le moment si un nom d'utilisateur unique est en passe d'être pris par un autre nœud ou non pour savoir sur le moment s'il faut l'autoriser soi-même ou non. Avec le temps et les opérations, on finira par avoir un ordonnancement total, mais pour le moment non.\nC'est l'objet du total order broadcast.\nLe total order broadcast nécessite qu'aucun message ne soit perdu, et que tous les messages soient délivrés à tous les nœuds dans le même ordre.\nLa connexion peut être interrompue, mais les algorithmes de total order broadcast doivent réessayer et rétablir l'ordre des messages dans tous les nœuds quand le réseau est rétabli.\nZooKeeper et etcd implémentent le total order broadcast.\nA noter aussi que le total order broadcast maintient l'ordre tel qu'il est au moment de l'émission des messages, donc c'est plus fort que le timestamp ordering.\nOn peut voir ça comme un log de messages transmis à tous les nœuds dans le bon ordre.\nOn peut ainsi implémenter la linearizability à partir d'un système respectant le total order broadcast.\nPour l'écriture :\nOn ajoute un message au log disant qu'on voudrait écrire\nOn lit le log et on attend que notre message nous parvienne\nSi le premier message concernant ce sur quoi on voulait écrire est le nôtre, alors on peut valider l'écriture dans le log.\nPour la lecture :\nOn peut faire pareil qu'avec l'écriture : ajouter un message indiquant qu'on veut lire, attendre de le recevoir, puis faire la lecture en fonction de l'ordre indiqué dans le log.\nC'est comme ça que ça marche dans les quorum reads, dans etcd.\nOn peut demander à avoir tous les messages de log liés à une lecture puis faire la lecture à partir de là.\nC'est comme ça que fonctionne la fonction sync() de ZooKeeper.\nOn peut lire à partir d'un réplica synchrone avec le leader (en cas de single-leader), dont on est sûr qu'il a les données les plus récentes.\nA l'inverse, on peut aussi implémenter un système total order broadcast à partir d'un système linéarisable : il suffit d'avoir un compteur linéarisable qu'on attache à chaque message envoyé via total order broadcast.\nOn peut enfin noter qu'à la fois la linearizability et le total order broadcast sont tous deux équivalents au consensus.\nLe consensus consiste en la possibilité pour les nœuds de se mettre d'accord sur quelque chose (on pense par exemple à l'élection de leader, ou à l'atomic commit problem où il faut choisir entre garder ou non une transaction présente sur certains nœuds), alors même que des noeuds peuvent être en faute à tout moment.\nC'est un sujet très subtil et complexe.\nLe FLP result est un résultat théorique montrant que le consensus est impossible dans un system model asynchrone. Dans la pratique, à l'aide de timeouts (même s'ils peuvent être parfois faussement positifs), on arrive à atteindre le consensus.\nQuand une transaction est écrite en BDD, il est hors de question de la retirer par la suite parce qu'elle a pu être prise en compte par d'autres transactions. Il faut donc bien réfléchir avant d'entrer définitivement l'écriture en BDD.\nLe two-phase commit (2PC) est un algorithme de consensus implémenté dans certaines BDD.\n2PC n'est pas très bon, des algorithmes plus modernes existent chez ZooKeeper (Zab) et etcd (Raft).\nAttention à ne pas confondre 2PC avec 2PL (2 phase lock) qui permet l'isolation pour la sérialisation, le mieux est d'ignorer le rapprochement de leur nom.\nFonctionnement :\non a besoin d'un nouveau composant : le coordinator.\nLors d'une transaction, après les lectures / écritures, quand on veut inscrire vraiment tout ça en BDD, le coordinator va procéder en 2 étapes :\ndemander successivement à chaque nœud si il est prêt à faire un commit et attendre leur réponse.\nsi oui, faire le commit, sinon annuler la transaction.\nL'idée c'est que lors de la 1ère phase, quand le coordinator demande si les nœuds sont prêts, en fait il leur demande aussi de tout préparer pour que même en cas de crash rien ne soit perdu de leur côté. La seule chose qui leur resterait à faire alors serait de valider les données déjà mises en forme pour aller dans la BDD.\nLorsque le coordinator prend sa décision finale de faire s'exécuter ou d'annuler la transaction en phase 2, alors il l'écrit localement et passe un point de non-retour. A partir de là il réessayera en permanence de faire finaliser la transaction auprès de tout nœud qui deviendrait indisponible à partir de ce moment-là.\nDans le cas où le coordinator crash juste après avoir demandé aux noeuds de se préparer fait que les noeuds doivent rester en attente. Ils ne peuvent pas unilatéralement prendre de décision de valider ou annuler une transaction chacun de leur côté. La solution est d'attendre que le coordinator revienne, lise ce qu'il avait décidé sur son fichier de log, et envoie les messages qui conviennent.\nIl existe un autre algorithme appelé 3-phase commit (3PC) qui résout le problème de l'aspect bloquant lié à l'attente du commit du coordinator, mais il implique des temps de réseaux bornés. Or nos réseaux habituels sont imprévisibles. Pour cette raison, c'est le 2PC qui continue d'être utilisé.\nLe souci de ce cas c'est surtout le lock au niveau de la BDD, souvient sur les entrées concernées par la transaction. Si le coordinator ne revient jamais ou que les logs sont perdus, alors on peut se retrouver face à des locks orphelins, et un administrateur humain devra manuellement résoudre ces conflits, puisque les locks sont censés survivre même à un redémarrage de la BDD.\nEn pratique, les transactions distribuées sont souvent décriées parce qu'elles coûteraient trop par rapport à ce qu'elles apporteraient.\nLes transactions distribuées utilisant MySQL sont connues pour être 10 fois plus lentes que les mêmes transactions sur un seul nœud.\nIl y a deux types de transactions distribuées : celles qui sont implémentées par une même BDD qui tourne sur plusieurs nœuds, et celles qui consistent à faire communiquer des technologies hétérogènes d'un nœud à un autre. Les dernières sont bien plus compliquées.\nLes transactions hétérogènes peuvent faire communiquer par exemple une BDD et un message broker, et ne commiter que si tout a marché, et annuler tout sinon.\nXA (eXtended Architecture) est justement un protocole qui permet d'implémenter le 2PC dans des technologies hétérogènes. Il s'agit d'une API en C qui se connecte aux programmes qui s'exécutent sur une machine.\nIl est supporté par de nombreuses BDD : PostgreSQL, MySQL, DB2, SQL Server, Oracle.\nEt par plein de message brokers : ActiveMQ, HornetQ, MSMQ, IBM MQ.\nXA a cependant des limitations, par exemple il ne permet pas de détecter les deadlocks, et ne supporte pas le SSI (serializable snapshot isolation).\nIl faut noter quand même que le coordinateur est souvent un single point of failure vu qu'il contient lui-même des données persistantes cruciales pour le fonctionnement du système. Mais étonnamment les possibilités de le rendre réplicable sont en général rudimentaires.\n2PC a quand même un point problématique aussi, c'est qu'il a tendance à amplifier les failures, puisque dès qu'un nœud ne répond pas on va annuler la transaction. C'est pas très “fault tolerant” tout ça.\nLe consensus tolérant les fautes :\nOn peut citer 4 propriétés définissant le consensus :\n3 de safety :\nUniform agreement : tous les nœuds doivent arriver au même choix.\nIntegrity : aucun nœud ne décide deux fois.\nValidity : le choix décidé est valide.\nEt une de liveness :\nTermination : les nœuds ne se retrouvent pas bloqués, même en cas de crash de certains d'entre eux. Ils évoluent vers la terminaison du processus de choix.\n2PC ne remplit pas cette condition puisque le coordinator peut bloquer le système en cas de faute.\nLes algorithmes de consensus tolérants aux fautes sont difficiles à implémenter (donc on ne va pas les implémenter nous-mêmes mais utiliser des outils qui les implémentent).\nCe sont les suivants :\nViewstamped replication (VSR)\nPaxos\nRaft\nZab\nCes algorithmes sont de type total order broadcast.\nA chaque tour les nœuds décident du prochain message à traiter, et décident par consensus.\nPour le remplacement des leaders les nœuds utilisent des timeouts, et lancent une élection avec un quorum. Et c'est seulement quand le nœud a bien reçu le message de la majorité qu'il sait qu'il est bien le leader.\nCes algorithmes sont encore un sujet de recherche, et ont parfois des edge cases problématiques qui basculent le leader entre 2 nœuds, ou qui forcent en permanence le leader à renoncer.\nServices de coordination :\nLes services comme ZooKeeper sont rarement utilisés directement par les développeurs. On va plutôt les utiliser à travers d'autres services comme HBase, Hadoop YARN, OpenStack Nova et Kafka.\nCe sont en gros des stores de clé-valeur qui tiennent en RAM.\nZooKeeper a notamment ces caractéristiques :\nLinearizable atomic operations : à l'aide d'un lock, une seule opération parmi les opérations concurrentes peut réussir.\nTotal ordering of operations : les fencing tokens permettent de préserver l'ordre des transactions.\nFailure detection : les nœuds ZooKeeper et les autres nœuds s'envoient des messages régulièrement, et en cas de timeout déclarent le nœud échoué.\nChange notifications : les clients (les autres nœuds) peuvent s'abonner à des changements spécifiques des autres nœuds à travers ZooKeeper, ce qui évite de faire des requêtes pour voir où ça en est.\nZooKeeper est pratique pour des informations qui changent toutes les minutes ou heures comme l'association d'une adresse ip à un leader.\nSi on veut répliquer l'état d'une application qui peut nécessiter des milliers ou millions de changements par seconde, on peut utiliser des outils comme Apache BookKeeper.\nZooKeeper fait partie des membership services, issu d'une longue recherche depuis les années 80. En couplant le consensus avec la détection de fautes, ils permettent d'arriver à une certaine connaissance de qui composent les membres du réseau.\nIntégrer des systèmes disparates ensemble est l'une des choses les plus importantes à faire dans une application non triviale.\nLes données sont souvent classées en 2 catégories qu'il est de bon ton d'expliciter :\nLes systems of record qui sont les données de référence.\nLes derived data systems qui sont en général des données dénormalisées, par exemple stockées dans un cache.","10---batch-processing#10 - Batch Processing":"Il existe 3 types de systèmes :\nServices (online systems) : un client envoie un message et reçoit une réponse. En général, le temps de réponse et la disponibilité (availability) sont très importants.\nBatch processing systems (offline systems) : des tâches de fond, souvent exécutées périodiquement, durant plusieurs minutes voire plusieurs jours. La performance se mesure par la quantité de données traitées.\nStream processing systems (near-real-time systems) : il s'agit d'une forme particulière de batch processing. On ne répond pas à une requête d'un client humain, mais on réagit à un événement assez rapidement après qu'il ait eu lieu.\nLe batch processing avec les outils Unix :\nL'outil sort d'Unix va automatiquement prendre en charge des données plus grandes qu'il n'y a de mémoire vive, mettre ça en disque pour faire les opérations, et paralléliser au niveau CPU.\nLa philosophie unix est très proche de l'agile et du devops. On casse les gros problèmes en petits, on fait de petits programmes qui font une chose et la font bien. On fait des itérations courtes.\nUne des clés de la puissance des outils unix est l'interface uniforme, permettant de les composer ensemble. De nos jours c'est plutôt l'exception que la norme parmi les programmes.\nOn a également une séparation entre la logique et le câblage des données grâce à stdin et stdout.\nLes outils unix sont très pratiques pour l'expérimentation : les entrées sont immuables, et on peut envoyer la sortie vers un less par exemple.\nMais le plus souci c'est que les outils unix ne marchent que sur une machine, pas sur des architectures distribuées.\nMapReduce est un modèle assez bas niveau de batch processing, connu pour être l'algorithme qui permet à Google d'être aussi scalable.\nIl ressemble aux outils unix mais sur des architectures distribuées.\nIl prend des inputs, et envoie le résultat dans des outputs.\nLes inputs ne sont normalement pas modifiés et il n'y a pas de side-effects autre que les outputs.\nLes fichiers d'output sont écrits de manière séquentielle.\nAlors que les outils unix écrivent dans stdout, MapReduce écrit dans un système de fichiers distribués.\nHadoop utilise HDFS (Hadoop distributed File System), qui est une implémentation open source de Google File System.\nIl en existe d'autres comme GlusterFS, Quantcast File System (QFS).\nD'autres services sont similaires : Amazon S3, Azure Blob Storage, OpenStack Shift.\nFonctionnement de HDFS :\nHDFS est basé sur une approche shared-nothing, c'est-à-dire qu'il lui suffit d'ordinateurs connectés par un réseau ip classique.\nUn démon tourne sur chaque nœud et expose les fichiers qui sont sur ce nœud. Et un serveur central appelé NameNode contient des références vers ces fichiers.\nIl y a de la réplication entre les nœuds.\nDe cette manière HDFS est capable de faire fonctionner des dizaines de milliers de machines et des petabytes de données.\nLe fonctionnement se fait en 4 étapes :\n1- On lit des fichiers et on les structure sous forme d'entrées.\nC'est le parser qui s'en charge.\n2- on appelle la fonction mapper pour extraire des clés-valeurs\nIl s'agit ici d'une fonction où on peut ajouter du code à nous. La fonction est appelée une fois par entrée et permet d'extraire de la manière souhaitée les clés-valeurs.\n3- on trie les clés-valeurs par clés\nC'est fait automatiquement.\n4- on appelle la fonction reducer pour faire notre action sur les clés-valeurs\nLà encore on peut ajouter du code à nous. On a en paramètre toutes les valeurs associées à une clé et on peut en faire ce qu'on veut en sortie.\nOn peut aussi enchaîner plusieurs MapReduce, l'un préparant les données en entrée pour l'autre.\nMapReduce permet aussi de paralléliser les opérations de manière transparente pour le code. Comme il y a de nombreuses entrées à traiter, chacune peut s'exécuter localement sur la machine du réplica où elle est. Cela permet aussi d'éviter les transferts réseau en localisant les calculs.\nConcernant le code custom des fonctions mapper et reducer, dans Hadoop elles sont écrites en Java, alors que dans MongoDB et CouchDB elles sont écrites en Javascript.\nContrairement aux outils Unix, MapReduce ne permet pas de chaîner directement ses jobs. Il faut plutôt écrire le résultat d'un job dans un dossier, puis donner ce dossier comme entrée au MapReduce suivant. C'est du moins comme ça que ça se passe dans Hadoop.\nDu coup tout un tas d'outils permettent de coordonner les jobs MapReduce dans Hadoop : Oozie, Azkaban, Luigi, Airflow, Pinball.\nD'autres outils haut niveau autour de Hadoop permettent également de gérer ce genre de choses : Pig, Hive, Cascading, Crunch, FlumeJava.\nA propos des reduce-side joins avec MapReduce :\nOn ne va envisager les jointures que sur des tables entières pour notre cas qui concerne les batchs, typiquement quand on traite des BDD destinées à l'analyse des données.\nPar exemple : si on a d'un côté des événements avec un user id, et de l'autre côté la table des users avec certaines de leurs caractéristiques. On va vouloir corréler les deux pour ne sélectionner que les faits d'un certain type d'utilisateurs.\nPour des raisons de performance, on va opter pour le plus de localité possible, et donc on ne va pas faire des accès random en traitant chaque entrée une par une là où elle est. On va plutôt copier la table des users dans le même filesystem HDFS que la table des faits, puis on va lire les deux conjointement.\nLa technique des sort-merge joins permet à plusieurs mappers de trier des données par la même clé (par exemple l'id de l'utilisateur pour des événements dont il est l'objet, et pour des données personnelles sur l'utilisateur), puis à un reducer de récupérer ces données et de les merger ensemble pour faire l'action qu'on voulait avec cette jointure.\nUne fois que les mappers ont fait leur travail, chaque clé agit comme une adresse au sens où les valeurs d'une même clé vont être envoyées au même nœud pour que le reducer soit exécuté avec ces valeurs-là. Il y a bien une localité des données pour l'exécution du traitement.\nD'une certaine manière on a séparé l'obtention des données du traitement des données, ce qui contraste avec la plupart des applications où on fait des requêtes en BDD en plein milieu du code.\nDans certains cas on peut se retrouver avec des hot keys par exemple des données liées aux followers de célébrités. Ceci peut donner trop de charge à un nœud de reducer, et les autres devront alors l'attendre pour que l'opération de MapReduce soit terminée.\nPour éviter ça on va détecter les hot keys et les traiter différemment des autres clés. On va les séparer dans plusieurs reducers différents sur plusieurs nœuds, et ensuite on fusionnera le résultat final.\nPig fait d'abord une opération pour déterminer les hot keys, et ensuite fait le traitement de la manière décrite.\nCrunch a besoin qu'on lui dise explicitement les hot keys.\nHive a aussi besoin que les hot keys soient spécifiés explicitement dans une table de metadata séparée.\nA propos des map-side joins :\nLes reduce-side joins sont pratiques parce que les mappers lisent les données quelles qu'elles soient, préparent, trient et donnent ça aux reducers. Mais tout ceci coûte en terme de copies au niveau du DD.\nSi nous avons des informations sur la structure des données, nous pouvons faire des map-side joins, où il s'agit simplement de tout faire dans les mappers et se débarrasser des reducers.\nUn cas où c'est utile est quand on doit faire une jointure entre un grand dataset et un petit dataset, suffisamment petit pour que ça puisse être chargé dans la RAM de chaque mapper. Chaque mapper aura alors à disposition l'ensemble du petit dataset pour chercher les entrées qui l'intéressent.\nOn appelle cet algorithme le broadcast hash join.\nCette méthode est supportée par Pig, Hive, Cascading et Crunch, ainsi que la data warehouse Impala.\nDans le cas où on a deux tables partitionnées de la même manière et qu'on veut faire une jointure dessus, on peut faire le map-side join sur chacune des partitions, ce qui permet de ne charger en mémoire qu'une faible quantité de données.\nSi les deux datasets sont en plus triés selon la même clé, alors on n'a pas besoin que l'une des deux entre en mémoire. Les mappers pourront chercher les données qui les intéressent du fait qu'elles sont triées de la même manière.\nLe choix d'un map-side join ou d'un reduce-side join a un impact sur les données résultant du MapReduce : avec le map-side les données seront partitionnées de la même manière qu'elles l'étaient à l'input, alors qu'avec le reduce-side, les données seront partitionnées selon la clé de la jointure.\nLe batch se rapprocherait plus des OLAP que des OLTP dans la mesure où il scanne une grande quantité de données, mais le résultat d'un batch sera une forme de structure et non pas un rapport à destination de data analysts.\nUn exemple de batch est l'utilisation initiale de MapReduce par Google pour faire des indexes pour son moteur de recherche. Encore aujourd'hui MapReduce est un bon moyen de créer des indexes pour Lucene / Solr.\nUn autre exemple est de construire des BDD key-value pour du machine learning, ou pour des systèmes de recommandation.\nOn pourrait penser que la bonne solution serait d'orienter la sortie du MapReduce vers notre BDD, entrée par entrée, mais c'est une mauvaise idée, à la fois pour des raisons de performance (localité des données, pas d'utilisation réseau, parallélisation des tâches) et d'atomicité du batch job.\nLa bonne solution consiste plutôt à créer une toute nouvelle BDD sur le filesystem distribué, de la même manière qu'on crée le fichier d'index.\nPlusieurs systèmes de BDD supportent le fait de créer des fichiers de BDD à partir d'opérations MapReduce : Voldemort, Terrapin, ElephantDB, HBase.\nCes fichiers sont écrits une fois et demeurent ensuite read-only.\nLes systèmes de BDD qui les supportent vont servir les anciennes données, commencer à copier ce fichier depuis le filesystem distribué vers le disque local, et dès que c'est fait switcher vers le fait de servir ces données-là.\nMapReduce suit la philosophie Unix :\nOn peut rejouer une opération MapReduce autant de fois qu'on veut sans dommages pour les données d'entrée.\nSi les données sont corrompues pour une raison éphémère, on retente l'opération.\nSi c'est un bug logiciel, on le résout, et on refait la même opération encore.\nOn a une séparation de la logique, et du câblage pour décider où vont les données.\nPar contre là où les outils unix font beaucoup de parsing parce que le format est le texte, Hadoop et compagnie peuvent utiliser Avro et Parquet pour permettre une évolutivité des schémas.\nComparaison entre Hadoop et les BDD distribuées :\nLes BDD distribuées implémentant le massively parallel processing (MPP) avaient déjà la capacité de faire des jointures distribuées en parallèle depuis 10 ans quand MapReduce est sorti. La différence c'est qu'elles obligent les données à respecter un schéma prédéfini.\nPar contraste, le modèle MapReduce a permis de collecter n'importe quelles données, y compris du texte, des images etc. et de les mettre tels quels, transférant alors le problème de l'interprétation de ces données au consommateur.\nÇa s'appelle le sushi principle : raw data is better. Et ça permet par exemple de consommer la même donnée différemment selon les contextes.\nOn peut par exemple collecter les données, et dans une étape séparée utiliser un MapReduce pour réorganiser ces données de manière à les transformer en data warehouse.\nLes BDD MPP sont efficaces pour le cas d'utilisation qu'elles prévoient : la manipulation des données via des requêtes SQL. En outre, ça fournit un bon niveau d'abstraction pour ne pas avoir à écrire de code.\nD'un autre côté, tout ne peut pas être traité avec des requêtes SQL. Si on a des utilisations particulières comme du machine learning, des systèmes de recommandation, de recherche dans du texte etc. alors on a probablement besoin d'exécuter du code custom sur ces données. C'est ce que permet MapReduce.\nSi on a MapReduce, on peut construire un modèle SQL par dessus. C'est ce qu'a fait Hive.\nLa versatilité permise par les raw data dans du Hadoop permettent d'implémenter du SQL, du MapReduce, mais aussi d'autres modèles encore.\nOn a des BDD OLTP comme HBase\nOn a des BDD analytiques comme Impala\nLes deux utilisent HDFS mais pas MapReduce.\nDeux autres différences :\nLa manière de gérer les fautes n'est pas la même : les systèmes MPP annulent la requête en cas de faute, alors que MapReduce va annuler une partie du job, peut être le mapper ou le reducer, et réessayer pour le terminer.\nLa gestion de la mémoire n'est pas la même : les systèmes MPP vont avoir tendance à stocker beaucoup en mémoire vive, alors que MapReduce va plutôt écrire sur disque dès que possible.\nCeci est en partie dû au fait que MapReduce a été fait par Google dans un contexte où les jobs de grande priorité et de faible priorité tournent sur les mêmes machines. En moyenne un job batch a 5% de chances d'être arrêté parce que ses ressources sont préemptées par un processus plus prioritaire. C'est aussi pour cette raison qu'on écrit sur disque dès que possible et qu'on tolère beaucoup les fautes.\nSans ce genre de contraintes de préemption, MapReduce pourrait se révéler moins pertinent dans sa manière de fonctionner.\nMalgré le succès de MapReduce dans les années 2000, il y a d'autres modèles intéressants.\nMapReduce, bien que simple à comprendre, n'est pas simple à mettre en œuvre. Par exemple, le moindre algorithme de jointure a besoin d'être refait from scratch.\nIl existe un ensemble d'outils construits par-dessus MapReduce, et qui fournissent d'autres abstractions (Pig, Hive, Cascading, Crunch).\nIl existe aussi des modèles complètement différents de MapReduce, et qui permettent d'obtenir de bien meilleures performances pour certaines tâches.\nContrairement aux programmes Unix, MapReduce fait de la matérialisation des états intermédiaires, c'est-à-dire que la sortie d'un MapReduce doit être complètement écrite avant de pouvoir être consommée par un autre processus. A contrario les programmes Unix mettent en place un buffer sous le forme du pipe qui permet au programme suivant de démarrer en consommant la sortie du précédent bout par bout au fur et à mesure.\nCeci a plusieurs désavantages :\nLe fait de devoir attendre qu'un job MapReduce soit complètement terminé avant d'entamer le suivant est source de lenteur.\nSouvent, le mapper ne sert qu'à lire le code déjà formaté correctement et est donc inutile. On pourrait alors chaîner plusieurs reducers.\nLe fait que les états intermédiaires matérialisés soient sur le filesytem distribué veut dire qu'ils sont aussi répliqués, ce qui est plutôt overkill pour l'usage qu'on en fait;\nPour répondre à ces problèmes, des dataflow engines ont été développés.\nParmi les plus connus il y a Spark, Tez et Flink.\nTez est relativement petit, alors que Spark et Flink sont des frameworks plus gros, avec leurs propres couches réseau, scheduler, API.\nIls permettent :\nde ne pas nécessairement faire l'étape de tri, ce qui permet de faire des économies quand l'ordre des entrées n'importe pas.\nde chaîner les operators (qui remplacent les mappers et reducers) dans l'ordre souhaité, ce qui permet aussi d'éviter les mappers inutiles.\ndes optimisations locales, sans faire appel au réseau, et sans écrire dans le filesystem distribué HDFS quand ce n'est pas nécessaire. On ne matérialise donc pas forcément les états intermédiaires.\nde commencer la prochaine opération dès que des données sont disponibles, et sans attendre que la précédente soit terminée.\nOn peut les utiliser pour faire les mêmes opérations qu'avec MapReduce, et comme les operators sont une généralisation des mappers et reducers, on peut switcher de MapReduce vers Spark ou Tez dans Pig, Hive ou Cascading.\nAlors qu'avec MapReduce on avait une bonne tolérance aux fautes, avec Spark, Flink et Tez on doit trouver d'autres astuces :\nSi la machine qui faisait le calcul est perdue, on trouve d'autres données qui permettent de reconstruire la donnée perdue : la liste des opérations appliquées, et un état précédent, ou au pire la donnée originale qui est sur HDFS.\nConcernant le problème du déterminisme, si une opération était non déterministe et que la donnée a été transmise à un autre acteur alors qu'on a une faute, alors il faut tuer l'acteur en question. Et de manière générale il faut éviter les opérations non déterministes.\nOn peut également utiliser les batch pour des données sous forme de graphs.\nPageRank est un exemple connu de système sous forme de graph.\nPour les parcourir et y faire des opérations, un MapReduce ne suffit pas puisqu'il ne peut faire qu'une lecture/écriture. Mais on peut répéter ce genre d'opérations sous forme itérative, tant qu'on n'a pas atteint le but recherché.\nCependant MapReduce n'est pas très efficace pour itérer plusieurs fois avec de petits changements.\nOn a alors un modèle appelé bulk synchronous parallel (BSP), aussi connu sous le nom de Pregel model, popularisé par un papier de Google.\nIl est implémenté par Apache Giraph, Spark GraphX API, Flink Gelly API.\nC'est la même chose qu'avec MapReduce sauf que les données sont conservées en mémoire, et en cas de faible changement, il n'y a que peu de choses à recréer.\nIl est résistant aux fautes, en vérifiant l'état de tous les vertices régulièrement, et en l'écrivant sur disque dur.\nLe calcul est parallélisé, et ça cause beaucoup de communication réseau. Dans la mesure du possible, si les données peuvent tenir en RAM sur un seul nœud, ou même sur son DD, il vaut mieux tenter l'approche non distribuée qui sera plus rapide, sinon le Pregel model est inévitable.\nA mesure que le temps passe, des couches sont construites par dessus MapReduce, permettant d'avoir des abstractions.\nLes jointures peuvent ainsi être faites par des opérateurs relationnels, permettant à l'outil de décider de la manière de l'implémenter. C'est supporté par Hive, Spark et Flink.\nGrâce à ces diverses abstractions, les batch processings se rapprochent des BDD distribuées d'un point de vue performance, tout en permettant quand c'est nécessaire, d'exécuter du code arbitrairement pour plus de flexibilité.","11---stream-processing#11 - Stream Processing":"L'idée derrière les streams c'est de faire la même chose que les batchs, mais de manière beaucoup plus récurrente, et jusqu'à la plus petite unité possible : plutôt que de faire le traitement une fois par jour on fait le traitement dès qu'on a des données nouvelles.\nLes données dans le stream processing sont des events. Ils sont mis à disposition par un producer, à destination de consumers. Ils sont groupés dans un stream d'events.\nComment transmettre les event streams :\nOn peut imaginer un mécanisme de polling où le producer met à disposition et les consumers vérifient régulièrement s'il n'y a pas de nouveaux events. Mais ça fait beaucoup de messages à envoyer si on veut être réactif. Il vaut mieux que les consumers soient notifiés à chaque event.\nEn général les BDD supportent mal cette technique. On a bien les triggers qui permettent d'exécuter du code à chaque requête, mais ça reste assez limité. Les BDD ne sont pas conçues pour ça.\nLa bonne solution est d'utiliser un messaging system.\nPour différencier ces systèmes, il faut regarder deux points :\nQue se passe-t-il si le producer crée plus d'events que les consumers ne peuvent consommer ?\nSoit les consumers sautent ces messages.\nSoit les messages sont mis dans un buffer qui grossit, et dans ce cas que se passe-t-il si ça continue de grossir jusqu'à dépasser la RAM ?\nSoit les consumers empêchent le producer de produire tant qu'ils n'ont pas fini les events déjà produits.\nQue se passe-t-il si le système est down ou que des nœuds crashent ? est-ce qu'on perd des events, ou est-ce qu'ils sont persistés / dupliqués ?\nUne première possibilité est la communication directe entre producer et consumers :\nDes librairies de messaging brokerless comme ZeroMQ et nanomsg utilisent TCP/IP pour communiquer.\nUDP multicast est un protocole qui permet d'envoyer des events sans garantie de réception.\nStatsD et Brubeck utilisent UDP pour envoyer des métriques en tolérant des pertes.\nLe consumer peut exposer une API REST ou RPC appelée par le producer. C'est l'idée des webhooks. Dans le cas où les consumers sont HS, il se peut simplement qu'ils ratent l'event.\nUne autre solution est l'utilisation de message brokers (ou message queues).\nCe sont en fait des BDD, soit in memory, soit avec une forme de persistance, qui mettent en relation les producers et consumers en général de manière asynchrone.\nIls tolèrent donc les crashs côté consumer, puisque le message pourra être traité plus tard.\nPar rapport aux BDD :\nIls ont des similarités, et peuvent même participer à des protocoles 2PC utilisant XA.\nMais il y a des différences :\nLes BDD gardent les données, alors que les brokers les effacent quand ça a été traité.\nLes brokers partent du principe que le nombre de messages à avoir en mémoire est faible. S'il grossit les performances peuvent se dégrader.\nLorsqu'il y a plusieurs consumers, on peut trouver deux stratégies pour leur envoyer les events :\nLoad balancing : si les messages coûtent cher à traiter, on donne chaque message à un consommateur.\nLes protocoles d'encapsulation AMQP et JMS supportent tous deux cette pratique.\nFan-out : Chaque consumer reçoit le message et peut le traiter indépendamment des autres.\nLà encore AMQP et JMS supportent cette pratique.\nPour que le broker sache quand il faut enlever le message de la queue et éviter de l'enlever en cas de crash du consumer, le consumer qui a traité le message doit faire un acknowledgement. Sinon le message reste et devra être traité.\nCes crashs peuvent causer un traitement des messages dans un ordre différent de celui d'arrivée. Si on veut éviter ça, on peut faire une queue par consumer.\nLes brokers traditionnels se distinguent des BDD ou des batches par le fait que les données sont détruites une fois traitées. Mais on peut très bien combiner la faible latence de traitement des messages (streaming) avec de la persistance durable : on a alors les log-based message brokers.\nIl s'agit d'écrire les events dans un fichier de log, comme on le ferait pour les LSM-Tree, ou les write-ahead logs. Les consumers peuvent alors traiter le fichier séquentiellement, et une fois à la fin être notifiés à chaque nouveau message.\nPour pouvoir scaler avec ce modèle au-delà de ce que peut supporter la lecture d'un seul disque, on peut utiliser le partitionnement : les messages sont partitionnés sur différentes machines représentant des producers, et des consumers viennent traiter les messages sur chaque partition.\nAu sein de chaque partition, on peut avoir un identifiant séquentiel indiquant l'ordre. Par contre, ça ne marche pas à travers les partitions.\nGrâce au partitionnement, ce type de log-based brokers, malgré le fait d'écrire sur disque, arrivent à traiter plusieurs millions de messages par seconde.\nCe type de broker est implémenté par Apache Kafka, Amazon Kinesis Streams et Twitter's Distributed Log. Google Cloud Pub/Sub est architecturé de cette façon, mais expose une JMS-style API.\nLes log-based brokers supportent le fan-out messaging puisque les logs sont conservés et peuvent être lus un grand nombre de fois.\nPour ce qui est du load-balancing messaging, c'est mis en place à l'aide des partitions, qui sont assignés à des consumers spécifiques.\nIl y a des désavantages :\nOn n'a qu'un consumer par partition.\nLes messages lents d'une même partition vont ralentir les autres messages de cette partition.\nQuand utiliser les brokers classiques vs log-based :\nQuand le processing des messages peut être coûteux, et qu'on a envie de paralléliser message par message (et quand l'ordre des messages n'est pas très important), on peut utiliser les brokers de type JMS / AMQP.\nQuand en revanche les messages sont rapides à traiter, et que l'ordre importe, alors les log-based brokers sont pertinents.\nVu que l'ordre est respecté seulement au sein des partitions, on peut très bien choisir comme clé de partitionnement la chose dont on veut que les événement liés gardent le bon ordre. Par exemple l'id d'un utilisateur.\nÉtant donné que l'ordre est respecté au sein de chaque partition, on n'a plus besoin d'acknowledgement quand le traitement est fait pour chaque event. On sait que ce sera fait dans l'ordre et on peut regarder régulièrement le log offset de chaque consumer.\nSi un consumer échoue, un autre reprendra au dernier log offset connu. Et si des messages avaient été traités mais dont le log offset n'était pas connu, ils seront traités deux fois (il va falloir régler ce problème).\nA propos de l'espace disque :\nA force d'écrire des logs sur le DD, il finit par être plein, et il faut alors supprimer des données ou les bouger vers un espace d'archivage.\nCela veut donc dire que si on consumer est vraiment trop lent, il pourrait finir par ne plus avoir accès aux messages non lus qui ont été déplacés.\nIl faut quand même relativiser ça : un DD typique fait 6To, et en écrivant séquentiellement à la vitesse max on écrit en moyenne à 150 Mo/s. Ce qui fait 11 heures pour remplir le disque dur. Et sachant qu'on n'écrit pas en permanence à la vitesse max, en général des events de plusieurs jours vont pouvoir être stockés sur une même machine productrice.\nSi un consumer est trop en retard, on peut aussi lever une alerte pour qu'un être humain gère. Vu les délais, il aura normalement le temps de régler la situation.\nOn peut noter aussi que pour les log-based brokers, vu qu'ils écrivent toujours sur DD, le temps de traitement reste à peu près constant, alors que pour les brokers plus classiques, si on dépasse la RAM et qu'on doit écrire sur DD, les performances se dégradent.\nOn peut remarquer que les log-based brokers sont plus proches des batches que les brokers classiques. Les données anciennes étant conservées, on peut les rejouer à loisir pour faire des tâches dessus.\nLes streams et les bases de données :\nLes principes des streams peuvent aussi être utiles pour les BDD.\nPar exemple, le replication log envoyé par le leader n'est rien d'autre qu'un stream.\nOn peut aussi considérer que chaque opération d'écriture en BDD est un événement, et qu'on peut reconstruire la BDD à partir du log d'events déterministes.\nOn se retrouve souvent avec des copies des données sous différents formats pour différents usages (cache, data warehouse etc.). Mais comment garder ces données synchronisées ?\nUne solution est d'utiliser les batches. Mais c'est lent, et on n'aura pas de données à jour rapidement.\nUne autre solution serait d'écrire en même temps dans la BDD principale et dans ces autres copies. Mais dans des systèmes distribués il peut survenir des inconsistances entre ces copies.\nPour régler ce problème, on pourrait transformer les copies en suiveuses de la BDD principales comme avec le modèle leader / follower.\nMalheureusement pendant longtemps les logs des messages allant dans la BDD ont été considérés comme des API privées. Récemment on a un intérêt vers le fait de les exploiter comme des streams qu'on appelle change data capture (CDC).\nLa solution est d'utiliser un log-based broker pour transporter les events d'écriture de la BDD (leader) vers les datasets qui sont des followers (search index, data warehouse etc.).\nC'est utilisé par Databus de LinkedIn, Wormhole de Facebook et Sherpa de Yahoo.\nBottled Water le fait pour PostgreSQL en lisant son write-ahead log.\nMaxwell et Debezium le font pour MySQL.\nMongodriver le fait pour MongoDB.\nGoldenGate le fait pour Oracle.\nKafka Connect Framework offre des connecteurs CDC pour divers BDD.\nRethinkDB, Firebase, CouchDB, MongoDB et VoltDB permettent aussi d'avoir un mécanisme pour exporter le stream des données hors de la BDD.\nEn général, cette solution est utilisée dans un mode de réplication asynchrone.\nCertains outils permettent de commencer un dataset suiveur avec un snapshot initial des données, plutôt que de récupérer la totalité des logs pour reconstruire la BDD.\nCertains outils comme Apache Kafka permettent aussi de récupérer les logs compactés, au sens de la compaction des LSM-Tree : seules les logs représentant la dernière version des entrées sont gardées. Si une entrée est supprimée à un moment, toutes les logs précédentes de cette entrée peuvent être supprimées aussi par la compaction par exemple.\nEvent sourcing : c'est une idée développée par la communauté domain-driven design (DDD).\nCela consiste à stocker tous les changements d'état d'une application sous forme de logs de change events\nLa différence entre le change data capture de la BDD et l'event sourcing c'est que le change data capture permet d'ajouter / enlever / modifier des choses dans la BDD et d'en faire un log, alors que l'event sourcing décourage ou interdit la modification, mais consiste plutôt à accumuler des events qui représentent des choses qui se produisent plutôt que de simples changements d'état qui s'annuleraient entre eux.\nLa conséquence est qu'on ne peut pas vraiment faire de compaction pour les events de l'event sourcing, parce qu'ils ne s'annulent pas entre eux à proprement parler. Il faut garder ces events immuables.\nL'event sourcing est un modèle très puissant pour représenter clairement ce qui se passe dans l'application, et permet aussi d'avoir des facilités pour débugger.\nIl existe des BDD spéciales pour l'event sourcing comme Event Store, mais en réalité n'importe quelle BDD ou message broker serait adapté.\nL'event sourcing sépare bien les events des commands. Quand une requête arrive de l'utilisateur c'est d'abord une command. Elle doit être traitée et validée, et c'est seulement quand on est sûr qu'elle l'est qu'elle devient un event immuable. Elle est alors transmise à divers systèmes consommateurs et ne peut pas être supprimée, mais seulement changée par un autre event d'annulation par exemple.\nLes streams et les états vis à vis de l'immuabilité :\nOn peut voir la BDD comme étant un sous ensemble, ou une version cache la plus récente des données que sont les logs d'events. Avec le mécanisme de compaction des SSTables c'est encore plus évident puisqu'on a les logs, et on vient enlever ce qui est “inutile” pour obtenir la BDD qui est l'état le plus actuel des données.\nUn des avantages à avoir les logs des changements immuables comme source de vérité principale à partir de laquelle on peut construire diverses formes de dataset est que même si on fait une opération malheureuse qui corrompt les données, si c'est juste sous forme de log il suffira de revenir en arrière dans les logs et c'est réglé. Avec une vraie BDD si on a corrompu les données on risque de ne pas pouvoir défaire.\nOn peut dériver diverses formes de données à partir des logs :\nPar exemple, Druid ingère les données de Kafka, de même pour Pistachio qui utilise Kafka comme un commit log, et Kafka Connect peut exporter les données de Kafka vers diverses BDD ou indexes.\nStocker les données est facile si on n'a pas à se préoccuper de le faire dans un format qui permettra une lecture optimisée en fonction de notre contexte. On peut donc séparer l'écriture de la lecture, en créant de nouveaux dataset dérivés quand on a besoin des données pour faire quelque chose de spécifique.\nCette idée de séparer les données d'écriture et de lecture est connue sous le nom CQRS (Command Query Responsibility Segregation).\nDans cette approche la question de “faut-il vraiment dénormaliser ?” ne se pose plus vraiment : il est logique de dénormaliser pour optimiser en lecture, vu que de toute façon les données seront présentes sous une forme plus canonique dans la version écrite.\nAvantages et inconvénients de l'event sourcing et du change data capture :\nUn des inconvénients est que si l'écriture se fait de manière asynchrone pour gagner du temps (ce qui est souvent le cas), on risque de ne pas avoir la garantie de read after your writes par exemple. Pour remédier à ça on pourrait rendre la copie synchrone, ou utiliser des transactions distribuées, ou un total order broadcast.\nUn des avantages est que ça peut faciliter la concurrency control : à chaque fois qu'une requête a besoin de modifier plusieurs objets, on peut très bien écrire un event dans le log qui implique l'ensemble de ces objets. Et donc on aurait des opérations atomiques écrites en une fois.\nA propos de l'immuabilité :\nElle est utile si les données ne changent pas tant que ça, mais si elles changent beaucoup on risque de se retrouver avec des logs énormes pour pas beaucoup de données.\nOn a aussi des contraintes légales qui imposent parfois de supprimer certaines données.\nOn peut alors réécrire l'historique pour enlever certaines données. Datomic appelle ça l'excision.\nIl faut savoir aussi qu'étant donné les diverses copies de dataset, backups et autres, c'est assez difficile de complètement supprimer les données.\nTraitement des streams.\nOn peut faire 3 choses avec un stream :\nL'écrire en BDD ou autre forme de persistance.\nLe donner directement à l'utilisateur en lui affichant.\nLe modifier pour fabriquer un nouveau stream à travers un operator comme avec les batchs, dont le résultat ira à nouveau dans une persistance ou chez l'utilisateur.\nTout ceci est assez similaire à ce qui se passe avec les batchs.\nLa différence c'est que le stream ne se finit pas, et donc on ne peut pas faire de sort ou de jointures sort-merge comme avec les batchs.\nLa tolérance aux erreurs aussi n'est pas la même : on peut difficilement rejouer un stream qui tourne depuis des années comme on rejouerait un batch qui vient d'échouer.\nA propos des usages du streaming :\nOn l'utilise pour du monitoring quand on veut être averti de choses particulières, par exemple avec la détection de fraudes, le statut des machines d'une usine etc.\nLes complex event processing (CEP) permettent de déclarer des patterns à trouver (souvent avec du SQL), et créent des complex events à chaque fois que ça match, il s'agit de trouver une combinaison d'events.\nC'est implémenté dans Esper, IBM InfoSphere Streams, Apama, TIBCO StreamBase, SQLstream.\nLes stream analytics qui ressemblent aux CEP mais sont plus orientés vers le fait de trouver des résultats agrégés à partir des données streamées. Par exemple calculer une moyenne, une statistique.\nOn utilise souvent des fenêtres de données pour faire les calculs dessus.\nOn utilise parfois des algorithmes probabilistes comme les Bloom Filters pour savoir si un élément est dans un set et d'autres. Ces algorithmes produisent des résultats approximatifs mais utilisent moins de mémoire.\nParmi les outils on a Apache Storm, Spark Streaming, Flink, Concord, Samza et Kafka Streams. Et parmi les outils hostés on a Google Cloud Dataflow et Azure Stream Analytics.\nLes dataset dérivées des logs comme dans l'event sourcing peuvent être vus comme des materialized views, dans ce cas il faut prendre en compte l'ensemble des logs et pas juste une fenêtre.\nSamza et Kafka Streams font ça.\nOn peut faire aussi un peu pareil que les CEP mais en recherchant un seul event qui match un critère de recherche. Alors que d'habitude on doit indexer avant de faire une recherche, là il s'agit de rechercher en plein streaming.\nLa feature percolator d'Elasticsearch permet de faire ça.\nLa notion de temps dans la gestion des stream processing :\nAlors que dans les batch processing ce qui compte c'est éventuellement le timestamp des events analysés, et pas le temps pendant le quel le batch s'exécute (ce qui rend la réexécution du batch transparente d'ailleurs), dans le cadre du stream processing le temps pendant lequel le processing s'exécute peut être pris en compte, par exemple pour faire des fenêtres.\nAttention cependant aux lags : il est possible que lors du stream processing un event soit processé bien après avoir été émis. Et dans ce cas on peut se retrouver avec des events traités dans un ordre qui n'est pas le bon vis-à-vis de leur émission.\nQuand on stream avec des fenêtres de temps contenant des events pour y faire des opérations, on ne peut jamais être sûr que tous les events de la fenêtre sont arrivés : ils ont peut être été retardés (qu'on appelle straggler)\nDans ce cas, soit on dit tant pis et on annule les events retardataires, en levant éventuellement une alerte s'il y en a trop.\nSoit on publie plus tard un correctif avec les events retardataires.\nQuand on veut prendre en compte le temps, le temps de la création de l'event est souvent plus précis (par exemple un event peut être créé offline par un mobile, et envoyé seulement quand il est connecté), mais aussi moins fiable vu que la machine n'est pas sous notre contrôle contrairement au serveur.\nUne des solutions est de relever (1) le temps de l'event indiqué par le client, (2) le temps de l'envoi de l'event indiqué par le client, et (3) le temps de la réception de l'event par le serveur. De cette manière on peut comparer les horloges du client et du serveur vu que le (2) et le (3) doivent être très proches.\nIl y a plusieurs types de fenêtres temporelles :\nTumbling window : Les fenêtres sont fixes, et chaque event appartient à une fenêtre.\nHopping window : Les fenêtres font la même taille mais se chevauchent, certains events qui sont entre les deux sont dans les deux fenêtres.\nSliding window : Les fenêtres font la même taille mais se déplacent dans le temps, et donc les events les plus anciens sont exclus au fur et à mesure, remplacés par des events plus récents.\nSession window : Les fenêtres n'ont pas la même taille, elles regroupent des events proches dans le temps où un même utilisateur a été actif.\nLes streams étant une généralisation des batchs, on a ici le même besoin des jointures.\nOn peut dénombrer 3 types :\nLe stream-stream join (window join) consiste à joindre deux ensemble streams d'events ensemble. Par exemple dans le cadre d'une recherche, joindre les events de recherches faites aux events clics qui s'en sont suivis (ou à l'absence de clics après timeout).\nLe stream-table join (stream enrichement) consiste à “enrichir” les events issus d'un stream avec le contenu d'une BDD. Par exemple les actions d'un utilisateur enrichis (complétés ou triés) avec des infos issus de son profil.\nPour ce faire il nous faut une copie de la BDD sur le disque local de préférence, et si suffisamment petit on peut même la mettre en RAM. C'est très similaire aux Map-side joins des batchs.\nVu que les données de la table risquent d'être mises à jour, on peut utiliser le change data capture pour récolter les mises à jour de la table régulièrement.\nLe table-table join (materialized view maintenance) consiste à matérialiser une requête de jointure entre deux tables, à chaque fois qu'il y a un changement dans ces deux tables qui risque d'affecter le résultat de cette jointure.\nOn peut prendre l'exemple de twitter qui, en même temps qu'il stocke les tweets et followers, construit une timeline en cache au fur et à mesure.\nOn remarque que dans la plupart des cas, le temps est important, et que deux événements, ou un événement et une mise à jour en BDD pourraient arriver avant ou après l'autre (du fait du partitionnement). Ceci rend la jointure non déterministe (si on la refait on risque d'avoir un résultat différent).\nDans les data warehouses ce problème s'appelle _slowly changing dimension (SCD) _et la solution à ça peut être d'ajouter un identifiant qui est changé à chaque event. Mais la conséquence c'est qu'on ne peut plus faire de compaction.\nA propos des fautes dans le cadre des streams :\nL'avantage avec les batchs c'était le fait de pouvoir réexécuter en cas d'erreur, et d'avoir au final le job exécuté comme s'il l'avait été une seule fois.\nUne des solutions est le microbatching : on fait des petites fenêtres de données (souvent d'1 seconde) et on les traite comme des batchs.\nSpark Streaming fait ça.\nUne variante consiste à faire des checkpoints réguliers sur DD, et en cas de crash on recommence à partir du checkpoint.\nFlink fait ça.\nAttention cependant au moment où on fait autre chose avec ces données, comme écrire en BDD ou envoyer un email. Dans ce cas, il s'agit de side effects qui pourront être réexécutés en cas de réexécution du microbatch.\nPour régler ce problème, il faut tout préparer, et exécuter tout ce qui est validation des opérations, side-effects et autres en une seule fois et de manière atomique.\nC'est un peu de la même manière que le 2PC (two phase commit) des transactions distribuées.\nC'est utilisé par Google Cloud Dataflow, VoltDB et Apache Kafka.\nUne autre solution pour ce problème est de créer de l'idempotence, c'est-à-dire faire en sorte qu'une chose faite plusieurs fois donne le même résultat.\nOn peut le faire par exemple en retenant un offset qui fera en sorte de ne rien faire si on tente de refaire l'opération.\nAttention au fait que cela implique qu'il faut rejouer les messages dans le même ordre (un broker log-based permet ça), de manière déterministe, et sans concurrence.\nOn peut aussi vouloir que des states (par exemple compteurs, moyennes etc.) soient reconstruites en cas de faute.\nDans certains cas ça peut être fait à partir des events, par exemple parce qu'il s'agit d'un état qui porte sur peu d'entre eux.\nSinon une solution peut être de les sauvegarder régulièrement quelque part pour aller les chercher en cas de besoin.\nFlink capture régulièrement ces valeurs et les écrit sur du HDFS.\nSamza et Kafka Streams répliquent les changements des states vers un stockage persistant avec compaction.\nVoltDB réplique les states en faisant le processing des messages sur plusieurs nœuds.\nIl faut voir que la sauvegarde en local avec accès au disque ou la sauvegarde via le réseau peuvent chacun être plus ou moins performants en fonction des cas.","12---the-future-of-data-systems#12 - The Future of Data Systems":"Chaque outil a ses avantages et inconvénients, et il n'y a pas d'outils parfaits.\nCertaines personnes disent que tel ou tel type d'outil n'a aucune utilité, mais ça reflète surtout le fait qu'eux ne l'utilisent pas, et qu'ils ne voient pas plus loin que le bout de leur nez.\nIl convient souvent de combiner plusieurs outils pour plusieurs usages :\nParmi ceux-ci on peut trouver :\nUne BDD relationnelle pour la persistance de données structurées. (ex : PostgreSQL)\nUn index de recherche pour une recherche performante, mais qui est moins bon sur la persistance des données (ex : Elasticsearch)\nUn système d'analyse du type data warehouse ou batch / stream processing.\nParmi les batchs / streams on pourrait vouloir alimenter un système de machine learning, de classification, de ranking, de recommandations, de notification basée sur le changement de données.\nUn cache ou des données dénormalisées issues des données initiales.\nPar exemple, on peut avoir une BDD et un search index, avec les données écrites d'abord dans la BDD, puis propagées dans le search index via change data capture (CDC).\nSi on décide qu'on veut écrire à la fois dans la BDD, et dans le search index, alors on risque d'avoir des latences qui causent des différences d'ordre d'écriture entre les deux.\nUne solution à ça c'est d'utiliser un système d'entonnoir qui force l'ordre, dans l'idée d'un total order broadcast.\nQue choisir entre les données dérivées (CDC, event sourcing) et les transactions distribuées (2PC) pour faire communiquer plusieurs outils entre eux ?\nSelon l'auteur, XA, le protocole qui permet de faire communiquer les outils via les transactions distribuées a une mauvaise tolérance aux fautes et une faible performance. Et en l'absence d'un autre protocole aussi répandu (ce qui ne risque pas d'arriver rapidement), il est plus pertinent d'opter pour les datasets dérivés.\nCependant, les transactions distribuées supportent la linearizability et donc permettent par exemple le “read your own writes”, alors que les données dérivées sont en général asynchrones et donc n'apportent pas ces garanties. Cette eventual consistency est à mettre dans la balance.\nPlus tard on parlera d'un moyen de contourner ce problème.\nAttention au fait de vouloir du total ordering :\nPour avoir du total ordering il faut que les données passent par une seule machine (par exemple single leader). Sinon on peut créer des partitions mais on aura des ambiguïtés entre partitions.\nDans le cas de plusieurs datacenters on a en général besoin de 2 leaders => on n'aura donc pas de total ordering.\nQuand on fait du micro-service, il est courant de déployer le code sur des machines avec chacune son stockage et sans que ce stockage soit donc partagé => on se retrouve là aussi donc à ne pas respecter le total ordering.\nPour être clair : le total ordering implique le total order broadcast, qui est équivalent au consensus. Et la plupart des algorithmes de consensus ne sont pas faits pour marcher si le throughput dépasse les données que peut gérer un seul serveur. Le fait de pouvoir gérer un tel throughput avec des datacenters distribués dans le monde est un sujet de recherche.\nA propos de l'ordre causal :\nPour les événements qui touchent le même objet, celui-ci étant sur la même partition on peut ordonner ces actions et respecter la causalité.\nEn revanche, pour les événements qui portent sur plusieurs objets il n'y a pas de solution facile. Quelques pistes :\nLes clocks logiques peuvent aider.\nSi on log des events pendant pour les lectures, alors les autres évents peuvent les utiliser pour identifier le moment où un événement ne s'était pas produit et créer un ordre comme ça.\nLes structures de résolution automatique de conflit (fusion des objets par exemple) peuvent aussi aider.\nA propos des batches et streams :\nUne des raisons pour lesquelles il est pratique d'avoir des dataset dérivés par batch/stream plutôt que transactions distribuées est qu'on peut fauter quelque part et ne pas tout annuler, mais seulement retenter la construction du batch/stream.\nUn des avantages des batchs/streams c'est qu'avec les datasets dérivés, on peut changer le schéma de nos données pour un dataset, et reprocesser le tout, ou continuer pour le stream. On n'a pas à faire d'opérations destructives pour faire évoluer notre code.\nOn peut d'ailleurs faire les changements graduellement, blocs de données par bloc de données.\nLa lambda architecture consiste à avoir un batch et un stream qui vont processer la même chose pour avoir la donnée immédiatement, mais avoir un process mieux tolérant aux erreurs plus tard. Le stream fait une approximation, alors que le batch fait un calcul plus précis régulièrement.\nIl y a cependant plusieurs problèmes :\nMaintenir la logique dans le batch et le stream est difficile.\nIl faut merger les deux régulièrement, et ça peut être difficile si les opérations appliquées sont difficiles.\nReprocesser toutes les données avec le batch est très coûteux, donc on peut à la place reprocesser une seule heure de données et y ajouter le stream. Cependant, rendre le batch incrémental le fragilise.\nMais plus récemment on a d'autres solutions pour rendre la lambda architecture plus utilisable grâce à certaines features qui sont de plus en plus supportés par les outils.\nA propos de BDD :\nLes BDD et les filesystem font la même tâche.\nMais ils ont certaines différences : les filesystem Unix offrent une API bas niveau pour traiter avec les fichiers, alors que les BDD offrent une API plus haut niveau avec SQL.\nD'une certaine manière certaines BDD NoSQL tentent d'ajouter la philosophie Unix aux BDD.\nLes BDD et les batchs / streams ont des choses en commun :\nPar exemple, la construction de search indexs dans les batchs/streams sont un peu la même chose que la construction d'index secondaires.\nEt du coup on en arrive à la conclusion qu'en fait les batchs/streams ne sont que la continuation d'une même base de données transformée pour l'adapter aux besoins, distribuée sur d'autres machines et administrée éventuellement par d'autres équipes.\nL'auteur spécule que les données seront organisées en deux grands axes, qui sont en fait deux faces de la même pièce :\nFederated databases (unifying reads) : il s'agit de fournir une API de lecture pour accéder à toutes les données existantes du système, tout en laissant les applications spécialisées accéder directement aux datasets spécifiques dont elles ont besoin. L'idée est donc de connecter toutes les données ensemble.\nPostgreSQL et son foreign data wrapper permet de faire ça.\nUnbundled databases (unifying writes) : il s'agit de traiter les écritures pour qu'on puisse écrire dans n'importe quelle version des données, et qu'elles soient quand même synchronisées avec le reste. Alors que les BDD supportent les indexes secondaires, ici on a différents datasets interconnectés et donc on doit en quelque sorte maintenir nos indexes à la main.\nOn est en plein dans la tradition Unix où des petits outils font une chose bien, et peuvent s'interconnecter.\nAlors que la fédération des données n'est pas trop difficile, maintenir les données synchronisées est plus compliqué à faire.\nPour accomplir ces données synchronisées on recourt traditionnellement aux transactions distribuées, mais selon l'auteur c'est la mauvaise approche. L'approche sous forme de données dérivées depuis un event log asynchrone, et l'utilisation de l'idempotence est bien plus solide.\nUne des raisons déjà évoquée est que faire communiquer des systèmes de données hétérogènes via un mauvais protocole marche moins bien que via une meilleure abstraction avec des logs d'event et de l'idempotence.\nLe gros plus de l'approche avec les event logs est le couplage faible entre les composants :\nLa nature asynchrone de cette approche permet de tolérer bien mieux les fautes (par exemple, un consommateur fautif va rattraper son retard plus tard via les messages accumulés) alors qu'avec les transactions distribuées synchrones par nature, les fautes ont tendance à être amplifiées.\nAu niveau des équipes, chacune peut se spécialiser dans un type de dataset pour un usage, et le faire indépendamment des autres.\nEntre utiliser un système de BDD intégré et un système composé de datasets dérivés, le choix des datasets dérivés n'est pas forcément systématique. Ça peut être une forme d'optimisation prématurée, et d'ailleurs si un système de BDD répond à nos besoins, autant l'utiliser lui seul.\nCe qui manque dans l'histoire c'est une manière simple et haut niveau d'interconnecter ces systèmes, par exemple “declare mysql | elasticsearch” comme équivalent de “CREATE INDEX” dans une BDD.\nIl y a des recherches à ce sujet mais pour le moment rien de tel, on doit faire beaucoup de choses à la main.\nPour continuer sur l'idée de l'unbundling databases, et des applications autour du dataflow :\nOn peut trouver des ressemblances avec le concept d'unbundling des BDD et des langages de dataflow comme Oz, Juttle, les langages fonctionnels réactifs comme Erlang, et les langages de programmation logique comme Bloom.\nL'idée de l'unbundling est aussi présente dans les tableurs quand ils mettent à jour toute une colonne dès qu'une donnée est écrite. Il faut juste faire la même chose mais dans un contexte distribué, et avec des technologies disparates.\nOn a différentes formes de données dérivées, mais en gros dès que la dérivation est spécifique à notre métier, il faut écrire du code applicatif pour gérer ce dataset-là. Et les BDD ont en général du mal à permettre ça. Il y a bien les triggers / stored procedures, mais ça reste une feature secondaire.\nIl est devenu un pattern courant et une bonne pratique de séparer le code applicatif du state (ie. la persistance), en ayant des serveurs stateless qui accèdent à une BDD commune.\nLes développeurs fonctionnels disent qu'ils sont pour “la séparation de l'église et de l'état”.\nCependant, de même que dans la plupart des langages il n'y a pas de système de souscription (sauf à le faire avec le pattern observer), dans les BDD il n'y en a pas non plus sauf récemment avec les CDC par exemple.\nVu qu'on veut sortir la logique de mise à jour automatique par exemple d'un index dans la BDD hors de celle-ci, on peut partir du principe que la donnée n'est pas une chose passive utilisée par l'application, mais que les changements dans un dataset peuvent déclencher du code applicatif pour créer un autre dataset.\nA cet effet, on peut utiliser des log message brokers (et non pas des message brokers traditionnels qui servent à exécuter des jobs de manière asynchrone).\nL'ordre des messages est souvent important pour maintenir des datasets dérivés.\nOn doit être tolérant aux fautes et ne pas perdre de messages, sous peine d'inconsistance.\nLes message brokers permettent au code applicatif de s'exécuter sous forme d'operators, ce qui est pratique.\nLe stream processing et les services :\nL'architecture sous forme de services est plutôt à la mode, son avantage principal est de permettre une forme de scalabilité dans l'entreprise, en permettant à plusieurs équipes de déployer séparément.\nIl y a cependant une différence entre les services qui vont envoyer un message pour recevoir une réponse du service qui a les données, et le stream processing qui va construire et maintenir à jour un dataset local à la machine qui a le code applicatif, qui n'aura plus de requête réseau à faire => la méthode avec le stream processing est donc plus performante.\nLecture des données dérivées :\nLes données dérivées sont construites et mises à jour en observant la donnée initiale et la faisant passer à travers des operators, tout ceci pendant la phase d'écriture. On a ensuite du code exécuté qui lit ces données dérivées et qui répond à une requête d'un client, pendant la phase de lecture donc.\nCe point de rencontre représente en quelque sorte le point d'équilibre entre la quantité de travail qu'on souhaite faire à l'écriture, et la quantité de travail qu'on souhaite faire à la lecture.\nOn peut déplacer ce point de rencontre pour faire plus de travail à l'écriture, ou plus à la lecture.\nPar exemple pour la recherche, on peut très bien ne pas créer de search index, et tout faire à la lecture.\nOu alors on peut non seulement créer un search index à l'écriture, mais aussi créer tous les résultats de recherche possibles, comme ça à la lecture on n'aura plus qu'à lire un cache (aussi appelé materialized view).\nSi créer l'ensemble des résultats de recherche serait sans doute excessif, on peut très bien imaginer mettre en cache les résultats des recherches les plus fréquentes.\nOn voit qu'on retrouve aussi notre exemple de twitter qui avait choisi de mettre en cache toutes les timelines, sauf pour les célébrités où il faisait la recherche en BDD.\nAutre exemple de lecture de données dérivées : les applications web sur mobile qui gagnent de plus de capacité d'autonomie, y compris offline, peuvent stocker une forme de dataset dérivé au sein même du mobile, permettant au code sur le client d'en faire quelque chose offline.\nLes outils frontend comme le langage Elm et le framework React / Redux permettent de souscrire à des events de l'utilisateur, en mode event sourcing.\nIl serait tout à fait naturel de faire la même chose dans la relation client / serveur : permettre au client de faire une requête, puis de réceptionner non pas une réponse mais un stream de messages réguliers.\nLes log message brokers passent en général leur contenu à une forme ou une autre de BDD spécialisée, mais il y a aussi une certaine persistance des events eux-mêmes (les logs) dans le message broker. En général seuls les events d'écriture y sont consignés, ce qui est raisonnable mais n'est pas la seule manière de faire possible.\nIl est possible qu'en fonction du besoin applicatif, on ait aussi intérêt à consigner les events de lecture. Ça permet notamment de faire un stream-table join entre les lectures et les données existantes.\nC'est utile en particulier dans le cas où on a plusieurs partitions qu'il faut traverser pour obtenir notre résultat.\nLa feature de distributed RPC de Storm implémente cette fonctionnalité.\nÇa prend bien sûr plus de place donc il faut y réfléchir.\nUn des avantages est que ça permet aussi de régler le problème de causalité vis-à-vis d'écritures sur des objets différents.\nA la recherche des données correctes :\nOn a tendance à avoir un mouvement vers une plus grande performance, availability et scalability, avec une consistency qui est parfois délaissée.\nExemple : la réplication leaderless.\nOn peut aussi noter le rapport hasardeux à l'isolation et l'implémentation de faibles niveaux d'isolation dans beaucoup de BDD.\nOn peut répondre à certaines problématiques de corruption de données à l'aide de la serializability et des atomic commits, mais c'est vraiment coûteux et ça marche surtout sur un seul datacenter, avec des limites de scalabilité.\nIl y a aussi les transactions qui permettent de régler certains problèmes, mais ce n'est pas la seule solution.\nN'oublions pas non plus les erreurs et bugs applicatifs qui peuvent endommager les données de manière définitive, même en présence de serializability…\nPour lutter contre ces problèmes voici quelques solutions :\nL'immutabilité des données (du genre event sourcing et autres) permet d'être sûr que même en écrivant des données corrompues, on pourra toujours les annuler pour retrouver l'état d'avant.\nRendre les opérations idempotentes pour qu'elles ne puissent être exécutées qu'une seule fois au plus est une forme de protection contre la corruption de données.\nDe manière générale, il est intéressant d'implémenter des mécanismes end-to-end qui vont suivre la requête de bout en bout. Par exemple TCP fournit ce genre de garanties à son niveau, mais une connexion TCP peut sauter et on peut en établir une autre pour refaire la même transaction, on a alors besoin de quelque chose qui suit notre transaction.\nMalheureusement, implémenter de tels mécanismes end-to-end au niveau applicatif n'est pas simple. Pour l'auteur, il faudrait qu'on trouve la bonne abstraction pour rendre ça facile, mais il y a de la recherche à faire.\nAppliquer des contraintes :\nLa contrainte de uniqueness dans un système distribué nécessite le consensus, et donc une forme de fonctionnement synchrone. Si les writes se faisaient de manière asynchrone, alors on ne saurait pas immédiatement si on peut écrire en respectant cette contrainte ou pas, et on aurait le conflit plus tard.\nUn bon moyen pour garantir cette contrainte est de partitionner en fonction de la clé qui doit avoir la contrainte d'unicité. Mais là aussi bien sûr on ne pourra pas écrire dans la BDD de manière asynchrone.\nPour les contraintes au sein des log-based message brokers il s'agit aussi de faire en sorte que les requêtes avec possibilité de conflit soient dans la même partition, et de vérifier séquentiellement, message par message, que la requête respecte bien la contrainte vis-à-vis de la BDD locale.\nDans le cas où les entrées qui sont l'objet de contraintes sont localisées dans des partitions différentes ça se complique un peu.\nOn peut utiliser un atomic commit (par exemple 2PC).\nMais on peut aussi faire sans (exemple de débit / crédit d'un compte) :\nOn attribue un id à la requête.\nLe stream processor crée 2 messages : un pour décrémenter le compte qui a un débit, et un autre pour incrémenter l'autre compte (qui sont chacun sur leur partition).\nLes processors suivants consomment les messages, appliquent le débit ou le crédit, et dédupliquent en fonction de l'id de la transaction initiale.\nVu qu'on est dans un log based broker avec l'ordre des messages préservés et de la persistance, en cas de crash de l'un des consommateurs, il redémarre et réapplique les messages non processés dans l'ordre prévu.\nNous avons donc réussi à réaliser une transaction multi-partition sans utiliser de protocole de type atomic commit, en cassant la transaction en plusieurs morceaux s'exécutant chacun sur leur partition, et en ayant un mécanisme end-to-end (ici l'id) assurant l'intégrité du tout (le fait qu'un bout ne sera pas exécuté 2 fois).\nA propos de l'intégrité et de la relation au temps :\nLe terme consistency englobe en réalité deux enjeux :\nLa timeliness qui consiste à s'assurer que l'observateur voit une donnée à jour. C'est tout l'objet du terme eventual consistency quand la timeliness n'est pas respectée.\nL'integrity qui consiste à préserver les données d'une corruption permanente des données, y compris dérivées. Pour la régler il faudrait réparer et non pas juste attendre ou réessayer.\nSi le non-respect de la timeliness est embêtant, le non-respect de l'integrity peut être catastrophique.\nAlors que dans les transactions ACID la timeliness et l'integrity sont confondues, on vient de voir que dans le stream processing on peut les décorréler, et arriver à garantir l'integrity tout en ayant un fonctionnement asynchrone et donc ne garantissant pas la timeliness. Et le tout sans utiliser les transactions distribuées coûteuses;\nSelon l'auteur, cette technique est particulièrement prometteuse.\nOn peut aussi se demander si toutes les applications ont vraiment besoin d'un respect intransigeant de la timeliness, et donc d'un respect de la linearizability (dès qu'une écriture est faite, elle impacte les lecteurs) :\nOn peut très bien faire une compensating transaction dans le cas où on a accepté une transaction côté client mais qu'il se révèle qu'elle ne respecte pas les contraintes.\nD'ailleurs un processus d'excuse et de compensation peut être pertinent dans de nombreux cas, par exemple pour la réservation, souvent on propose plus de places que disponibles en partant du principe qu'il y aura des désistements. Et dans le cas où on a mal prévu, il faut pouvoir avoir un processus de compensation.\nOn peut créer un système qui pour l'essentiel évite la coordination :\n1 - on préserve l'intégrité des données dans les systèmes dérivés sans atomic commit, linearizability, ou coordination synchrone entre partitions.\n2 - la plupart des applications peuvent se passer de contraintes temporelles fortes pour la timeliness.\nSelon l'auteur, ce type de système sans coordination a beaucoup d'avantages. On peut très bien utiliser la coordination synchrone pour certaines opérations importantes qui ne permettent pas de retour en arrière, et garder le reste sans cette coordination.\nFinalement on peut voir la chose de cette manière : avoir de fortes garanties synchrones du type transactions distribuées réduit le nombre d'excuses qu'il faudra faire pour les données inconsistantes présentées, mais ne pas les utiliser réduit le nombre d'excuses qu'il faudra faire pour toutes les indisponibilités du système dues à la faible performance induite par la coordination.\nVis-à-vis des erreurs matérielles et logicielles :\nIl y a des corruptions probables contre lesquelles notre système model prévoit des parades, et des corruptions contre lesquelles non, comme par exemple faire confiance aux opérations du CPU. Pourtant tout peut arriver avec plus ou moins de probabilité.\nIl ne faut pas oublier non plus que les BDD ne sont que des logiciels qui peuvent avoir des bugs, et pour nos codes applicatifs c'est encore pire.\nLa corruption des données finit par arriver qu'on le veuille ou non. Il faut une forme d'auto-auditabilité. Il faut vérifier régulièrement que nos données sont bien là et intègres, de même que nos backups.\nL'approche représentée par l'event sourcing permet d'auditer plus facilement les données.\nEt si on a bien fait la séparation entre les données sources et dérivées c'est encore plus clair.\nOn peut faire un checksum sur le log d'events pour le vérifier, et on peut rejouer les batchs pour recréer des données dérivées propres.\nUne bonne pratique dans la vérification des données est de le faire sur des flows end-to-end. Cela permet d'inclure tout le hardware et le software dans ce qui est vraiment vérifié.\nLes techniques cryptographiques de vérification de l'intégrité introduites par la blockchain est un mécanisme très intéressant pour l'avenir de l'intégrité des données."}},"/books/learning-domain-driven-design":{"title":"Learning Domain Driven Design: Aligning Software Architecture and Business Strategy","data":{"introduction#Introduction":"Le problème principal qui met en échec la plupart des projets de dev c'est la communication. Et c'est le cœur de ce qu'est le DDD.\nLe but du DDD c'est d'aligner le Design logiciel avec le Domaine business.\nLe DDD se décompose en 2 parties :\nLe design stratégique : créer une compréhension commune du domaine, et prendre des décisions haut niveau sur le projet.\nC'est les questions “Quel logiciel on crée ?” et “Pourquoi on le crée ?”\nLe design tactique : écrire du code qui épouse le domaine.\nC'est la question “Comment on crée chaque partie du logiciel ?”","part-i--strategic-design#Part I : Strategic Design":"","1---analyzing-business-domains#1 - Analyzing Business Domains":"Pour développer un bon logiciel qui réponde au besoin de notre entreprise, il faut que nous les devs connaissions la stratégie du business : différencier les parties importantes des parties moins importantes pour adapter nos techniques.\nLe business domain est le domaine d'activité principal de l'entreprise.\nExemple : FedEx fait de la livraison.\nPour une grande entreprise qui fait plusieurs choses très différentes (comme Google ou Amazon), on peut parler de plusieurs domains.\nUn subdomain est une activité que l'entreprise fait pour mener à bien son business.\nExemple : Starbucks fait des cafés, mais il doit aussi recruter, gérer la tréso etc.\nIl existe 3 types de subdomains :\nLes core subdomains sont les seules activités qui donnent à l'entreprise un avantage concurrentiel par rapport à ses concurrents. Soit par une innovation produit, soit par une optimisation qui permet de produire à moindre coût.\nC'est une activité par nature complexe étant donné qu'il faut qu'elle ne soit pas facilement reproductible par les concurrents.\nLe statut de “core” est aussi par nature temporaire : dès que les concurrents rattrappent ce n'est plus core.\nA noter aussi qu'un core subdomain peut très bien ne pas résider dans du logiciel. Par ex une bijouterie va vendre mieux que les concurrents grâce au style donné par les artisans bijoutiers (qui donne l'avantage compétitif et qui est donc le core subdomain), la boutique en ligne dans ce cas est un generic subdomain.\nLes generic subdomains sont des problèmes communs déjà résolus et largement disponibles et utilisés par les concurrents (soit en open source, soit sous forme de service payant).\nIls sont complexes comme les core subdomains, mais ne donnent juste pas d'avantage compétitif.\nExemple : un mécanisme d'authentification est complexe, mais des solutions open source et payantes existent, et personne ne recode son système à soi.\nLes supporting subdomains sont des activités simples mais nécessaires et dont on n'a pas de solution générique à réutiliser (ou alors ça coûte moins cher de le faire soi-même) : c'est du ETL (extract, load, transform), ou encore CRUD.\nVu la simplicité, ils ne peuvent pas procurer d'avantage compétitif.\nVu qu'il n'y a pas d'avantage compétitif, on préfère appliquer l'effort sur les core subdomains qui apporteront plus de valeur business.\nCôté stratégie :\nLes core subdomains doivent être développés au sein de l'entreprise, par les développeurs les plus expérimentés, et en appliquant le maximum de qualité et les techniques d'ingénierie les plus avancées.\nOn ne peut pas les acheter sinon on perd la notion d'avantage compétitif, et il ne serait pas très malin de les sous-traiter.\nLes generic subdomains étant difficiles mais déjà résolus, il est plus rentable de les acheter (ou de faire appel aux services d'un consultant spécialisé), ou d'utiliser une solution open source.\nLes supporting subdomains sont simples et changent peu, donc ils peuvent être implémentés avec moins de qualité, ou moins de techniques sophistiquées (design patterns, techniques d'architecture etc.). Un simple framework rapide suffit.\nOn peut laisser les débutants se charger de ça, ou alors on peut le sous-traiter.\nPour trouver les subdomains, il va falloir faire l'analyse nous-mêmes.\nOn peut déjà partir des départements qui composent l'entreprise, mais ça nous donne des subdomains grossiers.\nOn peut alors “distiller” les subdomains en plus petits subdomains : on prend un département et on liste les sous-activités, puis on analyse pour chacune d'entre elles si elles sont core, generic ou support.\nOn peut distiller au maximum jusqu'à arriver à “un subdomain comme un ensemble cohérent de cas d'usages” : un même acteur qui fait plusieurs tâches précises.\nIl faut faire la distillation maximale pour les subdomains core, pour pouvoir éliminer les petits bouts génériques ou support à l'intérieur et se concentrer uniquement sur ce qui a le plus de valeur.\nPour les autres on s'arrête de distiller à partir du moment où une distillation donne des activités du même type (generic ou support), aller au-delà ne nous permettra de toute façon pas de prendre des décisions plus stratégiques.\nLes devs vont devoir collaborer avec les domain experts, mais qui sont-ils ?\nCe ne sont pas les analystes qui recueillent le besoin, ni les ingénieurs qui créent le système. Ces derniers transforment le modèle mental des domain experts pour en faire du logiciel.\nCe sont soit ceux qui arrivent avec les besoins (qui sont là depuis le début, qui ont créé l'activité etc.), soit les utilisateurs finaux dont on résout le problème.\nA noter que les domain experts peuvent très bien n'être experts que d'un sous-domaine seulement.","2---discovering-domain-knowledge#2 - Discovering Domain Knowledge":"Généralement, les gens du business (les domain experts) communiquent les besoins à des intermédiaires (system/business analysts, product owners, project managers), qui vont ensuite communiquer ça aux ingénieurs logiciel qui créent le logiciel.\ndomain experts -> gens au milieu -> software engineers\nOn assiste aussi à une ou plusieurs transformations :\ndomain knowledge -> analysis model -> software design model\nLe DDD propose d'arrêter les transformations, et que tous les acteurs utilisent le même langage pour se parler : l'Ubiquitous Language.\nIl doit pouvoir être compris par les domain experts, donc il va se baser sur les termes qu'ils utilisent déjà.\nChaque terme doit être précis et sans ambiguïté, et il ne doit pas y avoir de synonymes.\nLa raison est que si dans les conversations courantes on arrive à se comprendre même avec des ambiguïtés en fonction du contexte, dans le cadre du logiciel ça marche moins bien.\nLe but de l'ubiquitous language est d'être un modèle du business domain, qui va représenter le modèle mental des domain experts.\nUn modèle n'a pas à représenter toute la réalité mais seulement une partie de manière à être utile. Et donc ici aussi il ne s'agit pas de représenter tout le business domain, ni de faire de tout le monde un domain expert.\nCôté outils :\nIl faut un glossaire sous forme de wiki, que tous font évoluer au fur et à mesure. On y met les termes de l'ubiquitous language.\nPour la logique il faut d'autres outils comme des description de cas d'usage, ou des tests Gherkin.\nExemple Gherkin :\nScenario: Notifier l'agent d'un nouveau cas\n Given: Jules qui crée un nouveau cas \"Nouveau cas\"\n When: Le ticket est assigné à M. Wolf\n Then: L'agent reçoit une notification\nCeci dit, le plus important est l'usage quotidien de l'ubiquitous language par les acteurs. Les outils ne sont qu'un plus.\nPour que la compréhension du business domain soit bonne, il faut une communication régulière entre les domain experts et ceux qui sont en charge de réaliser le logiciel.\nCette communication se fait bien sûr dans l'ubiquitous language.\nÇa implique de poser beaucoup de questions aux domain experts.\nA force, on va se rendre compte qu'on pourra aider les domain experts à mieux comprendre leur champ d'expertise sur certains points, par exemple en pensant à des edge cases et pas seulement aux “happy paths”.\nOn peut alors aller vers une co-construction de la compréhension du domaine (même si les experts en sauront toujours plus).\nA propos de la langue de l'ubiquitous language, le conseil de Vlad est d'utiliser l'anglais au moins pour les noms d'entités du business domain.","3---managing-domain-complexity#3 - Managing Domain Complexity":"On se rend parfois compte qu'un même mot est utilisé par plusieurs domain experts avec une signification différente.\nExemple : le mot lead peut désigner un simple prospect intéressé dans le département marketing, alors que ça peut désigner une entité plus complexe avec des histoires de process de vente dans le département sales.\nEn général on a naturellement tendance à agrandir notre modèle pour que chaque version du mot puisse être représentée, mais ça finit par donner un modèle très complexe et difficile à maintenir.\nLa solution DDD est de créer deux bounded contexts qui auront chacun leur ubiquitous language.\nUn bounded context doit avoir des limites (boundaries).\nUne manière de les trouver est de repérer les incohérences de terminologies utilisées par les domain experts : un bounded context ne pourra pas être plus grand que ce que ces incohérences permettent.\nMais on peut diviser ces ensembles en bounded contexts plus petits. Il s'agit là d'une décision stratégique, qui dépend du contexte.\nAvoir des bounded context trop grands impliquera une plus grande complexité du modèle, alors que s'ils sont trop petits on aura un plus grand effort d'intégration à faire.\nLes raisons qui peuvent pousser à découper en bounded contexts plus petits peuvent être :\nd'ordre organisationnels : par exemple une nouvelle équipe qui va prendre en charge un morceau du logiciel qui va se développer sur un rythme différent du reste.\nd'ordre non fonctionnels : par exemple le fait de devoir scaler certaines parties du logiciel indépendamment.\nDe manière générale, on va essayer de trouver des fonctionnalités cohérentes qui opèrent sur un même jeu de données, et les laisser dans un même bounded context.\nLa différence entre subdomains et bounded contexts c'est que les subdomains sont là de fait et ressortent avec l'analyse. Ils sont de la responsabilité du business. Alors que les bounded contexts sont le résultat d'un choix stratégique, et sont de la responsabilité des ingénieurs logiciel.\nChaque bounded context a une séparation physique : il devra être implémenté avec sa codebase, en tant que projet autonome, et éventuellement service autonome.\nDans le cas où il s'agit d'un service autonome, on peut aussi choisir le langage, la stack, indépendants des autres.\nUn bounded context ne doit être géré que par une seule équipe de dev.\nUne équipe peut par contre en avoir plusieurs en charge.\nSi on se tourne vers la vie de tous les jours, on peut remarquer plein d'exemples où des mots ont des significations différentes selon les contextes, et où un modèle différent serait utile pour chaque :\nUn carton rectangulaire qu'on met au sol permet de représenter un réfrigérateur pour savoir s'il va bien s'insérer. Ce modèle est partiel mais il est utile pour ce qu'on veut faire, et c'est tout ce qui compte.\nEt si on a besoin de vérifier aussi la hauteur, on peut utiliser un autre modèle du réfrigérateur qui suffit : un simple mètre. => On a là une vision de l'approche DDD des modèles.","4---integrating-bounded-contexts#4 - Integrating Bounded Contexts":"Chaque bounded context permet de représenter un modèle au sens DDD, c'est-à-dire une abstraction utile pour résoudre un problème particulier.\nLes bounded contexts doivent quand même avoir des points de contact pour que le logiciel forme un tout, ces points sont appelés contracts.\nOn parle ici de points de contact au niveau du modèle (terminologie, concepts), mais le modèle se traduit à chaque fois en implémentation aussi.\nLa nature de ces contracts dépend de la nature de la relation entre les équipes qui gèrent les bounded contexts, il y en a de 3 types :\nCooperation : il s'agit soit de la même équipe, soit d'équipes qui ont une bonne communication et collaboration (par ex le succès de l'une dépend du succès de l'autre).\nPartnership : aucune équipe ne dicte sa loi à l'autre. Elles collaborent pour faire des changements dès qu'il y en a besoin d'un côté ou de l'autre.\nIl faut une intégration continue pour que les problèmes soient vite résolus.\nIl faut une communication vraiment au top entre équipes, et pas d'histoires de rivalités.\nShared Kernel : il s'agit d'un cas à part où les boundaries des bounded contexts sont violés : deux bounded contexts partagent une partie de leur modèle.\nPar exemple, ça peut être la partie d'authentification maison qu'ils auront en commun.\nLa partie partagée peut être changée par chaque bounded context, et affectera l'autre immédiatement. Il faut donc que des tests d'intégration soient déclenchés à chaque changement.\nBien réfléchir avant de l'utiliser : on l'utilise quand le coût pour appliquer les changements du modèle sous forme d'implémentation dépasse le coût de coordination entre équipes. C'est donc pertinent pour des modèles qui changent beaucoup.\nCas d'usage courants :\nQuand deux équipes n'ont pas une assez bonne communication, et que le modèle partnership donnerait lieu à des problèmes d'intégration.\nQuand on a un système legacy qu'on essaye de découper en bounded contexts, la partie pas encore découpée peut être temporairement partagée par les autres bounded contexts.\nQuand une même équipe a deux bounded contexts en charge, il est possible qu'avec le temps les frontières de ces deux bounded contexts s'estompent. Pour éviter ça on peut introduire une partie partagée qui va bien définir les frontières des deux autres.\nCustomer-Supplier : contrairement au modèle de type cooperation, la réussite de chaque équipe en charge de chaque bounded context n'est pas liée. On a donc un rapport de force qui va pencher vers l'une ou l'autre partie (le customer qui consomme ou le supplier qui fournit).\nConformist : la provider n'a pas vraiment de motivation à satisfaire le customer. Soit parce que c'est un provider externe, soit il est interne et c'est la politique de l'entreprise qui fait ça. Le customer va donc se conformer au modèle et aux changements de modèle du provider.\nLe supplier le fait soit parce que c'est un standard bien établi, soit simplement parce que ça lui convient.\nAnticorruption Layer : le provider a toujours le pouvoir, mais cette fois le consumer ne va pas se conformer, il va vouloir se protéger par une couche d'abstraction entre ce qui lui arrive et son propre modèle.\nLe consumer peut vouloir cette couche quand :\nIl contient un core subdomain, et que le modèle du provider pourrait être gênant.\nLe modèle du provider est bordélique, et on veut s'en protéger.\nLe modèle du provider change souvent, et on veut s'en protéger.\nGrâce à cette couche filtrante, le modèle du consumer n'aura pas à être pollué par des concepts dont il n'a pas besoin.\nOpen-Host Service : cette fois c'est le consumer qui a l'avantage parce que le provider a une motivation à satisfaire le consumer. Le provider va donc mettre en place une couche d'abstraction de son côté pour découpler son modèle interne du modèle public qu'il expose.\nC'est comme l'anticorruption layer, mais côté provider.\nLe provider peut éventuellement maintenir plusieurs OHS le temps que les customers migrent au nouveau modèle public.\nSeparate ways : les deux bounded contexts n'interagissent pas du tout. Si elles ont des fonctionnalités communes, elles les dupliquent de leur côté.\nÇa peut être quand la communication entre équipes est impossible.\nOu encore quand ça coûte plus cher de partager une fonctionnalité que de la refaire chacun de son côté. Parce que la fonctionnalité est trop simple, ou que les deux modèles sont trop différents.\nLe context map est une représentation graphique de chaque bounded context, avec les relations que chacun entretient avec les autres.\nÇa peut permettre de faire des choix stratégiques haut niveau.\nÇa peut faire apparaître des problèmes organisationnels :\nPar exemple si une équipe provider a tous ses consumers qui mettent en place un anticorruption layer. Ou encore si une équipe n'a que des relations separate ways avec les autres.\nLe context map doit être maintenu par l'ensemble des équipes.\nOn peut utiliser des outils comme Context Mapper, qui consiste à avoir un fichier source dans un dépôt de code, et permet de visualiser le graphique résultant avec une extension VSCode.","part-ii--tactical-design#Part II : Tactical Design":"","5---implementing-simple-business-logic#5 - Implementing Simple Business Logic":"Ce chapitre présente deux patterns tactiques simples. Ce sont des patterns connus, décrits notamment dans Patterns of Enterprise Application Architecture de Martin Fowler.\n1 - Transaction Script : il s'agit pour le provider de fournir une interface publique avec des endpoints qui ont un comportement transactionnel : soit une requête réussit entièrement, soit elle est entièrement annulée.\nMartin Fowler écrit à propos de ce pattern : Organizes business logic by procedures where each procedure handles a single request from the presentation.\nNotre fonction peut accéder à la base de données pour faire des manipulations, soit à travers une mince couche d'abstraction, soit directement. (Donc forte dépendance avec la BDD).\nDans le cas simple où on a une BDD relationnelle, il s'agit simplement d'utiliser la fonctionnalité native de transaction de sa BDD : on crée la transaction au début de la fonction, et on commit ou rollback à la fin.\nLa chose se complique quand notre BDD ne supporte pas les transactions, qu'on doit mettre à jour des données dans plusieurs BDD, ou encore qu'on est dans un contexte distribué où les transactions ne sont pas effectives immédiatement (eventual consistency).\nPotentiels problèmes qu'on rencontre souvent :\nOubli de transaction : parfois on oublie tout simplement d'utiliser le mécanisme de transaction au début de notre fonction. En exécutant plusieurs écritures en BDD on peut avoir la 1ère qui réussit et la 2ème qui échoue. On se retrouve alors potentiellement dans un système incohérent.\nSystème distribué : souvent dans les systèmes distribués on met à jour une donnée, et on prévient d'autres serveurs en leur envoyant un message. Il suffit que l'envoi de message ne marche pas pour que le système devienne incohérent parce qu'une partie du travail ne sera pas faite.\nIl n'y a pas de solution simple à ce problème, mais de nombreuses solutions qui font chacune du mieux qu'elles peuvent, en fonction du contexte.\n(pas dit dans le livre, mais c'est l'objet de Designing Data-Intensive Applications de Martin Kleppmann)\nTransaction Script est le pattern le plus simple, et convient pour les opérations simples de type ETL (extract, transform, load) où la logique business est elle-même simple et change peu souvent.\nIl convient bien :\nDans les supporting subdomains.\nComme couche d'adapter pour intégrer un generic subdomain.\nDans un anticorruption layer vis-à-vis d'un autre bounded context.\nPar contre sa simplicité fait que plus la logique business est complexe, plus il y a de la duplication de logique entre les diverses fonctions, et donc un risque d'incohérence au fil du temps. Donc il ne conviendra pas aux core subdomains.\nTous les autres patterns plus avancés, sont d'une manière ou d'une autre basés sur Transaction Script.\n2 - Active Record : on garde les caractéristiques transactionnelles du Transaction Script, mais on va ajouter des objets (les fameux Active Records) qui vont être en charge de transformer les données depuis la BDD vers des structures en mémoire qui conviennent mieux à ce qu'on veut faire dans nos fonctions.\nMartin Fowler écrit à propos de ce pattern : An object that wraps a row in a database table or view, encapsulates the database access, and the domain adds domain logic on that data.\nLa conséquence est que les fonctions appelées par les endpoints ne vont plus faire d'appel à la BDD, mais vont passer uniquement par les Active Records pour manipuler les données.\nL'Active Record va fournir des méthodes pour accéder aux données et les modifier, et va faire la traduction à chaque fois.\nOn encapsule la complexité de la transformation des données pour éviter de dupliquer cette logique-là un peu partout.\nActive Record est utile pour les cas d'usage simples, mais qui impliquent des transformations de données potentiellement complexes.\nIl convient donc lui aussi seulement aux supporting subdomains, ou à des couches d'adapter pour intégrer un generic subdomain ou un autre bounded context.\nQuand il est utilisé pour les logiques business complexes comme pour les core subdomains, on parle parfois d'un anemic domain model antipattern (et c'est déconseillé).\nIl s'agit cependant de rester pragmatique : bien qu'il soit souhaitable de protéger les données, il faut se demander à chaque fois quelles seraient les implications de la corruption de données dans tel ou tel projet.\nPar exemple, si on ingère des millions d'entrées venant de devices IOT dans notre base, risquer d'en corrompre 0.001% peut ne pas forcément être très grave.","6---tackling-complex-business-logic#6 - Tackling Complex Business Logic":"Ce chapitre présente le pattern du DDD pour les logiques business compliquées (à utiliser dans les core subdomains donc) : le domain model pattern.\nCe pattern est aussi dans le bouquin de Fowler, mais il a été développé plus en détail dans celui d'Eric Evans : Domain Driven Design: Tackling Complexity in the Heart of Software, sorti un an après (2003).\nAu niveau de l'implémentation :\nLe code du domain model est là pour représenter le domaine qui est déjà complexe, donc il ne doit pas introduire de complexité supplémentaire liée aux technos ou à l'infrastructure. => on utilisera donc du code pur (par exemple du TypeScript pur), pas de framework, d'ORM ou autre chose d'extérieur dans ce code.\nDébarrassé de tout ce qui est lié aux technos, le code peut utiliser l'ubiquitous language et épouser le modèle mental des domain experts.\nA noter que ce livre présente les choses d'un point de vue orienté objet, mais une approche fonctionnelle serait tout aussi valide, d'autres ressources dans la communauté du DDD vont dans ce sens.\nLes blocs qui composent le domain model sont :\n1- Value objects :\nCe sont des objets immutables identifiés par les valeurs de chacune de leurs propriétés. Si on veut le même objet avec une valeur différente pour une de ses propriétés, on crée de fait un autre objet.\nPar exemple, dans Python ou JavaScript, le string est un value object : on ne peut pas modifier une valeur string en elle-même, mais on peut créer de nouveaux strings avec des valeurs différentes (et qui auront bien une référence en mémoire différente).\nLes value objects n'ont pas d'attribut ID, puisque leur identification se fait par la composition des valeurs de leurs propriétés qui les rend uniques.\nEn revanche, il faut implémenter un opérateur d'égalité pour pouvoir comparer deux value objects entre eux.\nLe value object sera utilisé comme type pour les données à la place des types primitifs (comme string, number etc).\nL'utilisation systématique de types primitifs est connue comme “the primitive obsession”.\nPar exemple, plutôt que d'avoir email: string, on peut avoir email: Email, avec le value object Email qui va faire la validation et refuser toute valeur qui ne correspond pas.\nÇa évite de dupliquer la logique de validation partout, mais aussi d'oublier de faire cette validation à certains endroits.\nEn plus de la simple validation de données, le value object peut encapsuler en un endroit unique du code business lié au concept précis qu'il décrit. (Et c'est là qu'il “brille” vraiment)\nConcept qui est normalement dans le lexique de l'ubiquitous language de notre bounded context.\nPar exemple, un value object représentant une couleur peut avoir une méthode pour mixer la couleur avec une autre, et en obtenir une 3ème, selon des règles business bien particulières.\nEt ça augmente la clarté du code en rendant plus explicite l'intention.\nOn utilise les value objects dès que possible. Typiquement comme type des attributs des entities.\n2.1- Entities :\nCe sont des objets dont les propriétés peuvent changer (de type value object ou de type primitif), identifiés par leur ID qui lui ne change pas.\nExemple d'entity : une personne représentée par son nom, son email etc.\nLes entities sont importants, mais ils n'ont vraiment d'existence que dans le cadre d'un aggregate.\n2.2 - Aggregates :\nUn aggregate est une entity qui en contient d'autres.\nOn choisit un aggregate root qui est l'entity principale, et contient les autres.\nL'idée c'est de regrouper des entities qui sont amenées à changer ensemble.\nL'aggregate est responsable de garantir la consistance des données en son sein.\nLes objets extérieurs pourront donc seulement lire ses propriétés, mais pour les changer il faudra toujours passer par l'interface publique de l'aggregate.\nLes fonctions de cette interface publique s'appellent les commands.\nIl y a 2 manière d'implémenter ces commands :\nSoit comme méthodes publiques de l'aggregate root.\nSoit la logique se trouve dans des objets de commande spécifiques à l'aggregate et contenant la logique à appliquer. On instancie un tel objet command avec les infos qu'on veut changer, et on le passe à une méthode “execute” de l'aggregate root chargée d'appliquer la commande.\nLe fait que l'aggregate soit le seul responsable de modifier ses données fait que la logique business le concernant ne peut qu'être en son sein, et pas disséminée ailleurs.\nLa couche applicative (aussi appelée souche “service” : celle qui forward les actions de l'API publique au domain model) n'a donc plus qu'à :\nCharger l'état actuel de l'aggregate.\nExécuter la commande requise.\nEnregistrer le nouvel état en BDD.\nRenvoyer le résultat à l'appelant.\nAttention à la concurrence. Pour éviter les problèmes, la couche applicative devra s'assurer que l'état lu au début n'a pas changé entre-temps avant de valider le nouvel état de l'aggregate en BDD.\nPour ce faire, le moyen le plus classique est que l'aggregate ait une propriété “version” incrémentée à chaque modification.\nSi la version a changé entre-temps par une autre action concurrente, on pourra choisir d'annuler notre action, ou d'appliquer un mécanisme de résolution approprié à notre logique business (si on en est capable).\nL'aggregate doit avoir un comportement transactionnel : toute commande devra soit être intégralement exécutée, soit annulée.\nEn revanche, aucune opération ne pourra modifier plusieurs aggregates de manière transactionnelle : c'est une transaction par action sur un seul aggregate.\nSi on se retrouve à avoir besoin de modifier plusieurs aggregates en une transaction, c'est que les limites de nos aggregates ont été mal pensées : il faut les revoir.\nExemple d'aggregate : un ticket de support, qui peut contenir un ou plusieurs messages, chacun pouvant contenir une ou plusieurs pièces jointes.\nLa règle de base pour délimiter son aggregate c'est de le choisir le plus petit possible, et d'y inclure toutes les données qui doivent rester fortement consistantes (strong consistency) avec l'aggregate.\nDonc si en prenant en compte la logique business, certaines données liées à l'aggregate peuvent être un peu désynchronisées (et se mettre à jour un peu plus tard) sans causer de problèmes ( => eventual consistency), alors elles doivent être hors de l'aggregate.\nDans ce cas la bonne pratique est de placer dans l'aggregate une propriété représentant leur ID, là où pour une entity faisant partie de l'aggregate on placera en propriété une instance de celle-ci :\n// le customer ne fait pas partie de l'aggregate\nprivate customer: UserID;\n// les messages en font partie\nprivate messages: Message[];\nExemple d'aggregate qu'on délimite : imaginons qu'on a notre ticket de support, avec une règle business qui implique de le réassigner à un autre agent s'il n'y a pas eu de nouveau messages alors qu'il reste moins de 50% du temps restant pour le traiter.\nSi les messages mettent du temps à être à jour par rapport au ticket (c'est-à-dire qu'il y a une eventual consistency entre les messages et leur ticket), on aura du mal à respecter la règle business parce qu'on ne saura pas si il y a eu un message récemment ou pas. => Donc les entities “messages” vont dans le même aggregate.\nEn revanche, si les données des agents ou des produits sont légèrement en retard, ça ne nous empêchera pas de respecter notre règle business. => Donc ceux-là ne vont pas dans le même aggregate.\n2.3 - Domain Events :\nLes domain events sont des événements importants que chaque aggregate peut publier, et auquel d'autres aggregates peuvent souscrire.\nLeur nom est important, et il doit être formulé au passé.\nPar ex : Ticket assigned ou Ticket escaleted\nLeur contenu doit décrire précisément ce qui s'est passé :\nExemple :\n{\n \"ticketId\": \"7bfaaf0c-128a-46f8-99b3-63f0851eb\",\n \"eventId\": 146,\n \"eventType\": \"ticket-escalation\",\n \"escalationReason\": \"missed-sla\",\n \"escalationTime\": 65463113312\n}\n3 - Domain services :\nQuand on a de la logique business qui n'appartient à aucun aggregate ou qui semble être pertinente pour plusieurs aggregates, on peut la placer dans un domain service.\nLe mot service ici ne fait pas référence à autre chose qu'on appellerait habituellement des services, c'est simplement des objets sans état qui contiennent de la logique business.\nDans les domain services on peut toucher à plusieurs aggregates, mais seulement pour de la lecture : la contrainte de ne modifier qu'un aggregate par transaction tient toujours.\nExemple de domain service : le temps pour répondre aux tickets de support doit être calculé en fonction de plusieurs paramètres, dépendant de l'agent en charge, d'éventuelles escalations, des shifts horaires etc. On peut donc avoir une fonction qui va lire dans plusieurs aggregates des informations, calcule le temps et le renvoie.\nAu fond, l'idée des value objects et des aggregates c'est de réduire le degré de liberté du système en encapsulant des morceaux de logique dans des endroits uniques, pour en réduire la complexité.","7---modeling-the-dimension-of-time#7 - Modeling the Dimension of Time":"On peut appliquer l'event-sourcing au domain model pattern pour créer un event-sourced domain model pattern.\nAvec l'event sourcing on introduit la dimension du temps dans notre modèle.\nIl s'agit de reprendre les mêmes blocs que le domain model pattern, mais avec une différence au niveau de la manière de stocker les informations en base de données (et c'est ça qui caractérise l'event sourcing) : on va stocker les domain events des aggregates comme source de vérité, au lieu de l'état actuel de l'aggregate.\nOn appelle cette BDD d'événements l'event store.\nLa BDD doit au minimum permettre de :\nrécupérer tous les événements d'un même aggregate\najouter des événements à un aggregate\nIl s'agit de rejouer l'ensemble des domain events d'un aggregate à chaque fois qu'on le charge en mémoire pour obtenir son état.\nUne fois qu'on a son état, on peut exécuter la logique business comme avant, en jouant la bonne commande sur l'aggregate.\nNotre aggregate génèrera alors des domain events, mais ne modifiera jamais son state autrement que par ces events.\nLa persistance se fera en persistant les domain event dans l'event store.\nAvec le domain model pattern classique on avait aussi des domain events qui informaient les composants extérieurs des changements importants, mais là la différence c'est que ces events doivent être émis pour chaque changement d'état de l'aggregate.\nCôté code, on a notre objet aggregate root avec ses propriétés de type value object ou entity, et cet objet a une méthode apply(event: EventType) qui est surchargée plusieurs fois pour chaque type d'event qui peut survenir.\npublic class Person {\n public id: PersonId;\n public name: Name;\n public email: Email;\n public version: number;\n function apply(event: PersonCreated) {\n this.id = event.id;\n this.name = event.name;\n this.email = event.email;\n this.version = 0;\n }\n function apply(event: EmailUpdated) {\n this.email = event.email;\n this.version += 1;\n }\n}\nLe constructeur de la classe prend la liste d'events et la rejoue dans l'ordre à chaque instanciation.\nOn peut rejouer la projection des événements avec des variantes :\nEn rejouant les X premiers événements on remonte à n'importe quel état précis dans le temps.\nEn nous intéressant seulement à certains événements et en implémentant des actions particulières dans les fonctions apply de ceux-ci, on peut rechercher des données particulières et faire de l'analyse de données.\nPar exemple, si on cherche toutes les personnes qui ont pu avoir une modification de leur email plus de 3 fois en une journée, il suffit de créer une classe qui surcharge apply pour l'event EmailUpdated, et de rejouer les événements en comparant les dates de changement dans notre méthode apply.\nAvantages et inconvénients :\nAvantages de la version event-sourced par rapport à la version classique :\nOn peut remonter dans le temps. Par exemple pour analyser, ou encore pour débugger en allant à l'état exact qui a causé le bug.\nOn a du deep insight sur ce qui se passe. Particulièrement utile pour les core subdomains qui sont complexes.\nOn a une forte traçabilité de tout ce qui se passe, très utile dans un contexte d'audit, par exemple si la loi l'oblige comme avec les transactions monétaires.\nQuand deux transactions écrivent la donnée de manière concurrente, on va habituellement annuler une des deux transactions. Ici on a des infos plus fines sur les deux changements concurrents, et donc on peut plus facilement prendre des décisions business sur la manière de les concilier au lieu de les annuler.\nDésavantages :\nCourbe d'apprentissage pour l'équipe : il faut du temps et la volonté pour l'équipe d'apprendre ce paradigme.\nL'évolution du modèle est plus compliquée : on ne change pas un schéma d'events comme on change la structure d'une BDD relationnelle.\nA ce propos il y a le livre Versioning in an Event Sourced System de Greg Young.\nLa complexité globale de l'architecture est plus grande.\nRDV au chapitre 10 pour avoir des règles basiques sur le type de design à choisir.\nA propos de la performance :\nOui rejouer les events à chaque fois qu'on instancie un objet affecte la performance, mais dans les faits :\nIl faut bien noter que les events sont propres à chaque aggregate, et que ceux-ci ont un cycle de vie.\nPar ex un ticket de support passera par plusieurs étapes, et finira par être fermé, donc n'aura plus de changements d'état.\nEn deçà de 10 000 events par aggregate, l'impact de performance ne se ressent pas.\nLa moyenne du cycle de vie des objets dans la plupart des systèmes est de l'ordre de 100 events.\nDans les rares cas où la performance est un problème, il y a la technique du snapshot.\nIl s'agit de générer de manière asynchrone une projection sous forme de BDD servant de cache, et ayant les données des états de chaque aggregates, mais aussi le numéro du dernier event pris en compte.\nQuand l'application a besoin de charger un aggregate, elle le fait depuis ce cache, puis lit le numéro du dernier event pris en compte, et va chercher les events manquants dans l'event store pour les appliquer elle-même et obtenir l'état à jour de l'aggregate.\nAinsi donc, même si l'aggregate a 100 000 events, l'application n'a à chaque fois qu'à appliquer les quelques events à rattraper qui n'avaient pas encore été appliqués sur la BDD dénormalisée servant de cache.\nAttention cependant, avant d'utiliser une technique avancée comme celle du snapshot il faut :\nS'assurer qu'on a un vrai problème de performance (+ de 10 000 events par aggregate).\nVérifier les limites de notre aggregate et voir si il n'est pas trop gros et qu'on ne pourrait pas plutôt le scinder.\nL'event sourcing scale très bien : vu qu'on a une séparation claire des données d'event par aggregate, on peut très bien sharder par identifiant d'aggregate.\nLe CQRS (décrit au chapitre suivant) peut répondre à la problématique de performance de par sa nature.\nComment supprimer des données de l'event store (par ex pour des considérations légales) ?\nL'idée de l'event sourcing c'est qu'on ne supprime pas les données d'event, c'est la condition pour toujours pouvoir rejouer, mais aussi la condition pour qu'elles ne soient jamais corrompues.\nOn peut utiliser la technique du forgettable payload : on stocke dans notre event une version chiffrée des donnée sensibles, et on stocke une paire clé de chiffrement / ID de l'aggregate dans un stockage key-value à part. Quand on a envie d'effacer une des données sensibles, il suffit de supprimer sa clé.\nNe pourrait-on pas continuer à utiliser une BDD relationnelle comme BDD principale, et écrire en plus les events soit dans un log, soit en utilisant un database trigger pour écrire dans une table en même temps ?\nC'est une mauvaise idée parce que le log qui ne serait pas la source de vérité finirait par se dégrader au fil du temps, parce qu'on y ferait moins attention.\nEt en plus il est difficile de se donner du mal à bien représenter le sens des events dans ceux-ci si ils ne sont pas la source de vérité et que cette info n'est pas destinée à la BDD principale.","8---architectural-patterns#8 - Architectural Patterns":"Jusqu'ici on s'est intéressé aux patterns tactiques pour la logique business, ici on dézoome un peu et on s'intéresse à la relation entre les diverses parties du code du système (la logique business est une de ces parties).\nSi on ne maintient pas une limite claire entre le code business et le reste, on risque d'avoir une logique diffuse un peu partout.\nÇa rend le code difficile à changer parce qu'il faut passer partout.\nCa fait qu'on peut oublier certains endroits et avoir des inconsistances.\nOn voit dans ce chapitre 3 architectural patterns :\nLayered architecture\nC'est un des patterns les plus courants, il s'agit d'avoir 3 couches.\n1- la couche de présentation représente l'interface utilisateur, mais de nos jours c'est plus large : Interface graphique, interface en ligne de commande, API programmatique ou réseau.\nOn l'appelle parfois aussi la user interface layer.\n2- la couche business : c'est là où on a les patterns décrits avant : active record, domain model pattern etc.\nOn l'appelle parfois aussi le domain layer ou le model layer.\n3- la couche data access où on stocke et manipule des données dans des bases de données, dans du cloud etc.\nOn l 'appelle parfois aussi l'infrastructure layer.\nCôté dépendance, la couche de présentation n'a accès qu'à la couche business, et la couche business qu'à la couche data access : presentation -> business -> data access\nCe pattern est souvent étendu avec une couche additionnelle appelée service : elle va venir se placer entre la présentation et le business. presentation -> service -> business -> data access\nLe service layer permet de faire la logique d'orchestration autour de la couche business, et permet de découpler davantage les couches présentation et business.\nPar exemple, on peut avoir un controller (du pattern MVC) qui réceptionne un appel API, et qui va simplement faire appel au bon service, puis renvoyer la réponse de celui-ci. Le service quant à lui va commencer une transaction, faire appel par exemple à un objet Active Record, et soit valider soit annuler la transaction avant de renvoyer le résultat.\nLe service layer peut être utile dans le cas où la partie business est un Active Record, mais si c'est un Transaction Script alors il n'y a rien à abstraire vis-à-vis de la couche présentation.\nOn l'appelle parfois aussi l'application layer.\nAttention à ne pas confondre la layered architecture avec une architecture N-Tier :\nN-Tier fait référence au fait d'avoir des composants déployables indépendamment et donc qu'on considère séparés “physiquement”, qui n'ont pas le même cycle de vie : par exemple ce qui tourne dans un navigateur, ce qui tourne sur un serveur applicatif, le serveur de base de données etc.\nQuand on parle de layered architecture, on parle bien des couches logicielles au sein d'une même entité physique, avec le même cycle de vie.\nEtant donné la dépendance entre couche business et couche de data access, la layered architecture est mal adaptée à du code business sous forme de domain model pattern. Elle est par contre adaptée pour du transaction script ou de l'active record.\nPorts and adapters\nElle est bien plus adaptée au domain model pattern parce que le code business va être découplé du reste.\nOn va mettre le business layer d'abord, sans qu'il ne dépende de rien, et ensuite on va regrouper tout ce qui est communication avec la BDD, frameworks, providers externes etc. dans une couche qu'on appellera infrastructure. Et enfin on ajoute une couche application layer (c'est plus parlant que service layer) au milieu.\nBusiness layer <- Application layer <- infrastructure layer\nGrâce au Dependency Inversion Principle, on va à chaque fois injecter les dépendances des couches supérieures dans les couches du dessous : la couche business ne connaît pas la nature concrète de la couche application, et la couche application ne connaît pas la nature concrète de la couche infrastructure.\nC'est ici que la notion de “ports and adapters” prend son sens : On a des ports qui sont des interfaces mises en place par les couches du dessus, et des adapters qui sont des implémentations concrètes faites par les couches du dessous, et passés en paramètre des fonctions des couches du dessus.\nExemple :\n// le port (couche business)\ninterface IMessaging {\n void publish(Message payload);\n}\n// l'adapter (couche infrastructure)\npublic class SQBus implements Imessaging {\n void publish(Message payload) {\n // ...\n }\n}\nIci la classe adapter qui respecte le contrat (le port) établi par la couche business pourra être donnée en paramètre dans les fonctions de la couche business où elle accepte un objet Imessaging. Elle pourra utiliser l'objet sans savoir exactement ce qu'il fait (publier dans un bus, mettre l'info simplement dans une variable etc..).\nOn l'appelle aussi (avec quelques variations) : hexagonal architecture, onion architecture ou encore clean architecture.\nDans ces variantes, l'application est parfois appelée service layer ou encore use case layer.\nEt le business layer est parfois appelé domain layer, ou encore core layer.\nCQRS (Command-Query Responsibility Segregation)\nCette architecture est similaire au ports & adapters pour ce qui est de la place centrale du code business indépendant du reste. La différence se trouve dans la manière de gérer les données : on va vouloir adopter un “polyglot modeling”, c'est-à-dire plusieurs formes dénormalisées de la donnée pour la lecture.\nA l'origine le CQRS a été pensé pour répondre au problème de l'event sourcing, en fournissant la possibilité de lire le state actuel des données directement, au lieu d'avoir à repartir de la forme primaire (qui est la liste d'événements) et de rejouer tous les events pour obtenir le state actuel.\nMais il est utile aussi sans event sourcing, c'est-à-dire dans le cas classique où la BDD “source de vérité” est par exemple relationnelle.\nComme l'indique le nom, on va faire une ségrégation (une séparation) entre les commands (les écritures) et les queries (les lectures).\nD'un côté les commands sont faites vers la base de données principale, source de vérité, et fortement cohérente (les invariants sont respectés, les règles business validées etc.).\nDe l'autre les queries sont faites vers des bases de données dérivées, utiles pour chaque cas particulier.\nPar exemple des data warehouses sous forme de colonnes pour l'analyse, des BDD adaptées à la recherche, des BDD relationnelles pour avoir le state des aggregates tout de suite etc.\nLes BDD de lecture peuvent aussi être des fichiers, ou encore des BDD in-memory. Et on peut normalement les supprimer et les reconstituer à nouveau à partir de la BDD principale.\nIl faut que les BDD secondaires soient mises à jour à chaque fois que la BDD principale est modifiée. On appelle ça les projections.\nIl y a les projections synchrones :\nIl s'agit d'un modèle de catch-up subscription.\nQuand une requête est faite à la BDD de lecture, le système de projection va faire une requête vers la BDD principale pour obtenir tous les changements de la donnée voulue jusqu'au précédent checkpoint.\nLes nouvelles données sont utilisées pour mettre à jour la BDD de lecture.\nLe système de projection retient le nouveau checkpoint à partir duquel il faudra faire la prochaine mise à jour de la donnée en question.\nPour que ça puisse fonctionner, il faut que la BDD principale note toutes les modifications faites à l'ensemble des entrées qu'elle possède, avec des checkpoints (des versions) pour chacune.\nNDLR : Ca ressemble à des domain events parce que chaque changement est noté en BDD. Mais pour que ça en soit il faut aussi que la signification métier de chaque changement soit noté, et pas juste “telle entrée est modifiée à telle valeur”. C'est dans cette mesure que le CQRS peut être utilisé même sans event sourcing.\nEt les projections asynchrones :\nIl s'agit d'un mécanisme de souscription :\nLa BDD principale publie chacun de ses changements dans un message bus, auquel souscrivent les projection engines de chaque BDD secondaire..\nA chaque message chaque BDD secondaire est mise à jour.\nCe modèle est plus scalable et plus performant, mais il est soumis aussi à des problématiques de consistance des données des BDD secondaires, et une plus grande difficulté à les reconstruire.\nIl est donc conseillé de rester sur le modèle synchrone et éventuellement construire une projection asynchrone par dessus.\nLes queries ne peuvent pas écrire de données, par contre les commands qui écrivent les données peuvent aussi retourner des valeurs, notamment le statut de succès ou d'échec de la commande, avec l'erreur spécifique.\nLe CQRS est adapté au domain model pattern, il est utile quand on veut avoir plusieurs modèles de données adaptés à plusieurs besoins. Il est aussi adapté à l'event sourced domain model pattern pour lequel il est quasi obligatoire.\nCes patterns doivent être appliqués au cas par cas pour les besoins de chaque brique logicielle. Il ne faut pas choisir un seul pattern pour tout le système, ni même forcément un seul pattern pour un même bounded context, dans la mesure où plusieurs subdomains peuvent exister dans un même bounded context.","9---communication-patterns#9 - Communication Patterns":"Dans ce chapitre il est question des patterns de communication entre briques logicielles distinctes et ayant chacune leur bounded context. C'est la mise en application technique du chapitre 4.\nPour implémenter un anticorruption layer ou un open-host service, on peut procéder de plusieurs manières :\nEn mode stateless : chaque requête est autonome. Dans ce cas, le bounded context contient le layer de transformation.\nOn va utiliser le pattern Proxy pour que la requête passe d'abord par le layer (le proxy), puis soit traduite correctement vers la target.\nLa traduction peut être synchrone, auquel cas le proxy est dans le même composant.\nOn peut aussi vouloir utiliser un API gateway pattern, en ayant un composant externe tenant le rôle du proxy et permettant d'intégrer plusieurs flux vers une même target.\nDans ce cas, on pourra utiliser une solution open source comme Kong ou KrakenD, ou une solution payante comme AWS API Gateway, Google Api-gee ou Azure API Management.\nLa traduction peut aussi être asynchrone, auquel cas le proxy est un composant qui souscrit à des events, et envoie des messages.\nIl pourrait en plus filtrer certains messages non pertinents.\nDans le cas d'un Open-Host Service, le modèle asynchrone est très pratique parce qu'il permet de traduire correctement les événements du langage privé vers les événements du langage public.\nEt même de filtrer certains événements qui n'ont d'utilité qu'au sein du bounded context et pas pour l'intégration avec les autres.\nEn mode statefull où le layer possède sa BDD et implémente une logique complexe.\nOn a le cas où on veut agréger les données entrantes. Soit parce qu'on veut les traiter par batchs, soit parce qu'on veut créer un plus gros message à partir de plus petits messages arrivant.\nLe BDD propre au proxy sert alors à gérer ce processus d'agrégation.\nParfois on peut utiliser un outil tout fait comme Kafka, AWS Kinesis, ou une solution de batch comme Apache Nifi, AWS Glue, Spark.\nOn a aussi le cas où on veut unifier des sources de données. Par exemple un backend for frontend qui va chercher des données chez plusieurs autres bounded contexts pour les afficher.\nA propos de l'implémentation technique des domain events émis par les aggregates et consommé par les autres composants.\nUne façon de faire triviale mais mauvaise serait de laisser l'aggregate faire l'ajout de l'event dans le message bus.\nL'event serait émis avant même que la transaction (qui a lieu dans le layer applicatif, après que le code de l'aggregate ait été exécuté) n'ait été commitée. On pourrait alors avoir des composants avertis d'une chose qui n'est pas encore vraie en DB.\nVoir même qui ne sera jamais vraie en DB si la transaction échoue.\nUne variante serait d'émettre l'event dans le layer applicatif juste après le commit dans la BDD.\nMais là encore que se passerait-il si la publication de l'event ne marchait pas ? Ou encore si le serveur crashait juste avant la publication de l'event ? On aurait la transaction validée mais pas d'event pour prévenir les autres.\nLe pattern Outbox permet d'adresser ces cas.\n1- Le state et l'event sont ajoutés dans la BDD en une seule transaction.\nL'event peut être ajouté dans une table différente si c'est possible. Si c'est une database NoSQL qui ne le permet pas, alors on peut le rajouter dans l'état de l'aggregate.\n2- Un message relay va s'occuper de traiter les events non publiés de la BDD en envoyant le message dans le message bus.\nLe relay peut soit poller régulièrement la BDD (il faut faire attention à ne pas la surcharger), soit on peut utiliser les fonctionnalités proactives de la BDD pour déclencher le relay.\n3- Une fois que l'event est envoyé, le message relay va enlever l'event de la BDD, ou le marquer comme publié.\nA noter que le relay garantit la publication du message au moins une fois : si il crash lui-même après avoir publié l'event mais avant de l'avoir enlevé de la BDD, il va le republier une 2ème fois.\nLe pattern Saga permet d'adresser les cas où on doit écrire dans plusieurs aggregates, vu que par principe on a décidé qu'une transaction ne devait écrire que dans un seul aggregate.\nLe saga va écouter des events émis par certains composants, et déclencher des commandes sur d'autres. Si la commande échoue, le saga est responsable de déclencher des commandes pour assurer la logique business.\nExemple : imaginons deux entities qu'on ne veut pas mettre dans le même aggregate parce qu'elles sont trop peu liées entre elles : une campagne de publicité et un publisher.\nOn veut que l'activation d'une campagne déclenche sa publication auprès du publisher, et qu'en fonction de la décision du publisher, elle soit soit validée soit annulée.\nLe saga va écouter l'event d'activation de l'aggregate de la campagne, et déclencher la commande de publication auprès de l'aggregate du publisher. Et il écoute aussi les events de validation ou d'annulation auprès de l'aggregate du publisher, pour déclencher une commande auprès de celui de la campagne en retour.\nLa logique de la saga peut aussi nécessiter de garder en mémoire son état, par exemple pour gérer correctement les actions de compensation. Dans ce cas les events de la saga peuvent être mis en BDD, et la saga peut être implémentée elle-même comme un event-sourced aggregate.\nDans ce cas il faut séparer la logique d'exécution des commandes de la mise à jour du state de la saga, et utiliser le même principe que pour l'outbox pattern. On aura un message relay qui garantira l'exécution de la commande même si on échoue à quelque étape que ce soit.\nAttention tout de même à ne pas utiliser les saga pour compenser des limites d'aggregates mal pensées. L'action du saga étant asynchrone, les données seront eventually consistent entre-elles. Les seules données fortement consistantes sont celles qui sont dans un même aggregate.\nLe Process Manager se base sur le même principe que la saga, mais là où la saga associe juste un event à une commande, le process manager va implémenter une logique plus complexe liée à plusieurs events pour choisir les commandes à déclencher.\nIl est implémenté sous forme de state-based ou event-sourced aggregate avec une persistance de son état en BDD.\nLà où la saga est déclenchée implicitement quand un événement qu'elle doit écouter se produit, le process manager est instancié par l'application et s'occupe de mener à bien un flow en plusieurs étapes.\nExemple : la réservation d'un voyage business commence par la sélection du trajet le plus optimal, puis la validation par l'employé. Dans le cas où l'employé préfère un autre trajet, le manager doit valider. Puis l'hôtel pré-approuvé doit être réservé. Et en cas d'absence d'hôtels disponibles, l'ensemble de la réservation est annulé.\nLà encore, vu qu'on a des commandes et un état, il vaut mieux implémenter l'outbox pattern pour jouer les commandes et mettre à jour l'état de manière sûre.","part-iii--applying-domain-driven-design-in-practice#Part III : Applying Domain-Driven Design in Practice":"","10---heuristics#10 - Heuristics":"Ce chapitre donne des heuristiques, c'est-à-dire des règles à appliquer qui marchent dans la plupart des cas.\nBounded contexts\nRecomposer différemment des bounded contexts quand on se rend compte que le découpage n'est pas bon coûte cher. Il vaut donc mieux commencer par des bounded contexts grands, et les redécouper plus finement par la suite.\nCette règle s'applique en particulier pour les bounded contexts contenant un core subdomain, qui change par nature beaucoup. Il peut être sage de laisser d'autres subdomains avec lesquels il réagit beaucoup dans le même bounded context.\nBusiness logic patterns\nSi le subdomain trace des transactions monétaires, ou doit permettre une traçabilité, ou encore des possibilités d'analyse approfondies des données, alors il faut choisir l'event sourced domain model pattern.\nSinon, si la logique business est quand même complexe (core subdomain), alors il faut choisir le domain model pattern classique.\nSinon, si la logique business inclut des transformations de données complexes, alors il faut choisir l'active record pattern.\nEt sinon, il faut se rabattre sur le transaction script pattern.\nConcernant le fait de savoir si une logique business est complexe :\nElle l'est si elle contient des règles business compliquées, des invariants et des algorithmes compliqués. On peut s'en rendre compte à partir de la validation des inputs (?).\nElle l'est aussi si l'ubiquitous language est compliqué. Et à l'inverse s'il décrit surtout des opérations CRUD, alors elle ne l'est pas.\nSi on se retrouve à avoir une différence entre le pattern qu'on veut utiliser pour répondre à la nature de la logique business, et la catégorie de notre subdomain, alors c'est l'occasion de s'interroger sur la pertinence de notre catégorisation.\nMais il ne faut pas oublier non plus que l'avantage compétitif peut ne pas résider dans la technique.\nArchitectural patterns\nL'event sourced domain model nécessite le CQRS. Dans le cas contraire, on est vraiment limité : on ne pourra qu'obtenir les entities par leur ID.\nLe domain model classique nécessite le ports & adapters, sinon on va avoir du mal à isoler la logique business de la persistance.\nL'active record va bien avec une layered architecture à 4 couches, avec la couche service qui va contenir la logique qui manipule l'active record.\nLe transaction script peut être implémenté avec une simple layered architecture à 3 couches.\nEnfin, même dans le cas où on n'a pas choisi l'event sourcing pour notre logique business, on peut quand même adopter le CQRS dans le cas où on aurait besoin d'avoir la donnée sous plusieurs formes différentes.\nTesting strategy\nLa testing pyramid (beaucoup de unit tests, moins de tests d'intégration, et peu de tests end to end) est bien adaptée aux domain model patterns (event sourced ou classique). Les value objects et les aggregates font de parfaits units autonomes.\nLe testing diamond (peu de test unitaires, beaucoup de tests d'intégration, et peu de tests end to end) est bien adapté à l'active record pattern. La logique business étant éparpillée à travers le layer service et business, il est pertinent de les tester ensemble. (avec la BDD du coup ?)\nLa reversed testing pyramid (peu de unit tests, plus de tests d'intégration, et beaucoup de tests end to end) est bien adaptée au transaction script. La logique business étant simple, et le nombre de couches faible, on peut directement tester de bout en bout.\nL'auteur a déjà rencontré des équipes qui utilisaient par exemple l'event sourced domain model pattern partout. Pour autant il ne le conseille pas, et a rencontré beaucoup plus d'équipes qui s'en sortaient bien avec ces règles d'heuristiques.","11---evolving-design-decisions#11 - Evolving Design Decisions":"Identifier les subdomains a des implications importantes. Mais il faut régulièrement questionner leur statut et éventuellement le réévaluer.\nCore -> supporting : Si l'avantage compétitif apporté par le subdomain n'est plus justifié, l'entreprise peut décider de réduire la complexité du subdomain au minimum pour se concentrer sur ceux qui ont plus de valeur.\nCore -> generic : On peut avoir un procédé qui constituait un avantage compétitif, qui se fait dépasser par un acteur qui se met à fournir le fournir sous forme de service sur le marché. Dans ce cas on se retrouve contraint d'utiliser sa solution pour rester à la page, et notre core subdomain devient un generic subdomain.\nSupporting -> core : Il s'agit de cas où un supporting subdomain dont la logique se complexifie. Si elle n'apporte pas d'avantage compétitif, alors il n'y a pas de raison à cette complexification et il faut la corriger. Sinon c'est qu'on se retrouve avec un core subdomain.\nSupporting -> generic : On peut imaginer qu'on ait créé un système simple pour gérer quelque chose. Puis il apparaît un système open source qui fait la même chose, mais a aussi des fonctionnalités que l'entreprise n'avait pas priorisé étant donné la faible valeur apportée par ce système. Mais si c'est à faible coût alors pourquoi pas : l'entreprise abandonne la solution maison pour la solution open source.\nGeneric -> core : Une entreprise utilisant une solution externe se retrouve limitée par celle-ci. Elle décide finalement d'implémenter une solution maison qui réponde vraiment à son besoin, et grâce à ça de mieux rendre son service. Un exemple est Amazon qui a créé une solution d'infrastructure web maison pour ses besoins, et a fini par la commercialiser sous le nom AWS.\nGeneric -> supporting : Pour la raison inverse de passer de supporting à generic, on peut décider que la solution open source ne vaut plus le coup parce qu'elle coûte trop cher à intégrer, et revenir à une solution maison simple.\nQuand ajouter des fonctionnalités devient douloureux, c'est un signe qu'on se retrouve avec des patterns tactiques qui ne sont plus adaptés à la complexité de notre logique business.\nPar exemple, si un supporting subdomain se met à avoir une logique de plus en plus complexe, il est peut être temps de le transformer en core.\nSi on a fait nos choix en conscience, et qu'on a connaissance des différents patterns existants, migrer n'est pas si difficile.\nTransaction script -> active record :\nIl s'agit dans les deux cas de logique procédurale. Quand on a des structures de données compliquées, il faut les repérer et les encapsuler la logique de lecture/écriture dans des objets.\nActive record -> domain model :\nOn pense à le faire quand on remarque que la logique business qui manipule les active records devient complexe, et qu'on se trouve face à des duplications qui mènent à des inconsistances à travers la codebase.\nOn identifie d'abord les value objects immuables dans notre logique. On identifie aussi la logique qui pourrait aller dans ces objets et on la migre dedans.\nEnsuite on essaye de délimiter les données qui doivent être mises à jour ensemble.\nOn peut mettre les setters des active records privés, et constater où ils sont appelés dans le code.\nOn peut alors déplacer tout le code qui est cassé à l'intérieur de l'active record. On obtient un bon candidat pour un aggregate.\nOn va ensuite examiner la hiérarchie d'entities qu'il faut construire, et extraire éventuellement plusieurs aggregates du code : un aggregate sera la plus petite unité dont les données doivent être fortement consistantes.\nEnfin on se débrouille pour que les méthodes de l'aggregate ne soient appelables que par l'aggregate root, et que seule l'interface publique soit appelable de l'extérieur.\nDomain model -> event sourced domain model :\nIl faut commencer par modéliser les domain events.\nLa partie la plus compliquée va être de gérer les events passés qui n'existent pas. On a deux manières de le faire :\nGénérer de fausses transitions passées.\nOn va analyser chaque aggregate et on va imaginer une manière réaliste d'obtenir l'état actuel par une succession de domain events successifs.\nOn va alors générer ces events en base et faire comme si notre aggregate avait toujours été event-sourced.\nL'avantage c'est qu'on pourra toujours faire des projections, y compris en partant de zéro.\nLe désavantage c'est que les events avant la migration seront inventés, et pourraient aussi induire en erreur sur une analyse.\nModéliser des events de migration.\nL'autre solution c'est d'accepter explicitement qu'avant un certain point on n'a pas de données. On va alors créer un event initial pour chaque aggregate, qui va simplement avoir pour effet de mettre la valeur actuelle de l'état de toutes les valeurs de l'aggregate.\nL'avantage c'est qu'on ne pourra pas avoir de fausses projections.\nLes changements organisationnels peuvent affecter les patterns d'intégration des bounded contexts :\nPar exemple, si une seule équipe gérait un bounded context avec plusieurs subdomains, l'arrivée d'une 2ème équipe mènera à la séparation du bounded context en deux, parce qu'un bounded context ne doit pas être géré par deux équipes différentes.\nAutre exemple : si un des bounded contexts est affecté à une autre équipe, et que la communication n'est pas bonne, on va passer du partnership à du customer-supplier.\nEt quand on a encore plus de problèmes de communication, il vaut parfois mieux dupliquer la fonctionnalité et passer sur du separate ways.\nIl faut prendre soin de la connaissance du domaine.\nEn particulier pour les core subdomains, la modélisation du domaine est complexe et change souvent. Il faut donc régulièrement revoir les value objects, les aggregates etc.\nParfois la connaissance du domaine se perd, la documentation n'est plus à jour, les gens bougent etc. Il faut alors la retrouver, par exemple avec des sessions d'event storming.\nA mesure que le code grossit, les décisions de design deviennent obsolètes et doivent être adaptées. Si on ne le fait pas, on va obtenir un big ball of mud.\nIl faut identifier et régulièrement éliminer l'accidental complexity résultant des décisions design obsolètes, et n'adresser que l'essential complexity inhérente au domaine avec les outils tactiques du DDD.\nLes subdomains qui deviennent de plus en plus gros peuvent être distillés pour en mettre en évidence d'autres. Il faut en particulier le faire avec les core subdomains pour que la partie core soit circonscrite à ce qui est vraiment nécessaire.\nLes bounded contexts peuvent aussi être revisités quand ils grossissent trop :\nOn peut simplifier le modèle du bounded context en extrayant un bounded context chargé d'un problème spécifique qui a grossi.\nParfois on se rend compte qu'un bounded context n'arrive pas à faire une action sans être dépendant d'un autre bounded context. On peut alors revoir ses limites pour augmenter son autonomie.\nLes aggregates doivent rester des unités incluant le plus petit set de données possible qui doivent être fortement consistantes entre elles.\nAu fil du temps on peut être amené à ajouter des choses dans des aggregates existants parce que c'est plus pratique.\nIl faut donc régulièrement revérifier le contenu des aggregates. Et ne pas hésiter à extraire des fonctionnalités dans un nouvel aggregate pour simplifier le premier.\nOn se rend souvent compte que le nouvel aggregate révèle un nouveau modèle et amène à la création d'un nouveau bounded context.","12---eventstorming#12 - EventStorming":"L'event storming est un atelier qui permet de modéliser ensemble un process business particulier.\nIl s'agit de construire la story du business process concerné, à travers une timeline d'events qu'on fait apparaître sur un tableau au cours de l'atelier.\nLes membres participant à l'atelier doivent être le plus divers possible (devs, domain experts, product owners, UI/UX, CSM etc.). Mais il vaut mieux ne pas dépasser 10 personnes.\nCôté matériel :\nÇa se passe sur un mur, avec un gros rouleau de papier sur lequel on va pouvoir coller des post-its.\nIl faut des post-its de couleur, chaque couleur représentant un concept particulier. Et des marqueurs pour écrire dessus.\nL'atelier dure 2 à 4 heures, donc il faut prévoir de quoi grignoter.\nIl faut une salle spacieuse, et rien qui ne gène pour que chaque participant accède librement au tableau.\nIl ne faut pas de chaises, les participants sont debout.\nL'atelier se passe en 10 étapes, pendant lequel le modèle est enrichi collectivement :\nÉtape 1 - Unstructured Exploration : il s'agit d'une étape en mode brainstorming : tous les participants prennent des post-its oranges, et écrivent dessus des events liés au process business auquel on s'intéresse.\nLes events sont collés sur le tableau sans se soucier de l'ordre ou de la redondance.\nLes events sont formulés au passé.\nExemple d'events : “Notification sent”, “Destination chosen”, “Order shipped”.\nÉtape 2 - Timelines : Les participants vont organiser les post-its dans le bon ordre, en commençant par le “happy path scenario”, puis par les cas d'erreur.\nOn peut tracer des flèches à partir d'un post-it pour mener à plusieurs flows de posi-its possibles.\nC'est aussi le moment d'enlever les doublons.\nÉtape 3 - Pain Points : On va marquer les points qui requièrent une attention particulière : des bottlenecks, des étapes manuelles à automatiser, de la doc ou du domain knowledge manquant etc.\nOn fait ça avec des post-its roses, tournés de 45° pour être “en diamant”.\nCette étape y est dédiée spécifiquement, mais le facilitateur doit aussi faire attention aux pain points levés tout au long de l'atelier et les marquer.\nÉtape 4 - Pivotal Events : On va s'intéresser à des events marquant une étape importante dans le processus, et tracer une barre verticale le long du tableau, pour délimiter l'avant et l'après cet event.\nExemple : dans un flow d'achat, “order initialized”, “order shipped” et “order delivered” peuvent être des events importants.\nÉtape 5 - Commands : On va ajouter des commandes sur des post-its bleus, juste avant l'event qui résulte de la commande.\nLes commandes sont formulées à l'impératif.\nExemple : “Publish campaign”, “Submit order”.\nSi l'actor (la persona du business domain, ex : client, administrateur etc.) qui émet la commande est évident, on l'ajoute sur le post-it de la commande avec un post-it jaune.\nÉtape 6 - Policies : Presque à chaque fois il y a des commandes qui n'ont pas d'actor associé. On peut alors leur ajouter une automation policy qui consiste à les déclencher lors d'un domain event, sans intervention manuelle.\nOn ajoute ces policies avec des post-its violets, et on va les placer entre la commande et l'event concernés.\nOn peut ajouter d'éventuels critères de déclenchement. Par exemple, si un event “complaint received” doit déclencher la commande “escalate” seulement si ça vient d'un client VIP, on peut mettre sur le post-it de policy que c'est seulement si client VIP.\nDans le cas où l'event et la commande sont loin, ne pas hésiter à tracer une flèche pour les joindre.\nÉtape 7 - Read Models : On ajoute les “read models”, c'est-à-dire les interfaces utilisateur (écran, rapport, notification etc.) utilisées par les actors pour prendre leurs décisions d'exécuter les commandes.\nIls seront ajoutés sur des post-its verts.\nExemple de read model : “Shopping cart”, pour un actor “customer”, qui va actionner la commande “submit order”.\nÉtape 8 - External Systems : On va représenter les intéractions avec des systèmes externes, c'est-à-dire qui ne font pas partie du domaine qu'on est en train d'explorer.\nIls seront ajoutés sur des post-its roses.\nÇa peut être un composant qui déclenche une commande, par exemple un CRM qui déclenche l'ordre d'expédier une commande?\nCa peut aussi être un composant qu'on va notifier, par exemple notifier le CRM suite à un event qui dit que l'expédition est approuvée.\nA la fin de cette étape, toutes les commandes devront avoir leur origine associée : soit un actor, soit une policy, soit un système externe.\nÉtape 9 - Aggregates : On va pouvoir organiser les commandes et events représentés en aggregates, sachant qu'un aggregate reçoit des commandes, et émet des events.\nOn va ajouter de longs post-its jaunes verticaux pour représenter l'aggregate. On va les placer entre les commandes et les events.\nÉtape 10 - Bounded Contexts : On va finalement essayer de regrouper les aggregates entre eux, soit parce qu'ils représentent des fonctionnalités liées entre elles, soit par leur couplage via des policies.\nCes groupes d'aggregates seront de bons candidats pour des bounded contexts.\nOn peut les matérialiser avec des pointillés entourant les groupes d'aggregates liés.\nLe créateur de l'event storming lui-même (Alberto Brandolini) dit qu'il s'agit simplement de conseil sur la manière de mener l'atelier. On peut très bien l'adapter comme ça nous convient.\nL'auteur du livre applique d'abord les étapes 1 à 4 sur le domaine tout entier pour obtenir une vision large du domaine et identifier les process business.\nEnsuite il organise un atelier pour chaque process business pertinent, en suivant cette fois toutes les étapes.\nLa vraie valeur de l'event storming c'est surtout le processus en lui-même, et le fait qu'il permette la communication entre les différentes parties prenantes, en leur permettant d'aligner leur modèle mental, de découvrir d'éventuels modèles en conflit, et de formuler un ubiquitous language.\nLes objets obtenus sur le tableau sont un bonus. Tous les ingrédients sont là pour implémenter un event-sourced domain model pattern si on est bien face à un core subdomain et qu'on veut faire ce choix.\nL'utilisation de l'event storming permet non seulement de construire l'ubiquitous language, et construire le domain model, mais il est aussi utile pour explorer de nouvelles fonctionnalités, retrouver de la connaissance du domaine perdue, onboarder de nouvelles recrues en leur donnant de la connaissance du domaine.\nEn revanche, l'event storming sera peu utile si le process business examiné est simple et évident.\nTips pour les facilitateurs :\nAu début de l'atelier, donner une vision d'ensemble du processus, et présenter les éléments de modélisation qui vont être utilisés tout au long (en construisant une légende avec les post-its de chaque couleur par exemple).\nSi on sent un ralentissement du dynamisme du groupe, on peut relancer avec une question par exemple, ou peut être que c'est le moment de passer à l'étape suivante.\nTout le monde doit participer, si certain(e)s sont en retrait, essayer de les inclure en leur posant une question sur l'état actuel du modèle.\nL'activité étant intense, il y aura au moins un break.\nIl ne faut pas reprendre l'activité tant que tout le monde n'est pas de retour.\nOn peut reprendre en récapitulant le modèle tel qu'il a été défini pour le moment.\nLes ateliers en remote sont plus difficiles à mener, l'auteur conseille de se limiter à 5 personnes.\nParmi les outils existants, miro.com est celui qui est le plus connu au moment de l'écriture du livre.\nPas besoin d'être ceinture noire pour faciliter une session : on apprend en faisant.","13---domain-driven-design-in-the-real-world#13 - Domain-Driven Design in the Real World":"Les techniques du domain driven design apporteront le plus de bénéfices aux brownfield projects : les projets ayant déjà un business et s'étant éventuellement embourbés sous forme de big ball of mud.\nPas besoin que tous les devs soient des ceintures noires du DDD, ni d'appliquer toutes les techniques que le DDD propose.\nPar exemple, si on préfère d'autres patterns tactiques que ceux du DDD, c'est tout à fait OK.\nIl faut d'abord commencer par l'analyse stratégique :\nD'abord comprendre le domaine avec une vue haut niveau (qui sont les clients, que fait l'entreprise, qui sont les compétiteurs etc.).\nPuis identifier les subdomains. Pour ça on peut partir de l'organigramme de l'entreprise.\nPour les core subdomains, on peut se demander ce qui différencie l'entreprise de ses compétiteurs :\nPeut-être un algorithme maison que les autres n'ont pas ?\nPeut être un avantage non-technique comme la capacité à embaucher du personnel top niveau, ou de produire un design artistique unique ?\nUne heuristique qui fonctionne bien est de trouver les composants qui sont dans le pire état : ceux qui sont devenus des big balls of mud, que les devs détestent mais que les dirigeants refusent de faire réécrire à cause du risque business.\nPour les generic subdomains il s'agit simplement de trouver les solutions prêtes à l'emploi, soit open source, soit payantes.\nLes supporting subdomains sont les composants restants.\nIls peuvent être dans un mauvais état mais suscitent moins de plainte de la part des devs parce qu'ils sont moins souvent modifiés.\nOn n'est pas obligés d'identifier tous les subdomains. On peut commencer par les plus importants.\nOn peut ensuite identifier et analyser les différents composants logiciels.\nLe critère ici c'est le cycle de vie : les différents composants sont ceux qu'on peut faire évoluer et déployer indépendamment des autres.\nOn peut alors regarder les patterns d'architecture utilisés pour chaque composant, et vérifier si un pattern complexe serait plus adapté, ou à l'inverse si on pourrait utiliser un plus simple ou même une solution existante.\nOn peut ensuite faire comme si ces composants étaient des bounded contexts, et tracer le context map avec la relation entre chacun d'entre eux.\nOn peut là aussi vérifier si les patterns d'intégration de bounded context sont améliorables : plusieurs équipes travaillant sur le même composant, implémentations de core subdomain dupliquées, implémentation de core subdomain sous-traitée, frictions à cause d'une mauvaise communication entre équipes etc.\nOn pourra par la suite utiliser l'event storming pour construire un ubiquitous language, et éventuellement retrouver de la connaissance du domaine perdue.\nEnsuite on peut mettre en place une stratégie de modernisation :\nIl ne s'agira pas de tout réécrire parce que ça marche rarement, et que c'est supporté par le management encore plus rarement.\nIl faut accepter que dans un grand système, tout ne sera pas bien designé, et se concentrer sur les composants qu'on estime stratégiques.\nMais pour ça il faut déjà s'assurer qu'on a bien une délimitation, au moins logique, entre les subdomains (code séparé sous forme de modules, namespaces, packages etc.).\nNe pas oublier aussi les bouts de code du subdomain qui seraient ailleurs, sous forme de stored procedures d'une BDD, ou sous forme de serverless functions.\nOn peut alors commencer à extraire des composants logiques en bounded contexts physiques, en commençant par ceux qui apportent le plus de valeur.\nIl faut ensuite bien examiner les composants extraits et réfléchir à comment les moderniser :\nIl faut examiner leur intégration vis-à-vis de la relation entre les équipes qui en ont la charge et de leur niveau de communication (partnership vers customer/supplier, shared kernel vers separate ways etc.).\nCôté tactique, il faut se concentrer sur les composants qui apportent beaucoup de valeur (core) mais dont l'implémentation ne serait pas adaptée et rendrait difficile la maintenance, en utilisant plutôt un domain model pattern.\nIl faut aussi ne pas oublier de construire un ubiquitous language avec les domain experts, notamment au travers d'ateliers d'event storming.\nConcernant la modernisation de chaque bounded context extrait, on peut procéder de deux manières :\nLe strangler pattern : on va créer un nouveau bounded context à côté de l'ancien, et toutes les nouvelles fonctionnalités iront dedans. On migrera aussi les anciennes progressivement jusqu'à ce qu'il ne reste plus rien du premier.\nOn peut mettre en place une façade devant les deux bounded contexts pour rediriger les appels vers l'un ou l'autre. Elle disparaît quand le composant legacy meurt.\nLes deux bounded contexts peuvent temporairement partager une même base de données (chose qu'on ne fait pas d'habitude pour deux bounded contexts).\nLe refactoring progressif : on va changer le modèle et les patterns progressivement au sein du bounded context comme expliqué au chapitre 11.\nIl faut procéder par étapes, et ne pas sauter d'un transaction script à un event sourced domain model par exemple.\nIl faut passer du temps à trouver les bonnes limites pour les aggregates, en particulier si on va jusqu'à l'event sourced domain model, les changer ensuite est plus difficile.\nL'introduction du domain model pattern elle-même peut se faire en plusieurs étapes : par exemple commencer par trouver les objets immutables pour les extraire en value objects.\nComment introduire le DDD au sein de mon organisation ?\nÉtant donné les changements importants, y compris organisationnels et d'implication des effectifs hors ingénierie, avoir un appui du top management peut beaucoup aider. Mais c'est plutôt rare.\nCeci dit, l'essentiel du DDD reste des pratiques d'ingénierie logicielle, donc on peut commencer à l'utiliser dans ses activités quotidiennes même sans mise en place à l'échelle de l'organisation.\nOn peut déjà commencer à construire un ubiquitous language et l'utiliser.\nÉcouter les domain experts parler, leur demander des clarifications, repérer les doublons et demander à ce qu'on n'utilise qu'un des termes.\nParler avec les domain experts plus souvent, pas forcément au cours de meetings formels. En général ils sont ravis de parler aux devs qui sont sincèrement intéressés par comprendre le domaine.\nUtiliser la terminologie qu'on a constitué dans le code, et dans tous les autres supports ou échanges.\nPour les bounded contexts, l'important est de comprendre les principes sous-jacents pour pouvoir les utiliser :\nPourquoi créer plusieurs modèles utiles à chaque problème ? Parce qu'utiliser un très gros modèle est rarement efficace.\nPourquoi un bounded context ne doit pas avoir des modèles en conflit en son sein : à cause de la complexité que ça engendre.\nPourquoi plusieurs équipes ne devraient pas travailler sur un même bounded context ? A cause de la friction et de la mauvaise collaboration que ça engendre.\nC'est la même chose pour les patterns tactiques : il faut comprendre la logique de chaque pattern et utiliser cette logique pour améliorer son design, plutôt que faire appel à l'argument d'autorité “DDD” qui ne mènera nulle part.\nPourquoi faire des limites transactionnelles explicites ? Pour protéger la consistance de la donnée.\nPourquoi une transaction DB ne peut pas modifier plus d'une instance d'un aggregate à la fois ? Pour être sûr que les limites de consistance sont correctes.\nPourquoi l'état d'un aggregate ne peut pas être modifié directement par un autre composant ? Pour s'assurer que toute la logique business est située au même endroit et pas dupliquée.\nPourquoi ne pourrait-on pas déplacer une partie de la logique d'un aggregate dans une stored procedure ? Pour être sûr de ne pas dupliquer la logique, parce que la logique dupliquée dans un autre composant a tendance à se désynchroniser et mener à de la corruption de données.\nPourquoi essayer d'avoir des limites d'aggregates petites ? Parce que des limites transactionnelles larges augmentent la complexité de l'aggregate et impactent négativement la performance.\nPourquoi, à la place de l'event sourcing, ne pourrait-on pas écrire la donnée dans un fichier de log ? Parce qu'on n'aura pas de garantie de consistance de longue durée pour la donnée.\nA propos de l'event sourcing en particulier, expliquer le principe aux domain experts, et en particulier le niveau d'insight qu'on acquiert sur la donnée, va en général les convaincre que c'est une bonne idée.","part-iv--relationships-to-other-methodologies-and-patterns#Part IV : Relationships to Other Methodologies and Patterns":"","14---microservices#14 - Microservices":"Un service est une entité qui reçoit des données en entrée et envoie des données en sortie. Ca peut être de manière synchrone comme avec le modèle request/response, ou asynchrone comme avec le modèles basé sur les events.\nIl a une interface publique qui décrit comment on peut communiquer avec. Elle est en général suffisante pour comprendre ce que le service fait.\nUn microservice est un service avec une petite interface publique.\nEn limitant son interface on le rend plus facilement compréhensible, et on réduit les raisons qu'il a de changer.\nC'est aussi pour ça qu'un microservice possède sa propre base de données et ne l'expose pas.\nNDLR : cette courte définition n'est pas partagée par tout le monde.\nDave Farley définit le microservice comme étant d'abord une unité déployable indépendamment, et donc conçue de telle manière qu'elle n'ait pas besoin d'être testée avec d'autres unités. cf. vidéo 1, vidéo 2\nQuand on se pose la question d'à quel point notre service devrait avoir une petite interface, il faut prendre en compte la complexité locale (la complexité interne de chaque microservice) et la complexité globale. (la complexité de l'ensemble résultant de l'interaction entre microservices).\nPour réduire au maximum la complexité globale, il suffit d'implémenter l'ensemble sous forme d'un unique service monolithique. La complexité locale est alors maximale, et le risque est de finir avec un big ball of mud.\nPour réduire au maximum la complexité locale, on pourrait mettre chaque fonction dans un microservice, avec sa base de données. La complexité globale est alors maximale, et on risque alors de se retrouver avec un distributed big ball of mud.\nIl s'agit donc de trouver un juste milieu entre complexité locale et globale.\nL'auteur évoque le concept de depth proposé par John Ousterhout dans son livre The philosophy of Software Design : un deep module a une petite interface mais une plus grande implémentation, alors qu'un shallow module a une grande interface comparé à son implémentation (donc beaucoup de choses sont exposées).\nC'est la même chose pour les microservices, il faut les concevoir avec l'interface la plus petite possible, tout en ayant une implémentation importante en comparaison. Il faut que le microservice en tant qu'entité d'encapsulation encapsule des choses, sinon il ne fera qu'ajouter de la complexité accidentelle.\nLes microservices sont souvent confondus avec les bounded contexts.\nIl est vrai qu'un microservice est forcément un bounded context : il ne peut pas être géré par deux équipes, et il ne peut pas y avoir plusieurs modèles en conflit en son sein.\nEn revanche, un bounded context peut être plus large que ce qui serait raisonnable pour un microservice, il peut contenir plusieurs subdomains tant qu'un même modèle (et un même ubiquitous language) permet d'en rendre compte.\nDu coup :\nDes ensembles trop vastes contenant des modèles en conflit mènent à du big ball of mud.\nDes ensembles moins vastes avec un modèle unique sont des bounded contexts.\nDes ensemble suffisamment petits pour avoir une petite interface publique sont des microservices et aussi des bounded contexts.\nSi on découpe au-delà, on tombe sur du distributed big ball of mud.\nParfois on a envie d'utiliser des aggregates comme microservices.\nIl s'agit de regarder le couplage entre l'aggregate et les autres composants du subdomain : s'il communique souvent avec d'autres composants, si le changer mènerait souvent à changer d'autres composants etc. Plus ce couplage est fort, plus le microservice résultant sera “shallow”.\nParfois l'aggregate est suffisamment indépendant et ça marche bien, mais la plupart du temps c'est une mauvaise idée : l'aggregate se trouve être un découpage trop petit et mène au distributed big ball of mud.\nEnfin, une autre possibilité est d'utiliser les subdomains pour les microservices.\nLes subdomains sont de bons candidats pour des deep modules.\nIls sont concentrés sur les use-cases plutôt que sur la manière dont ceux-ci seront implémentés.\nLes use-cases sont dépendants les uns des autres.\nLe subdomain agit sur un même jeu de données.\nChoisir les subdomains comme heuristique pour ses microservices est donc une bonne idée.\nParfois il sera préférable de choisir un ensemble plus vaste qui sera un bounded context, parfois un ensemble plus restreint qui sera un aggregate. Mais la plupart du temps les subdomains feront l'affaire.\nLes Open-Host Services, et Anticorruption Layers peuvent contribuer à réduire davantage l'interface publique des microservices, en n'exposant qu'une version réduite du modèle interne.\nDans le cas de l'ACL, il peut être mis dans un service à part, réduisant donc l'interface publique du service consommateur qui se protège.","15---event-driven-architecture#15 - Event-Driven Architecture":"L'event driven architecture est une architecture dans laquelle les composants souscrivent et réagissent à des événements de manière asynchrone, plutôt que sous forme de requête/réponse de manière synchrone.\nLe pattern saga en est un exemple.\nOn parle bien ici de communication entre composants (ie. bounded contexts). Alors que l'event sourcing porte sur l'utilisation d'events à l'intérieur du BC, l'event driven architecture s'intéresse aux events utilisés pour communiquer entre BCs.\nL'event et la commande sont tous deux des messages.\nL'event décrit quelque chose qui s'est déjà passé et ne peut pas être annulé ou refusé.\nLa commande décrit quelque chose qui doit être fait, et qui pourrait être refusé, auquel cas on peut déclencher des commandes de compensation.\nOn peut classer les events en 3 catégories :\n1- L'event notification est un event qui sert à notifier un composant de quelque chose, pour le pousser à faire une query dès qu'il est disponible.\nOn ne va pas mettre toute l'info dans l'event, étant donné qu'on s'attend à ce que le composant refasse une query quand il est prêt.\nAvantages d'obliger à faire une query :\nCa peut être bien niveau sécurité : notifier avec des infos non sensibles dans l'event, et vérifier avec une autorisation plus forte quand la query explicite est faite.\nÇa peut permettre au composant d'être sûr d'avoir des infos à jour au moment du processing vu que la query sera synchrone, contrairement à l'event.\nÇa peut permettre de mettre en place une opération bloquante du point de vue concurrence pour que ce soit fait par une seule instance.\nExemple : ici on n'a quasi aucune info à part l'ID et un lien pour en savoir plus.\n{\n \"type\": \"mariage-recorded\",\n \"person-id\": \"01b9a761\",\n \"payload\": {\n \"person-id\": \"01b9a761\",\n \"details\": \"/01b9a761/mariage-data\"\n }\n}\n2- L'event-carried state transfer (ECST) est un event qui va donner l'information complète permettant à un composant externe de maintenir un cache de l'état interne de nos objets.\nOn peut soit envoyer à chaque fois un snapshot complet de l'état d'un objet, ou alors ne renvoyer que les modifications de cet état et le receveur se débrouille pour maintenir la cohérence de l'état au fur et à mesure.\nAvantages de maintenir un cache à distance :\nOn est plus tolérant aux fautes : le composant qui nous consomme peut continuer à fonctionner même si on est down.\nOn économise des requêtes : le composant consommateur n'a pas à faire de requête à chaque fois pour obtenir la donnée, et si elle ne change pas il n'y aura pas non plus d'event vu que le cache sera déjà à jour.\nExemple : ici on a la donnée qui a changé dans le state de la personne concernée, pour qu'on mette à jour notre cache.\n{\n \"type\": \"personal-details-changed\",\n \"person-id\": \"01b9a761\",\n \"payload\": {\n \"new-last-name\": \"Williams\"\n }\n}\n3- Le domain event décrit un événement lié au business domain, et qui s'est produit dans le passé. Il est là dans un but de modélisation du business domain et pas spécialement pour des considérations techniques vis-à-vis des autres composants.\nPar rapport à l'event notification :\nLe domain event inclut toute l'info nécessaire pour décrire l'event, alors que l'event notification non.\nLe domain event est utile en interne même si aucun composant externe s'y intéresse, alors que le but de l'event notification est uniquement l'intégration avec les composants externes.\nPar rapport à l'ECST :\nLe but est différent : le domain event décrit le fonctionnement du domaine, alors que l'ECST est là pour exposer l'état interne des objets pour des raisons techniques.\nEn s'abonnant à un type précis de domain event, on n'a pas du tout la garantie d'obtenir tous les changements d'un aggregate par exemple, contrairement à ce que permet l'ECST.\nExemple : on modélise l'événement qui s'est produit au plus près possible du domaine.\n{\n \"type\": \"married\",\n \"person-id\": \"01b9a761\",\n \"payload\": {\n \"person-id\": \"01b9a761\",\n \"assumed-partner-last-name\": \"true\"\n }\n}\nPour montrer qu'il ne suffit pas de saupoudrer un système d'events pour le transformer en event driven architecture, voici un exemple réel d'architecture produisant un distributed big ball of mud :\nImaginons un composant (bounded context) CRM utilisant l'event sourced domain model. Trois autres composants choisissent de consommer l'ensemble de ses domain events :\nLe BC Marketing les transforme en état, puis les utilise pour se mettre à jour.\nLe BC AdsOptimization fait quelque chose de similaire à Marketing pour d'autres besoins.\nLe BC Reporting les consomme, puis attend 5 mn avant de faire une query vers AdsOptimization, pour espérer qu'AdsOptimization aura déjà traité les données liées à cet event.\nOn se retrouve avec 3 problèmes :\nUn couplage temporel : Reporting attend 5 mn avant de faire sa requête, mais rien de garantit que ce sera suffisant. Si AdsOptimization est surchargé, qu'on a des problèmes réseau ou autres, Reporting risque de faire sa requête avant qu'AdsOptimization n'ait fini de processer les données liées à cet event.\nSolution : plutôt que de faire consommer les events de CRM à Reporting, on peut faire en sorte que ce soit AdsOptimization qui envoie un notification event à Reporting quand il a processé quelque chose de nouveau, pour que Reporting fasse sa requête.\nUn couplage fonctionnel : Marketing et AdsOptimization consomment tous deux les mêmes events, qu'ils transforment tous deux en objets avec état exactement de la même manière. Ça crée une duplication de logique dans ces deux bounded contexts.\nSolution : CRM peut implémenter l'open-host service pour y implémenter la logique qui était dupliquée dans Marketing et AdsOptimization. Comme ça les consommateurs ne s'encombrent pas d'implémenter ça.\nUn couplage à l'implémentation : vu que CRM est event-sourced, les autres BCs sont couplés à l'implémentation de CRM. A chaque fois qu'il change quelque chose, il faut qu'ils mettent à jour leur code aussi.\nSolution : CRM, bien qu'event-sourced, n'a pas besoin d'exposer tous ses domain events. Il peut choisir publiquement d'exposer seulement certains d'entre eux, ou choisir d'exposer un type d'event différent, par exemple ici des ECST vu que Marketing et AdsOptimization sont intéressés par le fait d'avoir un cache de l'état de certains des objets de CRM.\nQuelques bonnes pratiques pour l'event driven architecture :\nIl faut toujours s'attendre au pire : éviter le mindset “things will be ok” et partir du principe que tout peut échouer.\nOn parle d'architecture distribuée : le réseau peut avoir des problèmes, les serveurs peuvent crasher, les events peuvent arriver deux fois ou pas du tout etc.\nIl faut donc s'assurer à tout prix que les events arrivent bien correctement :\nUtiliser l'outbox pattern pour publier les messages.\nS'assurer que les messages pourront être dédupliqués ou réordonnés grâce à leur numéro s' ils arrivent dans le mauvais ordre.\nUtiliser les patterns saga et process manager pour orchestrer des process cross-bounded contexts qui nécessitent des actions de compensation.\nIl faut bien distinguer les events publics des privés : ne pas tout exposer tel quel mais traiter les events qu'on expose comme une interface publique classique.\nNe jamais exposer l'ensemble des domain events d'un event sourced bounded context.\nQuand on implémente un open-host service, bien s'assurer que le modèle qu'on veut exposer est bien différent du modèle interne à notre bounded context, ce qui peut impliquer de transformer certains events, et pas seulement les filtrer.\nExposer plutôt des ECST et des notification events, et des domain events avec parcimonie, en distinguant bien ceux qu'on expose.\nOn peut utiliser le besoin de consistance comme critère supplémentaire pour le choix du type d'event :\nSi l'eventual consistency est OK, on peut utiliser l'ECST.\nSi le BC consommateur a besoin de lire la dernière écriture dans le state du producteur, alors le notification event appelant à faire une query est sans doute plus adapté.","16---data-mesh#16 - Data Mesh":"Les transactions OLTP (T pour transaction) et OLAP (A pour analytics) ont des buts, des consommateurs et des temporalités différents.\nLes OLTP permet de servir les clients sur des opérations plutôt en temps réel, et dont les fonctionnalités sont connues et optimisées.\nLes OLAP sont là pour obtenir des insights à partir des données, et permettre à l'entreprise d'optimiser le business.\nIls prennent en général plus de temps parce qu'ils portent sur de grandes quantités de données, et utilisent des données moins à jour.\nIls portent sur des données normalisées qui permettent une grande flexibilité de requêtes de la part des data analysts.\nNDLR : les OLAP utilisent souvent des BDD orientées colonne plutôt que lignes, c'est-à-dire que les données de toutes les entrées d'une même colonne sont stockées physiquement les unes à la suite des autres, pour faciliter les requêtes qui portent sur peu de colonnes et beaucoup d'entrées. (cf. Designing Data-Intensive Applications).\nLes OLTP vont typiquement utiliser des données organisées sous forme relationnelle avec des entités individuelles reliées entre elles, pour faciliter le fonctionnement des systèmes opérationnels.\nLes OLAP en revanche s'intéressent plus aux activités business et vont utiliser un modèle de données basé sur les fact tables et dimension tables.\nLes fact tables sont des tables qu'on va remplir avec des événements liés au business qui se sont produits dans le passé.\nExemple : Fact_Sales peut être un fact table contenant une entrée pour chaque vente qui a eu lieu.\nIls sont choisis pour répondre aux besoins des data analysts qui vont utiliser la BDD.\nSelon les besoins, on peut choisir d'y mettre seulement certaines données, par exemple des changements de statut dont on garde seulement une entrée toutes les 30 minutes, parce que plus serait inutile ou inefficace pour ce qu'on veut.\nLes données y sont ajoutées pour être lues, on n'y fait pas de modification.\nLes dimension tables sont référencées par des foreign keys à partir de la fact table, et décrivent des propriétés du fact en question.\nExemple : Dim_Agents, Dim_Customers etc. qui vont chacun avoir leurs propres champs qui les décrivent.\nOn remarque bien la forte normalisation avec la fact table au centre, et les dimension tables autour, qui permettent de maximiser la flexibilité des requêtes possibles sur les données autour de ce fact.\nOn appelle ce modèle fact tables / dimension tables le star schema.\nIl existe une variante appelée le snowflake schema, où les dimensions sont sur plusieurs niveaux : les dimension tables ont elles-mêmes des foreign keys vers d'autres dimensions qui les décrivent.\nL'avantage du snowflake c'est qu'il prend moins de place pour les mêmes données, par contre il faudra faire plus de jointures et donc les requêtes seront plus lentes.\nLe data warehouse consiste à extraire les données des systèmes opérationnels, et les mettre dans une grande BDD avec un modèle orienté analytics (star, snowflakes etc.). Les data analysts et ingénieurs BI vont alors consommer cette BDD avec du SQL.\nOn a des flows de type ETL qui vont consommer les BDD mais aussi éventuellement des events, des logs etc. pour construire le data warehouse.\nIl peut y avoir de la déduplication, de l'élimination d'informations sensibles, de l'agrégation etc.\nLe data warehouse pose plusieurs problèmes :\nOn retombe sur la problématique d'un modèle unique pour régler tous les problèmes (dont on s'était sorti par la création de bounded contexts), qui est inefficace.\nUne des solutions qui a été trouvée c'est de créer des data marts qui vont constituer des BDD spécifiques, soit extraites à partir de la data warehouse, soit extraite directement à partir d'un système opérationnel.\nLe problème c'est que si le data mart est extrait de la data warehouse on ne règle pas le problème du modèle unique dont on dépend. Et si on extrait à partir d'un système opérationnel, on a du mal ensuite à faire des requêtes cross-database entre plusieurs data marts, à cause de problèmes de performance.\nOn a aussi un couplage à l'implémentation des systèmes opérationnels. Or être couplé à l'implémentation de gens à qui on parle peu c'est catastrophique. Dès qu'ils modifient leur schéma de données, ça casse les scripts d'ETL qui nourrit le data warehouse.\nLe data lake est censé résoudre certains de ces problèmes, en se plaçant entre les systèmes opérationnels et la data warehouse.\nElle centralise au même endroit les données opérationnelles sans changer leur schéma. Le data warehouse va alors pouvoir être rempli avec un ou plusieurs flows ETL à partir du data lake.\nQuand le modèle du warehouse n'est plus satisfaisant, les data analysts vont pouvoir piocher dans le data lake avec d'autres scripts ETL.\nIl y a cependant des problèmes :\nLe processus est plus complexe, et les data engineers se retrouvent souvent à maintenir plusieurs versions d'un même script ETL pour gérer plusieurs versions d'un système opérationnel.\n=> on n'a pas vraiment réglé le souci de couplage à une implémentation gérée par une autre équipe.\nLe data lake étant schema-less, il n'apporte pas de garantie vis-à-vis de la consistance des données venant des systèmes opérationnels.\nQuand les données deviennent grandes, le data lake se transforme en data swamp (marécage de données), rendant le travail des data scientists très difficile.\nLe data mesh tente de répondre à son tour à ces problématiques, en adoptant d'une certaine manière une approche DDD appliquée à la data.\nLe data mesh a 4 grands principes :\nDecompose data around domains :\nOn va prendre les bounded contexts qu'on avait créés, et y intégrer une partie data correspondant aux données issues des BDD opérationnelles de ce bounded context.\nL'équipe en charge du bounded context aura alors sous sa responsabilité à la fois la partie OLTP et la partie OLAP de son bounded context.\nOn élimine donc la friction qu'il y avait entre équipe feature et équipe data en intégrant une personne avec les compétences data dans l'équipe feature.\nData as a product :\nFini les scripts ETL douteux pour construire la data, chaque bounded context expose sa data proprement avec une API publique, à laquelle il accorde le même soin qu'une API publique destinée à être consommée par un composant OLTP du système.\nOn va mettre en place des SLA/SLO, on versionne le modèle de données exposé, les endpoints doivent être faciles à trouver et le schéma clairement défini etc.\nLa data étant un produit plutôt qu'un élément de seconde classe, chaque équipe gérant le bounded context a la responsabilité d'assurer la qualité et l'intégrité de la data exposée. Mais aussi servir la donnée sous les formats qui pourront intéresser les consommateurs (SQL, fichier etc.).\nL'idée c'est que les data analysts/BI puissent aller chercher facilement des données de plusieurs bounded context, appliquer éventuellement des transformations en local, et faire leur analyse.\nEnable autonomy :\nCréer une infrastructure pour gérer la data étant difficile, il faut une équipe centrale dédiée à l'infrastructure de la plateforme data.\nElle sera en charge de maintenir la plateforme qui permet aux feature teams de créer facilement leur produit data sous les différents formats possibles.\nPar contre elle reste la plus agnostique possible par rapport au modèle de données, qui est de la responsabilité des équipes qui produisent la donnée.\nBuild ecosystem :\nIl faut un corps de “gouvernance fédérale” pour penser l'écosystème data au sein de l'entreprise, et notamment la question de l'interopérabilité.\nCe corps est composé de représentants data et produit des équipes feature, et de représentants de l'équipe plateforme centrale.\nCôté relation avec le DDD :\nLe DDD aide à structurer la donnée analytique en amenant l'ubiquitous language et la connaissance du domaine.\nExposer une donnée différente de la donnée utilisée est l'open-host service pattern du DDD.\nGrâce au CQRS, on peut facilement mettre en place une ou plusieurs formes dénormalisées de plus qui auront un modèle analytique.\nLes patterns de relation entre bounded context s'appliquent aussi aux données analytiques (partnership, ACL, separate ways etc.).","appendix-a---applying-ddd-a-case-study#Appendix A - Applying DDD: A Case Study":"Vlad a passé quelques années dans une startup qui appliquait le DDD. Il s'agit ici d'une description de ce qu'ils ont fait, et des erreurs commises.\nL'entreprise portait sur le marketing en ligne, de la stratégie marketing aux éléments graphiques, campagnes marketing et appels des prospects récoltés.\nIl y avait aussi une importante partie data analytics sur l'optimisation des campagnes de marketing, le fait de faire travailler les agents sur les prospects les plus intéressants etc.\nLes 5 bounded contexts qu'il nous présente pour en tirer des leçons :\n1- Marketing : il s'agit d'une solution de gestion des campagnes.\nQuasiment chaque nom présent dans les requirements était un aggregate. Pour autant la plupart n'avaient que peu de logique, celle-ci étant essentiellement dans un énorme service layer.\nQuand on essaye d'implémenter un domain model mais qu'on finit avec un active record pattern, on appelle ça un anemic domain model antipattern.\nMalgré l'architecture mal adaptée, le projet a été un succès, grâce à l'ubiquitous language mis en place dès le début, et les conversations très fréquentes avec les domain experts.\n2- CRM : les sales l'utilisaient pour se répartir les prospects de manière optimisée.\nIls ont d'abord commencé à développer le CRM à l'intérieur du même monolithe que Marketing. Puis voyant que le modèle avait des incohérences, ils ont extrait CRM dans son propre bounded context (au niveau du code seulement).\nIls ont cette fois tenté d'utiliser le domain model pattern en mettant beaucoup de logique dans les aggregates, et chaque transaction n'affectant qu'un aggregate.\nLe tout prenant beaucoup de temps, le management a décidé de donner certaines fonctionnalités à l'équipe database, qui l'a fait sous forme de stored procedures.\nDeux équipes qui ne se parlent que peu, et qui travaillent sur le même bounded context : de la duplication de logique, des corruptions de données etc. la cata.\n3- Event Crunchers : ils ont remarqué que les événements venant des clients amenaient à modifier les deux bounded contexts, alors ils ont extrait la logique dans un 3ème.\nInitialement pensé comme un supporting subdomain, et développé avec du transaction script, la logique s'est vite complexifiée, et la qualité dégradée.\nFinalement au fil du temps la logique était devenue tellement complexe et bordélique, qu'ils ont dû le refaire sous forme d'event-sourced domain model, avec les autres bounded contexts souscrivant à ses events.\n4- Bonuses : il s'agit de calculer les bonus des sales.\nLà encore ça partait d'une logique simple, donc un supporting subdomain. Ils ont choisi d'utiliser l'active record pattern.\nLà encore la logique s'est complexifiée assez vite, grâce à l'ubiquitous language qui était en place avec les domain experts, ils ont pu se rendre compte que le modèle ne convenait plus à la complexité plus tôt que pour Event Crunchers : ils l'ont recodé sous forme d'event-sourced domain model.\n5- Marketing Hub : une nouvelle idée du management : se servir des nombreux prospects acquis pour les vendre à des clients.\nIls ont dès le début catalogué le subdomain comme core, et utilisé l'event-sourcing et CQRS.\nIls ont aussi utilisé les microservices, un concept qui devenait populaire à ce moment. Mais ils ont fait un microservice par aggregate, avec un aggregate event-sourced, et les autres state-based.\nAu fil du temps chaque micro service avait besoin de quasiment tous les autres, et ils se sont retrouvés avec un distributed monolith.\nFinalement, même si le subdomain était une source de profit et donc core, la partie logicielle en elle-même était très simple, et le pattern utilisé s'est révélé être largement overkill, amenant de la complexité accidentelle.\nL'ubiquitous language est selon Vlad le “core subdomain” du DDD : à chaque fois qu'ils l'ont bien mis en place, le projet a plutôt marché, et à chaque fois qu'ils ne l'ont pas fait, le projet a plutôt échoué.\nPlus on le met en place tôt, plus on évite des problèmes.\nLes subdomains sont aussi très importants. Mal les identifier amène à utiliser les mauvais patterns.\nVlad propose d'inverser la relation entre le subdomain et les patterns tactiques : d'abord choisir le pattern tactique qui convient le mieux au requirement, puis qualifier le type de subdomain, et enfin vérifier ce type avec les gens du business.\nSi le business pense que le subdomain est core, mais qu'on peut le réaliser facilement, on l'a peut être mal découpé et analysé, ou il faut se poser des questions sur la viabilité de l'idée business.\nSi le business pense que c'est supporting, mais qu'on ne peut le réaliser qu'avec des patterns complexes, alors :\nSoit le business s'enflamme sur les requirements et ajoute de l'accidental business complexity (mettre beaucoup de ressources sur une activité non rentable).\nSoit les gens du business ne se rendent pas compte qu'ils obtiennent un gain compétitif qu'ils n'avaient pas envisagé avec le subdomain en question.\nIl ne faut pas ignorer la douleur : si elle se manifeste, c'est sans doute que les patterns utilisés ne sont pas en phase avec la problématique business. Il ne faut pas hésiter à requalifier le subdomain.\nA propos des bounded contexts, comme évoqué précédemment, le mieux est de les choisir larges, puis de les découper quand le besoin se fait sentir, et que la connaissance du domaine augmente.\nFinalement la startup a été profitable assez vite, et a fini par être rachetée par un de leur gros client. Pendant ces années, ils ont été en mode startup : changer les priorités et requirements rapidement, des timeframes agressives, et une petite équipe R&D. Pour Vlad le DDD a rempli ses promesses."}},"/books/learning-to-scale":{"title":"Learning to Scale","data":{"1---introduction#1 - Introduction":"On prend souvent de mauvaises décisions basées sur des idées fausses, alors qu'on pourrait utiliser l'apprentissage pour les corriger.\nExemples :\nUne équipe choisit un raccourci qui impacte une autre équipe et l'oblige à travailler plus et à recruter pour s'en sortir. Personne ne se rend compte de l'origine du problème et du gaspillage global.\nUne commerciale passe son temps à affiner son argumentaire, et n'écoute pas les vrais besoins de ses clients. Elle finit par avoir de mauvais résultats sans jamais savoir pourquoi.\nL'équipe de direction élabore une réorganisation majeure, mais ne se rend pas compte que ça va empirer les choses, et qu'il vaudrait mieux s'appuyer sur les équipes et faire des changements graduels.\nToyota a inventé un modèle permettant de tirer parti de l'intelligence de chacun : le TPS (Toyota Production System).\nElle a commencé dès les années 50, et s'est fait connaître dans les années 80 de par son succès.\nEn dehors de Toyota, ce modèle s'appelle le lean.\nCe modèle a influencé le mouvement agile et le mouvement lean startup.\nLe livre The Lean Strategy, sorti en 2017, constitue une avancée majeure et permet de comprendre le lean comme stratégie d'entreprise globale, plutôt que partielle comme auparavant.\nLe livre Learning to Scale est une forme d'introduction à cette version plus complète du lean.","2---comment-nous-limitons-la-croissance#2 - Comment nous limitons la croissance":"Au moment où l'entreprise commence à grossir fortement, les fondateurs n'ont plus le temps de tout gérer par eux-mêmes.\nEn général, les fondateurs vont essayer d'embaucher des managers pour structurer des rôles de contrôle, mais les employés commettent toujours des erreurs et se démotivent, et l'entreprise se rigidifie.\nLa loi d'airain de Ballé dit qu'en période de forte croissance, les coûts engendrés par la complexité et le travail en silos, augmentent plus vite que le chiffre d'affaires.\nIl s'agit du “syndrôme des grandes entreprises”.\nOn peut le décliner en 4 points :\nPrivilégier les processus plutôt que le client.\nLes personnes du bas de l'échelle appliquent les processus faits par les dirigeants sans réfléchir, même si le résultat est inefficace.\nPréférer les silos au travail d'équipe.\nOn peut penser notamment aux différents chefs de service qui se rejettent la faute plutôt que de collaborer autour d'un but commun.\nNDLR : c'est très bien illustré dans The Phoenix Project.\nRécompenser l'obéissance plutôt que l'initiative.\nOn peut par exemple penser à un PM qui trouve des solutions issues de sa discovery, mais qui voit toutes ses propositions rejetées par son chef qui a déjà ses idées à lui.\nConfondre le leg (legacy) et l'héritage technologique (heritage).\nChaque entreprise qui a à un moment une bonne idée technologique qui perce, finit par elle-même être dépassée par des concurrents qui innovent.\nIl faut savoir abandonner les technologies qui procuraient un avantage concurrentiel hier et qui n'en procurent plus aujourd'hui (legacy).\nExemple : les clients lourds se font dépasser par le web.\nEt il faut savoir identifier une technologie qui reste un vrai avantage.\nLes modèles classiques de management sont basés sur les théories de personnes comme Taylor ou Ford. Mais elles posent des problèmes.\nFaire correspondre un produit uniforme à une clientèle la plus vaste possible permet d'économiser des coûts, mais les clients s'en vont quand la concurrence augmente.\nDécouper l'activité en petites tâches reproductibles permet d'être plus efficace, mais dès que le contexte change, le découpage en question ne correspond plus à ce qu'il faut faire.\nSpécialiser les personnes dans des rôles leur permet d'être plus performants dans ce qu'ils font, mais ils perdent de vue l'ensemble et peuvent agir contre l'intérêt de l'entreprise sans s'en rendre compte.\nLe contrôle centralisé permet d'agir vite, mais quand l'entreprise grossit, les dirigeants perdent le contact avec la réalité du terrain.\nDans le management classique de type Command & Control, on applique un processus en 4D.\nDéfinir (Define)\nL'équipe dirigeante définit les enjeux stratégiques de l'entreprise en s'appuyant éventuellement sur des conseils externes.\nDécider (Decide)\nL'équipe dirigeante définit la stratégie d'entreprise, c'est-à-dire le plan d'action.\nDiriger (Drive)\nL'équipe dirigeante communique le plan aux investisseurs et aux employés, et s'occupe de la mise en place en nommant des personnes pour l'exécution et en définissant des objectifs.\nDémêler (Deal [with consequences])\nAu bout d'un moment, le plan d'action décidé en dehors des réalités du terrain finit par avoir des conséquences néfastes. L'équipe dirigeante pense alors à définir une nouvelle stratégie.\nLe management de type 4D a pour conséquence :\nUne baisse de la motivation.\nUne innovation limitée parce qu'on ne s'appuie que sur quelques personnes (qui sont en plus déconnectées de la réalité du terrain) pour savoir quoi faire.\nLes meilleurs éléments qui partent en burnout.\nDe mauvais résultats financiers.\nLe lean est une alternative radicalement différente des autres approches.\nLe management de type Command & Control est le plus répandu parce qu'il découle d'un penchant humain naturel vers le contrôle.\nD'autres formes de management très différentes ont été expérimentées : en utilisant des mécanismes de récompense, en allant vers une autonomie totale des employés, en mettant en place les entreprises libérées, l'holacratie etc.\nMais toutes ces approches ont en commun d'être toujours sur la notion de contrôle : la question de savoir qui l'exerce et comment.\nLe lean est radicalement différent de toutes ces approches parce qu'il se base sur la manière dont les gens apprennent sur le terrain (et non pas sur une histoire de contrôle donc).\n\"Le lean n'est pas un système de production, c'est un système d'apprentissage.\"\n\"The lean strategy is neither a top-down nor bottom-up approach. Leaders don't forcefully impose their will, and people don't vote to make decisions. The lean strategy isn't about who's in control. It transforms the role of the CEO into someone whose purpose is to nurture a company-wide desire to deal with problems efficiently and adapt. Of course, a lean leader keeps most of the responsibilities any business leader has. You must decide on the major strategic orientations, promote the right people and know who to delegate. However you are much better prepared to make these decisions because of the time spent on the gemba. Changing points of view, going back and forth from a high-level vision to the analysis of detailed issues with the TPS gives you a much richer vision of the company's problems, customer expectations and the real abilities of your team.\"","3---faire-de-lapprentissage-une-stratégie#3 - Faire de l'apprentissage une stratégie":"Pour être une entreprise apprenante, il y a deux composantes :\n1 - Rechercher activement ce que nous ignorons et qui limite nos performances.\n2 - Développer activement les connaissances et compétences de chacun.\nExemple : un CTO arrive dans une entreprise, et la direction met la pression pour délivrer un projet. Il va se concentrer sur les deux aspects d'apprentissage.\n1 - Il cherche ce que les équipes ignorent et qui limite les performances.\nIl commence par les problèmes de qualité : un tiers du temps de dev est passé sur des bugs.\nIl passe du temps avec des devs et des PMs pour identifier l'origine de plusieurs bugs individuellement (plutôt que par statistique).\nIl finissent par identifier que les PM ne prennent pas en compte des edge cases problématiques, et que les devs ont des pratiques de qualité de code qui ne sont pas suffisamment solides.\n2 - Il crée un environnement où le travail quotidien va permettre une acquisition rapide de compétences et de connaissances.\nIl engage le département produit sur la réduction des bugs.\nIl montre aux PMs et à leur responsable, pendant des séances individuelles, comment prendre en compte les cas particuliers dans les spécifications.\nIl met en place une manière pour les devs de faire remonter les soucis dans les specs de manière régulière aux PMs pour établir une discussion entre eux.\nIl travaille avec les Tech Leads sur l'amélioration des pratiques de qualité de code, à partir de leur expérience mais aussi en passant en revue les articles et livres de référence sur ces sujets.\nLes Tech Leads utilisent ces connaissances pour former les devs et trouver des améliorations importantes pour pas trop d'efforts.\nPour que l'apprentissage soit au cœur de l'entreprise, il faut que les employés soient dans un environnement qui les incite à apprendre.\nLa question de quelles compétences sont les plus utiles sur le moment se pose : par exemple pour les développeurs, choisir entre explorer de nouvelles technos ou apprendre à faire du code avec peu de bugs.\nIl faut que le moment de l'apprentissage soit très proche du moment de la mise en pratique.\nIl faut de l'autonomie pour tester les choses par soi-même.\nIl faut de la sécurité psychologique.\nAu management classique Command & Control, le lean vient opposer le management de type Orient & Support.\nLe Command and Control consiste à donner des directives précises, puis à vérifier qu'elles sont exécutées.\nLes employés sont là pour exécuter correctement les ordres et non pas réfléchir. De toute façon, ils en seraient bien incapables.\nExemple : “Les chiffres de vente sont décevants. Chacun travaille de manière différente, donc j'ai repensé l'argumentaire commercial, élaboré un nouveau processus de vente et créé un nouveau modèle de proposition qu'il vous suffit juste de compléter pour gagner du temps. J'ai également augmenté la part variable de votre rémunération afin que vous soyez pleinement motivés. Je vais mettre en place des rendez-vous hebdomadaires avec chacun d'entre vous, d'ici quelques jours, pour répondre à vos questions et vous aider à appliquer ce nouveau processus avec succès.”\nL'Orient & Support consiste à déterminer des objectifs impératifs à atteindre, puis à aider les employés à exprimer la manière de les atteindre.\nLa détermination des objectifs elle-même vient de l'analyse de ce qui se passe sur le terrain, en compagnie des gens qui sont sur le terrain.\nOn part du principe qu'on a recruté des gens compétents et impliqués, qui, dans de bonnes conditions et avec un peu d'aide, pourront aller chercher les compétences et les connaissances dont ils auront besoin pour trouver les solutions.\nExemple : “Nous avons pris du retard sur nos objectifs de vente, nous avons donc besoin de comprendre ce qui se passe. Comme vous le savez, tous les problèmes rencontrés par le client qui achète nos produits se traduisent par un cycle de vente plus long. J'ai repensé notre tableau de ventes afin que chaque commercial puisse se rendre compte du lead time de chaque vente en cours. A partir de demain, je viendrai creuser chaque jour quelques cas de vente afin que nous puissions décortiquer des dossiers spécifiques. Cela devrait nous aider à découvrir pourquoi il est difficile pour certains clients d'acheter chez nous, et ce que nous pouvons faire pour développer une meilleure relation avec eux.”\nCentrer la stratégie autour de l'apprentissage commence par la hiérarchie.\nSi le CEO et l'équipe de direction n'adoptent pas la démarche lean, elle n'aura au mieux qu'un effet marginal dans l'entreprise.\nSi la hiérarchie ne pratique pas l'apprentissage au quotidien à partir du terrain :\nElle fera des erreurs avec de lourdes conséquences.\nLes employés finiront par se démotiver, voyant leurs propositions se faire rejeter par une direction qui a des idées préconçues.\nDans le lean, la stratégie se décline en 4C (4F).\nChercher (Find)\nL'équipe dirigeante va fréquemment sur le terrain pour aider les équipes à résoudre des problèmes du quotidien.\nConfronter (Face)\nElle adopte une “vue d'hélicoptère” pour découvrir les enjeux majeurs.\nL'exercice de l'hélicoptère consiste à faire des va et vient entre les détails des problèmes du terrain, et le niveau stratégique où il s'agit d'avoir une vision d'ensemble.\nCadrer (Frame)\nElle décline ces enjeux au sein des équipes pour que chacune contribue à son niveau à l'effort d'apprentissage de l'entreprise.\nCo-construire (Form)\nLes managers aident les équipes à développer des capacités à trouver des solutions, pour répondre aux enjeux qui les concernent.\nLa stratégie 4C implique d'arrêter de bouleverser l'entreprise sur des coups de tête.\nLes managers ont en général du mal à accepter ce point, parce que jouer aux apprentis sorciers est addictif.\nParmi ce qui rentre là-dedans :\nRéorganiser les services.\nPousser des idées de nouveaux produits.\nDéployer de nouveaux processus de fonctionnement.\nExternaliser des parties de l'activité.\nCôté cadre de fonctionnement pour favoriser l'apprentissage, le lean dispose du modèle TPS (Toyota Production System).\nUne partie des anciens de Toyota l'ont renommé Thinking People System pour mieux communiquer l'intention qu'il y a derrière.\nIl a été éprouvé dans de nombreuses industries, et pour toutes les tailles d'entreprise.\nIl est constitué de 4 parties principales :\nLa satisfaction client.\nRépondre au besoin spécifique de chaque client.\nCréer de nouveaux produits pour apporter de la valeur aux clients.\nLe Juste-à-temps.\nRéduire le lead time, et avoir un flux continu de valeur.\nLe Jidoka.\nRéussir à faire un travail de qualité, et éliminer la cause des défauts dès qu'ils se présentent.\nLe socle de stabilité.\nÊtre dans un environnement qui favorise la contribution et l'apprentissage de chacun sur le terrain.\nLe lean nécessite des mois, voire des années à être maîtrisé du fait de nombreux concepts contre-intuitifs\nMais selon l'auteur ça en vaut la peine, parce qu'il répond efficacement à la plupart des questions de management, et permet d'être efficace tout en créant un environnement agréable pour les employés.","4---votre-périple-lean#4 - Votre périple lean":"Ce qu'il ne faut pas faire (en tant que dirigeant) :\nDéléguer la mise en place du lean.\nLire quelques ressources sur le sujet et se dire qu'on est bon.\nLe lean c'est avant tout de la pratique.\nAppliquer le lean aux autres plutôt qu'à soi.\nMettre en place plein d'outils lean, et s'assurer que les gens les utilisent.\nLe lean est une pratique personnelle, comme le tennis ou le piano.\nIl y a des pratiquants de différents niveaux.\nQuand on est débutant, on n'arrive pas à grand-chose et c'est normal : il faut commencer par des exercices et progresser petit à petit.\nIl faut se lancer dans la pratique, et la compréhension viendra au fur et à mesure.\nAu bout d'un moment on arrive à un plateau, on peut alors lire une ressource plus avancée, ou se faire accompagner par un pratiquant qui a de l'expérience (senseï).\nVu la nature du lean (apprendre au quotidien à partir du terrain), un senseï extérieur ne pourra que tenir le rôle de coach, en donnant des pistes à partir de ce qu'il voit, des idées d'exercice etc. Il ne pourra pas mettre le lean en place dans l'entreprise à notre place.\nOn ne peut pas mettre en place le lean dans l'entreprise en l'imposant comme un process à respecter. Ça ne marcherait pas.\nUne des raisons c'est que le lean nécessite une grande implication pour entrer dans un processus d'apprentissage, et on ne peut pas espérer que tous les employés d'une entreprise aient cette implication en même temps.\nUne des conséquences c'est que le lean ne se met pas en place en quelques semaines.\nLa bonne manière de s'y prendre c'est de commencer soi-même à appliquer le processus en commençant par des exercices (le chapitre suivant en donne).\nOn peut ensuite embarquer les personnes motivées autour de soi, en les aidant à faire la même chose de leur côté.\nAu bout d'un moment, de plus en plus de personnes de l'entreprise pratiquent le lean.\nIl se peut qu'il reste finalement quelques personnes complètement réticentes à cette approche : elles trouveront une meilleure opportunité professionnelle ailleurs.","5---passez-à-la-pratique#5 - Passez à la pratique":"","1---chercher-pour-confronter#1 - Chercher pour confronter":"","commencer-par-le-gemba#Commencer par le Gemba":"Gemba veut dire “l'endroit où les choses se passent réellement”, c'est “le terrain”.\nÇa peut être d'aller voir les clients qui utilisent le produit, les employés qui fabriquent le produit, ou encore même les fournisseurs s'il y en a.\nLa posture à adopter pendant les Gemba walk c'est “Aller voir, demander pourquoi, faire preuve de respect” : il s'agit en grande partie d'essayer d'y apprendre des choses sur la réalité du terrain.\nExercice :\nEn tant que cadre / dirigeant, on réserve des créneaux dans son agenda, de préférence plusieurs fois par semaine, où on va faire un Gemba walk.\nOn va poser des questions axées Orient and Support : vers quoi les gens veulent aller, et en quoi on peut les aider.\nSi on va voir un client :\nOrient : qu'est-ce que vous essayez d'obtenir avec notre produit ?\nSupport : qu'est-ce que vous appréciez dans notre produit ? Qu'est-ce qui vous aiderait davantage ?\nSi on va voir une équipe de delivery :\nOrient : qu'est-ce que vous essayez d'améliorer dans le produit ?\nSupport : qu'est-ce qui est difficile ? Comment je pourrais vous aider ? Quelle est la prochaine étape pour résoudre le problème ?\nOn va ensuite demander à voir la réalité des faits.\nPar exemple, si on a demandé à une équipe de dev de raconter un incident de prod, on peut maintenant demander à voir le code, les specs, et les tests correspondant à l'endroit problématique.\nA une équipe de sales, après les avoir questionnés sur un client manqué, on pourra demander à voir les échanges par mail, les propositions commerciales etc. pour en apprendre plus à partir des faits.\nSi on tombe face à quelque chose qui nous surprend, il faut résister à la tentation Command & Control de dire ce qu'il faut faire pour faire “mieux”.\nAu lieu de ça, il faut chercher à comprendre pourquoi, pour essayer de remonter à la cause du problème, et laisser à l'équipe la possibilité de découvrir elle-même comment le régler.\nLe but n'est pas forcément de résoudre ce problème spécifique, mais plutôt que l'équipe apprenne comment avoir un autre regard sur le problème, pour le résoudre toute seule la prochaine fois.\nEn tant que dirigeant, on doit aussi être attentif aux dérives Command & Control et mauvaises pratiques du genre.\nEst-ce que certains employés sont mis sous pression ? Est-ce qu'ils en sont réduits à suivre des processus au détriment de l'intérêt du client ? Est-ce qu'ils se battent pour l'intérêt de leur département au détriment de l'intérêt de l'entreprise ? etc.\nPour que les employés osent révéler les vrais problèmes, il faut les mettre dans un** climat de confiance** et de sécurité psychologique. Et donc il faut tolérer l'erreur.\nEn revanche, il faut être moins tolérant envers le fait de commettre deux fois la même erreur, pour qu'ils ne s'imaginent pas que c'est la fête non plus.\nNDLR : “La confiance mais pas trop”...\nLes problèmes pertinents à résoudre sont regroupés autour de 4 axes, et sont décrits par le TPS.","gagner-le-sourire-des-clients#Gagner le sourire des clients":"La satisfaction client est un des piliers du lean, elle permet de rester concentré sur ce qui est important, pour éviter de perdre du temps avec des process inutiles, des jeux de pouvoir etc.\nL'idée du lean c'est de traiter chaque client individuellement, avec leurs besoins propres, plutôt que de créer des abstractions représentant des catégories de clients.\nIl s'agit bien sûr pour l'entreprise de répondre aux besoins de l'ensemble des clients, mais en restant concentré sur les besoins individuels on a de meilleures chances d'avoir un produit qui soit plus adapté à l'ensemble des clients.\nExemple chez Theodo : l'équipe sales personnalise son offre en fonction des problèmes de chaque client, et prépare même des mock ups spécifiques pour eux, ce qui lui permet d'augmenter son taux de closing.\nExercice :\nOn peut faire un tableau de type “mur client”, qui liste les problèmes remontés par les clients.\nLe tableau contient les colonnes Date | Client | Problème | Causes probables | Contre-mesure | Résultat.\nL'objectif est de remettre les clients au centre de l'attention, et de mieux connaître leurs besoins.\nIl ne faut pas se jeter trop vite sur les solutions. Et en particulier il faut faire attention à :\nNe pas proposer de solutions avant que tout le monde soit d'accord sur le problème.\nNe pas essayer de deviner des problèmes qui ne sont peut-être pas là.\nBien examiner les conséquences négatives des solutions avant de les adopter.\nUne méthode pour résoudre efficacement les problèmes est le cycle PDCA (Plan, Do, Check, Act). Elle a des airs de méthode scientifique.\n1 - Plan :\n1.1 - On clarifie d'abord le problème, et on se base sur les faits en analysant le Gemba.\n1.2 - On cherche une cause racine probable.\nOn cherche à répondre à la question : Quelle est l'erreur de raisonnement que nous commettons sans cesse, et qui est à l'origine de ce problème ?\nIl ne faut pas s'arrêter aux causes évidentes, mais chercher plusieurs possibilités, et creuser chaque cause pour arriver à son origine, en posant à chaque fois la question “Pourquoi ?” .\nOn sait qu'on est arrivé au bout des “Pourquoi ?” quand la réponse est quelque chose comme “On pensait que ça marchait comme ça, mais en fait…”, ou “On ne pensait pas que ça pouvait avoir cet effet là…”.\n1.3 - On trouve le plus petit changement possible qui permettrait de remédier au problème.\n2 - Do : on le met en place, avec l'accord de tout le monde.\n3 - Check : on vérifie si la situation s'améliore et dans quelle mesure.\n4 - Act : on fait le point sur ce qu'on a appris, et on voit s'il faut une nouvelle petite itération PDCA ou si le problème semble résolu durablement.\nLe PDCA est aussi un excellent outil de teamwork, parce qu'il permet de se concentrer sur l'analyse des causes du problème, plutôt que de passer essentiellement du temps à donner des solutions et se battre sur qui a le plus de pouvoir pour imposer la sienne.\nD'ailleurs on constate qu'on s'améliore sur le PDCA quand les discussions à ce sujet sont animées mais sans être “enflammées”.\nPour que la résolution de problème donne lieu à un apprentissage, il faut :\nQue la personne qui a fait le travail le retranscrive par écrit, dans un tableau du type “mur client”.\nEn rediscuter plus tard avec la personne.\nLes problèmes à chercher en priorité sont d'ordre opérationnel, et non pas de relations entre individus et équipes, ou de motivation.\nLa raison est que la cause du problème est souvent de ce côté, et que le versant relationnel / émotionnel n'en est qu'une conséquence qu'il faut traiter une fois qu'on a résolu le problème de fond.\nExemples :\nUn commercial démotivé, mais qui ne sait de toute façon pas rédiger de bonnes propositions commerciales.\nDeux équipes qui n'arrivent pas à travailler ensemble, mais qui ne savent de toute façon pas très bien ce que le client veut.","créer-un-flux-de-valeur#Créer un flux de valeur":"Le lead time est le temps entre la demande ou le besoin du client, et la livraison au client.\nPar extension c'est le temps qu'un élément met à aller du début à la fin de la chaîne de valeur.\nEt c'est donc aussi la représentation de tous les problèmes qui peuvent avoir lieu le long de cette chaîne.\nLa réduction du lead time est une des obsessions du lean.\nElle permet de faire remonter les problèmes pour les traiter, et offre des occasions d'apprentissage quotidiens pour tout le monde.\nElle permet de travailler en équipe, avec un objectif cross-silos.\nElle permet de diminuer les coûts.\nElle permet de satisfaire les clients en s'adaptant à leurs besoins rapidement.\nLe lean voit l'entreprise comme un ensemble de flux, quels que soient les départements.\nExemples :\nL'achat d'un client passe par la sélection d'un produit, l'achat, l'expédition, le transport, la livraison.\nL'approvisionnement d'un produit suit lui aussi un ensemble d'étapes.\nEt pareil pour le recrutement : on définit le poste, on cherche des candidats, on fait passer des entretiens, on négocie etc.\nExercice :\nOn peut choisir un flux dont on est responsable au sein de l'entreprise, et rendre visuel le lead time des éléments de ce flux : il s'agit du Kanban.\nLe Kanban n'est pas un outil qui organise la production, mais un outil d'apprentissage qui permet d'analyser visuellement quel élément stagne et pourquoi, pour aider à réduire le lead time.\nIl peut contenir différentes informations en fonction de ce que l'équipe veut apprendre à ce moment.\nLe principe c'est qu'un client (service interne ou client externe à l'entreprise) commande quelque chose à une équipe. On a alors besoin de quelques informations simples :\nQui est le client ?\nQu'est-ce qui est commandé ?\nA quel moment la commande a été émise ?\nOn peut le représenter directement sous la forme du lead time (durée depuis la création de la tâche) si le support digital permet de maintenir la valeur à jour.\nQui s'en occupe ou va s'en occuper ?\nEn regardant le Kanban, on pourra :\nSe demander si tel ou tel élément sur lequel l'équipe travaille est pertinent dans le contexte.\nPrendre quelques cas individuels de tâches qui stagnent, et essayer d'analyser pourquoi en reconstituant les événements.\nPour ça, on peut utiliser le PDCA, avec un tableau du type Date | Client | Problème | Causes probables | Contre-mesure | Résultat.\nLe plus efficace est de résoudre les problèmes avec 1 ou 2 personnes sur le Gemba.\nLe tableau devrait être affiché pour que l'équipe l'ait sous les yeux, et pour que les cadres puissent s'appuyer dessus pendant des Gemba walks.\nLes membres de l'équipe peuvent présenter les problèmes qu'ils ont résolus comme ça, par exemple pendant leur daily.\nL'idée c'est de résoudre des problèmes tous les jours.\nLe lead time est composé de 3 type de temps :\nLa stagnation : c'est en général 80% du temps, la tâche est laissée sans qu'on travaille dessus.\nLe contact : une personne ou une machine travaille sur la tâche. On peut le réduire en améliorant la manière dont on fait les choses.\nL'ajout de valeur : c'est le moment, pendant le travail, où il y a une transformation qui va apporter plus de valeur au client. C'est cette partie-là qu'on veut maximiser.\nLes variations d'activité doivent être réduites.\nPour les raisons suivantes :\nLa créativité qu'on met en œuvre pour répondre à la variation de l'activité n'est pas vraiment utile en elle-même. On peut tout autant faire preuve de créativité pour réduire ces variations.\nSur le long terme, la motivation vient du fait d'améliorer une activité stable, plutôt que de réagir en permanence à quelque chose de chaotique.\nUne entreprise flexible c'est une entreprise qui accepte des demandes variables des clients, mais qui est quand même capable de les traiter en interne par des processus stables.\nPour les réduire, on peut s'y prendre en 3 étapes :\n1 - réduire la surcharge : que ce soit pour les personnes ou les machines, quand elles sont au-delà de leurs capacités, elles deviennent moins performantes.\n2 - réduire la variabilité : on a du mal à faire des améliorations sur des choses qui varient trop.\nÇa peut être une charge de travail différente d'un jour à l'autre, des équipes différentes qui font la même tâche d'un jour à l'autre, des façons de travailler différentes d'un jour à l'autre etc.\n3 - réduire le gaspillage : toutes les activités qui n'apportent pas de valeur et dont on pourrait se passer. Ça peut aussi être le fait d'attendre, ou de faire des context switch.\nExercice :\nPour pouvoir mesurer le lead time de nos flux et essayer de le réduire, il faut être au clair sur la différence entre une tâche et un produit dans notre cas.\nLe lead time se mesure sur un produit disponible pour le client.\nMais la création d'un produit est en général composée de plusieurs tâches.\nExemples :\nLe produit qu'on crée peut être un rapport, et l'une des tâches peut être de collecter des données.\nUne équipe marketing peut créer des contenus multimédias, et parmi leurs tâches il peut y avoir l'analyse des campagnes actuelles, la recherche d'idées, la rédaction de texte etc.\nOn peut commencer par lister les produits qui sont créés dans notre département, et calculer le volume moyen par unité de temps, puis se concentrer sur l'activité qui a le plus gros volume.\nPar exemple : on fait en moyenne 1 newsletter et 4 vidéos par semaine : on va commencer par réduire le lead time des vidéos.\nCe qu'on cherche à atteindre c'est le juste-à-temps : que chaque personne dans la chaîne de valeur se passent la main exactement au moment où le suivant a fini sa tâche courante.\nLe but c'est qu'il n'y ait aucune personne qui reste en attente, ni de tâches non traitées qui s'accumulent.\nCa passe par 3 techniques :\nLe Takt time : on aligne toutes les activités sur une même cadence.\nExercice :\nOn peut calculer le Takt time de son activité en divisant la durée d'une itération par le nombre de “produits” qui sont sortis.\nCe Takt time va correspondre à la durée moyenne d'une étape, et donc on va pouvoir comparer dans un graphique la durée de chaque étape à cette durée moyenne.\nSi une tâche met en moyenne moins de temps que le Takt time, alors les personnes qui la font se retrouve sans rien à faire.\nSi une tâche met plus de temps, alors les personnes qui la font se retrouvent avec des tâches qui s'accumulent et qu'ils n'arrivent pas à traiter.\nPar exemple, si une tâche de discovery prend deux fois moins de temps dans une itération qu'une tâche de delivery, il faudra probablement deux fois plus de personnes sur la delivery pour que les cadences soient alignées.\nLe flux continu pièce à pièce : on fait circuler les tâches d'une étape à l'autre, une par une en continu.\nIl faut éviter de faire les tâches par lot.\nExemple : l'équipe de discovery fait une spécification très détaillée avant de passer la main à l'équipe de delivery.\nOn peut alors moins vite mettre les produits dans les mains du client.\nLes défauts sont noyés dans la masse et rendent l'analyse plus difficile.\nOn remarque aussi moins les problèmes de collaboration vu qu'il suffit de passer sur une autre tâche dès qu'on est bloqué.\nExercice :\nTravailler en flux pièce-à-pièce veut dire n'avoir qu'une tâche en cours.\nOn peut donc rendre visible dans le Kanban les cas où une personne a plusieurs tâches en cours (par exemple elle était bloquée sur l'une et a basculé sur la 2ème).\nEt ensuite on peut travailler avec les membres de l'équipe sur les raisons de ces tâches commencées mais non terminées, et comment faire pour y remédier.\nLe système de flux tiré : quand le flux continu n'est pas possible, on ne va produire que quand l'étape suivante a besoin de plus.\nExercice :\nA chaque étape, la personne maintient un stock de “produits” sur lesquels elle a fait sa partie et qui ne sont pas encore traités par l'étape suivante, et au bout d'un certain nombre elle arrête d'en faire plus parce que ce serait contre-productif.\nLe nombre d'éléments maximal dans le stock est à définir entre les personnes de chacune des deux étapes.\nEt d'ailleurs ce mécanisme doit aussi permettre d'amorcer une communication sur ce que la personne d'après attend en terme de qualité, ce qui lui faciliterait la vie etc.\nQue ce soit pour le Takt time ou le lead time, il est probable qu'on tombe sur une certaine variabilité des durées.\nPour autant il ne faut pas s'arrêter à ça, mais creuser les raisons de ces variations pour obtenir une compréhension fine de ce qui se passe.\nLa mise en place de chacun de ces mécanismes devrait révéler des problèmes et soulever des questions. Il faudra les régler un par un.\nCette phase de mise en place est importante, parce que l'objectif reste l'apprentissage et le développement des individus et non pas juste la mise en place d'un processus.","arrêt-au-défaut#Arrêt au défaut":"L'autre pilier de la maison TPS c'est le Jidoka. Il est composé de 3 parties :\nAndon : n'importe quelle personne (ou même une machine) peut arrêter la chaîne de production s'il voit une situation anormale, et faire intervenir le management pour aider à éliminer la cause racine.\nExemples de situations anormales :\nUne erreur dans le texte de la page d'accueil qu'on construit.\nUn serveur qui utilise son processeur à 100%.\nDes détails qui manquent dans le cahier des charges.\nLe but est d'aider la personne qui constate le problème, mais aussi de porter l'attention de chacun sur la qualité à toutes les étapes.\nLe mécanisme peut être un simple message dans un système de chat.\nLe principe :\n1 - Un membre de l'équipe ou une machine remarque une situation anormale.\n2 - Il arrête la production et signale le problème.\n3 - Le team leader aide la personne à résoudre le problème pour relancer la production.\nOu si c'est une machine, un membre règle le problème avec si besoin l'aide du team leader.\nL'intervention du team leader a surtout pour but d'aiguiller la personne, de l'aider à apprendre en résolvant elle-même le problème.\n4 - Le team leader / la personne responsable travaillent à éliminer la cause racine.\nLe lean recommande de traiter les problèmes le plus tôt possible, plutôt que de les laisser empirer.\nPour que ce système fonctionne tout en étant capable de continuer à produire, il faut des personnes dédiées à aider à répondre aux appels Andon : il s'agit des team leaders.\nIl faut un team leader tous les 4 à 8 personnes formant une équipe.\nEt tous les 4 à 8 team leaders, il faut un team leader de team leaders, et ainsi de suite si l'entreprise est grande.\nIl ne s'agit pas d'une hiérarchie classique : les team leaders du niveau 2 soutiennent les team leaders dont ils sont les formateurs, et de même pour les team leaders vis-à-vis de leur équipe.\nLes team leaders ne sont pas des managers au sens classique, leur rôle est essentiellement opérationnel.\nIls doivent s'assurer du bon fonctionnement de l'équipe et de la mise en place d'une culture de l'excellence technique. En quelque sorte ils sont l'équivalent des senseï lean en interne.\nIls doivent passer au moins la moitié (et parfois jusqu'à 90%) de leur temps sur leurs activités de team leader :\nVisualiser : s'assurer que les membres de l'équipe participent aux objectifs, utilisent le Kanban etc.\nSoutenir : répondre aux appels Andon de l'équipe.\nFaire remonter : escalader des appels Andon qu'ils sont incapable de résoudre.\nFormer : faire des sessions de formation pour l'équipe chaque semaine.\nMaintenir : s'assurer que l'environnement de travail de l'équipe est optimal.\nAméliorer : mener la démarche d'amélioration et d'apprentissage au quotidien avec l'équipe (Kaizen).\nLes tests sont faits au plus près de la réalisation du produit : c'est l'équipe qui fait qui est responsable de la qualité.\nLe test final n'est alors plus là pour vérifier que le produit final est bon, mais plutôt pour apprendre comment améliorer la qualité au moment de la fabrication.\nIl faut aussi que ce soit l'équipe qui a fait l'étape problématique qui règle le problème, pour apprendre et faire mieux la prochaine fois.\nExercice :\nLes bacs rouges sont utilisés en usine pour y mettre les articles défectueux, pour les analyser plus tard.\nOn peut reprendre le même principe avec des espaces “bacs rouges” numériques, où on met ce qui a pu causer un appel Andon, et qu'on examine avec son team leader dans un but d'apprentissage et pour éliminer la cause racine.\nPoka yoke : créer des systèmes qui rendent le travail moins pénible.\nDans le cas où on se rend compte que les problèmes viennent de personnes qui font preuve d'inattention, on peut être tentés de mettre en place des contrôles pour s'assurer qu'elles font attention.\nIl faut y résister parce que c'est une solution peu respectueuse des employés, et parce que ça introduirait une charge cognitive supplémentaire liée à l'activité de contrôle.\nAu lieu de ça, il faut mettre en place des mécanismes qui empêchent l'erreur.\nExemples :\nDes câbles USB qui ne peuvent pas être branchés dans la mauvaise direction.\nDes widgets de date qui ne permettent pas de sélectionner de date invalide.\nDu typage / linting dans le code, pour éviter des erreurs basiques.\nSéparation homme / machine : construire des outils qui mettent en valeur les personnes au lieu de les contrôler.\nL'automatisation peut apporter de grands gains de productivité, mais elle peut aussi servir à maltraiter les humains.\nPar exemple, quand les humains doivent passer des heures à observer que des machines ne s'arrêtent pas ou ne fassent pas de défauts.\nL'approche lean est de faire exécuter les tâches répétitives, rapides et précises aux machines, et les situations imprévues et les améliorations aux êtres humains.\nLes étapes sont :\n1 - Les humains améliorent leur activité jusqu'à la stabiliser et la rendre automatisable.\n2 - Ils automatisent les parties répétitives.\n3 - Ils mettent en place un mécanisme automatique pour que la machine prévienne en cas d'anomalie, et ils peuvent se consacrer aux améliorations et à la gestion des imprévus.\nExercice :\nPendant un Gemba walk, se demander si les outils que l'entreprise met à disposition aident les employés à faire leur travail ou sont là pour les contrôler.","créer-une-base-de-stabilité#Créer une base de stabilité":"Pour que les employés restent motivés, il faut un environnement stable et non pas chaotique, pour ça il y a deux piliers.\n1 - Reprendre le contrôle sur son environnement de travail.\nSi l'environnement de travail de tous les jours est chaotique, et qu'ils n'ont pas la possibilité de régler ces problèmes, les employés vont perdre la confiance dans le management et l'entreprise.\nExemple : un employé du service client essaye d'aider un client, mais la qualité du téléphone est mauvaise et le CRM est hyper lent.\nCa passe par :\nAvoir l'aide du team leader et de la direction pendant ses Gemba walks pour résoudre les problèmes d'environnement de travail.\nTPM (Total Productive Maintenance) : faire soi-même la maintenance des équipements qu'on utilise.\nS'approprier son espace de travail avec la démarche 5S.\nÇa peut être des outils physiques, mais pas seulement : pour un développeur le code va faire partie de l'espace de travail.\nLes 5S sont :\nSort : savoir ce qui est plus ou moins utilisé.\nStraighten : faire en sorte que les choses les plus utilisées soient à portée de main.\nShine : arranger visuellement l'espace de travail pour que les anomalies soient immédiatement visibles.\nStandardize : faire en sorte que l'espace de travail revienne à la normale après chaque séance.\nSustain : rester disciplinés en tant qu'équipe pour appliquer les 4 premiers principes de manière durable.\nExercice :\nOn peut commencer par s'interroger sur la liste des éléments du 5S, et demander ensuite à d'autres membres de l'équipe ce qu'ils font.\n2 - rendre le travail varié et intéressant\nHeijunka : on crée un plan de production lissé représentant le client idéal.\nOn a deux problèmes :\nLes demandes client fluctuent, donc si on y répond tel quel on va créer des variations de charge de travail dans les équipes.\nSi on travaille sur un seul produit à la chaîne, on sera plus efficace mais le travail sera ennuyeux, et les délais pour envoyer au client plus longs.\nOn va donc créer un modèle de client qui achète la quantité moyenne du mix de ce qu'achètent les clients, lissé dans le temps, et l'utiliser pour guider le rythme de production.\nIl faut le combiner au Kanban à flux tiré, et on obtient une production fluide, qui va s'améliorer avec l'utilisation de l'Andon et de l'apprentissage permanent.\nExemple : chez Theodo, le recrutement est lissé dans le temps, de manière à embaucher toute l'année au lieu d'embaucher par pics dès qu'on fait rentrer de l'argent.\nExercice :\nA partir des informations de besoin client, on peut créer un Heijunka box, qui est un tableau avec les produits en lignes, et des éléments temporels en colonnes (par exemple “lundi, mardi, mercredi etc.).\nDans chaque colonne on coche une case en mettant un trait, avec l'idée d'alterner les activités pour l'équipe.\nLes standards sont là pour aider les employés à distinguer les situations normales des situations anormales.\nIl faut résister à la tentation d'en faire quelque chose de prescriptif qui est là pour les contraindre, mais plutôt faire en sorte que le standard soit là juste pour guider et laisser la personne utiliser son intelligence.\nExemple de standard “Command & Control” à ne pas faire : une liste d'étapes détaillées à suivre pas à pas pour créer une page d'accueil.\nExercice :\nD'abord on peut lister les 10 compétences essentielles que l'équipe doit connaître.\nEt ensuite on peut définir avec l'équipe les situations normales des situations anormales.\nDans certains cas, ça peut donner lieu à un standard qui peut être une feuille de papier (maximum), ou un code de référence qui montre un formatage par exemple.\nLe contenu d'un standard en mode “Orient & Support” est en général composé de :\nUn paragraphe qui indique l'intention.\nLes points clés.\nLes erreurs classiques.","2---confronter-pour-cadrer#2 - Confronter pour cadrer":"Dans la phase Chercher, on a pu analyser l'entreprise en profondeur, et trouver les problèmes qui empêchent d'aller vite, faire un travail de qualité etc.\nDans la phase Confronter il s'agit de revenir sur ce qu'on a trouvé, et d'accepter le feedback, surtout celui qui nous amène à reconsidérer une vision erronée des choses.\nLa première étape est de chercher l'éléphant qui se cache sous les yeux de tous, et dont personne ne veut parler.\nExemples d'éléphants en interne :\nNous avons perdu notre avantage compétitif au fil du temps, la plupart de nos équipes font du travail de bas niveau, sans valeur ajoutée, ce qui conduit les clients à nous considérer comme un choix bas de gamme.\nNous sommes à la traîne car le CEO est un commercial compulsif qui fait souffrir l'équipe R&D depuis des années avec ses demandes de fonctionnalités aléatoires.\nExemple d'éléphant qui vient de causes extérieures :\nLes jeunes générations sont moins intéressées par le fait de posséder une voiture (pour une entreprise qui fabrique des voitures).\nEnsuite, il faut intensifier la collaboration entre cadres dirigeants.\nLes dirigeants de chaque département ont chacun leurs objectifs, et donc vont avoir tendance à tirer la couverture vers leur département.\nC'est le rôle du CEO de les rassembler autour d'un objectif commun désirable, et faire en sorte d'instaurer une collaboration où chacun aide les autres à réussir.\nPour que les objectifs soient clairs, il est préférable de les rendre visuels.\nExercice :\nL'Obeya consiste à réunir l'équipe dirigeante chaque semaine dans une grande pièce, avec de grands visuels qui servent de base à des études de problèmes.\nC'est un lieu d'apprentissage, pas un lieu de planification et de supervision (ce qui le transformerait en du Command & Control).\nOn peut y faire de l'exploration en profondeur d'un problème, avec toujours le client au centre de l'attention.\nÇa peut être un des collaborateurs qui présente un PDCA terminé, avec les apprentissages qu'il en tire.\nPour ce qui est des visuels, un exemple de ce qu'on peut y trouver :\nLes challenges : quels sont nos objectifs pour cette année, pour les années qui viennent ?\nLes clients : qu'est-ce qu'ils nous disent ? Comment les aider ?\nLes indicateurs de performance : comment mesurer nos progrès ?\nUne place pour ce qui se passe dans chaque département : quels changements sommes-nous en train de mener dans l'entreprise ?\nDans la stratégie lean, les objectifs tournent autour de thématiques éprouvées.\nIl s'agit de chercher à réduire de moitié ce qu'on ne veut pas, ou doubler ce qu'on veut.\nLes thématiques sont :\nLa sécurité : diviser le nombre d'accidents, diviser par deux le taux de turnover.\nLa qualité : diviser par deux le nombre de défauts, le nombre de demandes de support client.\nLe lead time : diviser par deux les délais de fabrication.\nLe coût : doubler la productivité (quantité de produits quotidiens divisé par le nombre total d'employés).\nL'environnement : diviser par deux l'empreinte environnementale.\nL'aspect purement financier ne fait pas partie des objectifs stratégiques, parce qu'il résulte de toute façon des autres objectifs opérationnels et qu'il est à la traîne.\nLa qualité permet de diminuer les coûts, et de rendre le client satisfait, donc ça a un impact direct sur l'aspect financier.\nLa réduction du lead time a de nombreux bénéfices qui ont des retombées financières : diminution des coûts de production, diminution des problèmes qu'on fait remonter, décision d'achat plus rapide du client.\nExercice :\nLe rapport financier principal dans une entreprise lean est assez différent d'une entreprise classique : il s'agit du compte d'exploitation par flux de valeur.\nIl tient dans une simple feuille A4.\nC'est un outil pratique pour un dirigeant pour faire l'exercice de l'hélicoptère, et choisir le prochain endroit où faire son Gemba walk.\nLe rapport contient un tableau avec en colonne les mois, et en lignes :\nLe chiffre d'affaires\nL'EBITDA\nLa trésorerie\nEt ensuite une ligne pour chaque aspect stratégique. Par exemple :\nPourcentage de turnover des employés (sécurité).\nPourcentage de résiliation de clients (qualité).\nNombre d'appels client (qualité).\netc.","3---cadrer-pour-co-construire#3 - Cadrer pour co-construire":"Les dirigeants commencent par rassembler les employés autour de deux questions qui vont constituer un défi d'apprentissage :\n1 - Comment pouvons-nous contribuer à l'objectif général ?\n2 - Qu'avons-nous besoin d'apprendre, au travers d'expérimentations PDCA, pour réussir ?\nIl ne faut surtout pas donner un plan de route pour chaque service, sinon on retomberait dans le Command & Control.\nL'étape suivante c'est de construire un consensus avec l'exercice “attrapper la balle”.\nL'alignement ne va se faire ni d'en haut, ni d'en bas, mais du résultat d'une négociation globale.\nLes dirigeants proposent un objectif global.\nLes team leaders et leurs équipes contribuent : “voici les gains de performance et les défis d'apprentissage que nous pouvons apporter à notre niveau”.\nLes objectifs fixés par les dirigeants et ceux proposés par les équipes doivent être mesurables.\nPar contre il faut qu'ils soient orientés “apprentissage”, c'est-à-dire laisser de la place pour que les équipes trouvent elles-mêmes des solutions, et non pas constituer un plan d'action.\nExemples d'objectifs Command & Control :\nPublier 10 annonces sur les 5 sites d'emploi les plus visibles.\nFournir le nouveau process global des réclamations.\nUn événement de team building trimestriel par service.\nExemples d'objectifs Orient & Support :\nFaire une proposition aux candidats moins de 5 jours après le premier contact.\nRéduire le lead time des réclamations de 50%.\nAtteindre une note de 4 / 5 de satisfaction des employés.\nConcernant les OKR, selon l'auteur ils peuvent être adaptés au lean, à condition qu'on ait en tête une perspective Orient & Support quand on les définit.\nMais on peut aussi en faire un outil de Command & Control.\nL'auteur propose d'ailleurs de les renommer OKL (Objectives & Key Learnings) pour que l'intention soit plus claire.\nPour éviter que les équipes soient complètement perdues sur des objectifs qu'elles ne savent pas comment faire avancer, le lean propose tout simplement le cadre du TPS.\nIl s'agit de l'ensemble des outils de ce chapitre : Andon, bacs rouges, Takt, Heijunka, flux pièce à pièce, réduction du lead time etc..\nOn priorise et résout petit à petit les problèmes qui font avancer notre objectif.","4---co-construire-pour-chercher#4 - Co-construire pour chercher":"Régler les situations anormales n'est pas suffisant, il faut aussi faire preuve de créativité pour améliorer la situation normale : c'est le principe du Kaizen.\nLe Kaizen se fait au quotidien, par de petites améliorations.\nC'est le rôle du team leader de trouver des sujets d'amélioration pour l'équipe.\nIl faut que le sujet soit motivant, pertinent par rapport à l'activité quotidienne de l'équipe, et atteignable en quelques jours ou semaines maximum.\nExemple de sujet de Kaizen : réduire le temps de réponse d'une application.\nIl participe à rendre le travail plus intéressant.\nIl s'agit là aussi d'utiliser le PDCA, mais en cherchant à améliorer le standard actuel, en faisant preuve de créativité.\nLe Kaizen courant doit être affiché sur un mur, avec les 6 étapes bien visibles :\n1 - Définir le potentiel d'amélioration : une petite avancée, mais qui est quand même enthousiasmante.\n2 - Analyser les méthodes actuelles : on analyse les pratiques de travail actuelles en détail pour trouver le principal point de blocage qu'on peut améliorer.\n3 - Produire des idées originales : on trouve des idées, et on n'hésite pas à regarder ce que fait le reste de l'industrie.\n4 - Développer un plan de mise en œuvre : l'idée c'est d'essayer la solution le plus vite possible, et d'avoir une manière d'évaluer les résultats.\n5 - Mettre en place le plan.\n6 - Évaluer la nouvelle méthode :\nOn détermine ce qui fonctionne ou non, et si ça a marché, comment ne pas retourner en arrière.\nOn va aussi présenter notre apprentissage à d'autres équipes.\nQuand une équipe obtient des améliorations qui marchent, il faut résister à la tentation de vouloir la généraliser à toute l'entreprise, sinon on va retomber dans le syndrome des grandes entreprises où les gens appliquent ce qu'on leur dit sans comprendre.\nIl faut plutôt considérer qu'on transmet un processus d'apprentissage et non pas un savoir.\nOn va donc présenter le tableau PDCA qui résume les étapes clés qui ont permis l'apprentissage, et laisser aux autres équipes la possibilité d'expérimenter par elles-mêmes si elles le veulent.\nConcernant les features produit, elles sont souvent le résultat de nombreuses personnes qui poussent chacune leur idée.\nCa donne des problèmes :\nLes devs se mettent à implémenter de nombreuses features, rendant la maintenance du produit difficile, à mesure qu'il devient gros.\nLa plupart de ces features ne répondent à aucune problématique des clients, mais viennent juste de l'idée des différents cadres de l'entreprise.\nVu que les features sont le résultat d'une négociation entre diverses personnes, elles n'ont pas de cohérence entre-elles.\nLa solution du lean est d'avoir un ingénieur en chef du produit.\nÇa peut être le CTO, le CEO, un PM, un PO etc. peu importe son titre.\nIl doit avoir une passion pour le produit et la confiance de la direction.\nIl définit une vision produit cohérente, et qui est en accord avec les besoins des clients.\nIl met tout le monde d'accord, et s'assure qu'il y ait une bonne collaboration autour du produit.\nIl s'assure que le produit avance par petites itérations, synchronisées au rythme du Takt.\nExercice :\nOn peut créer un tableau avec chaque produit en lignes, et des unités de temps en colonnes : on va alors mettre des traits à chaque release.\nL'équipe produit (ou l'équipe d'ingénieurs) se voit confier un objectif de type Orient & Support, qui va les amener à mettre en œuvre un apprentissage PDCA pour trouver la meilleure solution.\nExemple d'objectif Command & Control dans ce cas : “Ajouter un système de favoris”.\nExemple en mode Orient & Support : “Accéder rapidement aux pages fréquemment utilisées”.\nExercice :\nPour réfléchir à la prochaine version du produit, on peut utiliser une matrice QFD (Quality / Function Deployment).\nOn crée un tableau avec :\nEn lignes la liste des préférences / besoins des clients, par exemple “Chargement rapide”, “Ne pas manquer de nouveaux emails”.\nEn colonnes les solutions techniques qu'on peut mettre en place, par exemple “Mises à jour d'arrière plan“, “Vue prioritaire”.","6---la-stratégie-lean#6 - La stratégie lean":"Chaque cycle 4C permet à l'entreprise de s'adapter aux évolutions du marché.\nMais la question fondamentale du lean reste : “Qui doit apprendre quoi pour que l'entreprise réussisse ?”.\nC'est une vraie question que l'équipe dirigeante doit traiter régulièrement.\nExercice :\nOn peut créer un tableau avec une ligne pour chaque employé, et en colonnes les apprentissages, par exemple :\nNom : Suzanne.\nAutonome pour : élaborer des pages d'accueil et des campagnes publicitaires.\nProchain développement : élaborer et coordonner des campagnes marketing multicanal complètes.\nDéfi : améliorer les ventes en ligne de 20% par rapport à l'année dernière sur la campagne de Noël.\nLa trajectoire de chaque employé est unique, les managers doivent aider à la développer dans sa singularité, de la même manière qu'il faut prendre en compte les besoins individuels de chaque client."}},"/books/monolith-to-microservices":{"title":"Monolith to Microservices: : Evolutionary Patterns to Transform Your Monolith","data":{"1---just-enough-microservices#1 - Just Enough Microservices":"Les microservices sont un type particulier de service-oriented architecture (SOA).\nIls exposent une API via le réseau, donc forment une architecture distribuée.\nIl sont déployables indépendamment :\nIl s'agit d'être en mesure de modifier et déployer un seul service sans toucher aux autres.\nLe conseil de l'auteur est d'effectivement déployer les services indépendamment, plutôt que le tout ensemble en espérant une indépendance théorique.\nIls sont organisés autour d'un business domain.\nLe but est de rendre les changements affectant plusieurs microservices le plus rare possible, et favoriser les changement à l'intérieur du microservice.\nIls gardent la base de données privée, et ne l'exposent que via une API.\nPartager la DB est une des pires choses à faire pour avoir une déployabilité indépendante.\nNe pas la partager telle quelle permet de décider ce qu'on partage et ce qu'on ne partage pas, et aussi de garder une API publique stable tout en étant libre de faire des changements en interne.\nL'exemple utilisé dans ce livre est une entreprise de vente de CD de musique.\nElle a une application organisée en 3 couches techniques : UI, backend, DB.\nChaque couche est sous la responsabilité d'une équipe : équipe front, équipe back, équipe DB.\nEt d'ailleurs cette l'architecture découle probablement de l'organisation des équipes cf. loi de Conway.\nOn a donc une forte cohésion au niveau technique : s'il faut faire un travail sur un aspect technique (par exemple moderniser la UI), une seule équipe sera impactée.\nMais on a une faible cohésion par domaine business, puisque l'ajout d'une fonctionnalité nécessite l'intervention et la coordination de 3 équipes.\nA l'inverse on peut imaginer une architecture organisée autour des domaines, avec un bout de UI, un bout de backend et un bout de DB chacun, et sous la responsabilité d'équipes pluridisciplinaires : c'est comme ça qu'on va organiser nos microservices.\nLes microservices ont de nombreux avantages, et il s'agit de comprendre lesquels on cherche à obtenir en priorité pour orienter notre décomposition de monolithe.\nLa possibilité de scaler différemment des parties du système, et d'obtenir de la robustesse (le système peut continuer à opérer même si une partie est down).\nLa possibilité d'utiliser différentes stacks technologiques et de les faire communiquer ensemble.\nLa possibilité pour plusieurs équipes de travailler sur le système sans se marcher dessus.\nParmi les désavantages :\nLes problématiques des systèmes distribués : la communication réseau étant significativement plus lente que la communication in-process, et les paquets pouvant se perdre, on doit faire attention à beaucoup plus de choses.\nLes transactions deviennent problématiques.\nLes microservices arrivent avec leurs technologies spécifiques à maîtriser, qui peuvent causer bien plus de problèmes que les systèmes classiques si elles sont mal utilisées.\nLa UI ne doit pas être mise de côté dans la décomposition : si on veut pouvoir déployer rapidement des features complètes, il faut la décomposer elle-aussi pour qu'elle corresponde avec les services côté backend.\nL'auteur conseille de ne pas adopter une nouvelle stack technologique pour faire la migration vers les microservices. La migration en elle-même est déjà assez difficile, il vaut mieux garder les outils qu'on connaît dans un premier temps.\nA propos de la taille des microservices :\nC'est un des critères les moins importants, surtout quand on débute avec.\nIl vaut mieux s'intéresser d'abord à la question de savoir combien de microservices on sera capable de gérer dans l'organisation, et comment faire en sorte de ne pas trop les coupler.\nIl cite Chris Richardson qui parle d'avoir des microservices avec de petites interfaces.\nNDLR : c'est par cette idée que Vlad Khononov caractérise principalement les microservices dans Learning Domain Driven Design.\nL'idée initiale des microservices était de les avoir si petits qu'on pourrait facilement les recoder pour les remplacer (par exemple dans une techno qui permette plus de performance/scalabilité), mais l'auteur sous-entend que ce n'est plus vraiment un critère essentiel, en tout cas qui fait consensus.\nCôté ownership, l'architecture en microservices favorise le modèle où les équipes tech/produit sont au contact du client, et sont supportées par d'éventuelles équipes transverses.\nCa s'oppose au modèle plus traditionnel où le \"business\" gère la relation avec les clients, et où les développeurs sont dans un silo à part, sans ownership réel sur un business domain de bout en bout.\nLe terme monolith désigne ici l'unité de déploiement.\nLe single process monolith : il s'agit d'une app single-process, qu'on peut éventuellement dupliquer pour des raisons d'availability.\nEn général le monolithe va au moins communiquer avec une DB, formant un système distribué très simple.\nÇa représente l'essentiel des projets qui cherchent à migrer vers du microservice, donc l'auteur va se concentrer sur ça.\nIl est possible de réaliser un modular monolith en gardant le single-process, mais en créant des modules de code bien séparés.\nShopify est un bon exemple de modular monolith.\nOn se retrouve ceci dit souvent dans une configuration où le split de DB est négligé par les équipes.\nLe distributed monolith : on a plusieurs services communiquant à travers le réseau, mais le système a besoin d'être déployé en un bloc.\nC'est un système qui a tous les désavantages : absence de modularisation, et système distribué.\nLes third-party black-box systems : les services externes SASS qu'on utilise, ou open source qu'on installe.\nLes monoliths ont un certain nombre de désavantages :\nLes diverses parties du code ont tendance à être plus facilement couplées.\nLe travail à plusieurs équipes est plus compliqué en terme de conflit de modification, en terme de confusion d'ownership, et aussi pour savoir quand déployer.\nConcernant les avantages :\nOn n'a pas tous les problèmes associés aux systèmes distribués.\nLe workflow de développement, le monitoring et le débug est plus simple.\nOn peut réutiliser du code très facilement.\nA propos du couplage et de la cohésion :\nLe couplage c'est l'idée que changer une chose implique d'en changer aussi une autre. La cohésion c'est le fait de garder ensemble des choses qui ont un rapport entre-elles (et qui d'habitude changent ensemble).\nPour avoir un système facile à transformer, on a envie que le couplage soit faible, et la cohésion élevée.\nPar exemple, si la logique d'une fonctionnalité est présente à travers plusieurs modules, on va devoir les changer tous pour la modifier (couplage élevé), et les éléments de cette fonctionnalité ne sont pas rassemblés (cohésion faible).\nDans le cas spécifique des microservices, les modules en question qu'il faut considérer en priorité sont les microservices eux-mêmes, puisque modifier leurs limites coûte très cher.\nOn veut donc faire en sorte que chaque changement impacte, et donc oblige le redéploiement, du moins possible de microservices.\nSi dans les microservices on peut se tromper dans les limites de chaque service, dans le monolithe ces limites n'existent pas naturellement, et donc on a tendance à avoir un couplage généralisé où tout dépend de tout.\nIl y a différents types de couplage :\nImplementation coupling : il s'agit d'un service qui doit changer quand on modifie l'implémentation d'un autre service.\nL'exemple typique c'est le couplage à la DB d'un autre service.\nLa solution c'est soit d'avoir une API pour accéder à la donnée, soit d'avoir une DB publique spécifique pour les consommateurs externes, distincte de la DB interne du microservice.\nAvoir une interface publique distincte permet aussi de concevoir cette interface pour répondre aux besoins des consommateurs, en mode outside-in, plutôt qu'imaginer ce qu'on veut exposer parmi ce qu'on a déjà.\nL'auteur conseille de toujours faire ça : impliquer les consommateurs dans le design de l'API publique, pour que le service les serve au mieux.\nTemporal coupling : il s'agit de communication synchrone dépendante d'autres communications.\nPar exemple, si un service envoie un message à un autre service, qui doit d'abord interroger un 3ème avant de répondre. Si le 3ème est down le 2ème ne pourra pas répondre.\nLa solution peut être pour le 2ème service d'avoir les données du 3ème en cache.\nUne autre solution pourrait être d'utiliser des communications asynchrones : le 3ème service reçoit le message asynchrone et recontacte le 2ème quand il est dispo.\nPour plus d'infos sur le type de communications, voir le chapitre 4 de Building Microservices.\nDeployment coupling : à chaque fois qu'on doit redéployer des services quand on en déploie un.\nIdéalement on veut pouvoir déployer le plus petit set de choses pour avoir peu de risques et un feedback rapide (et aller vers une continuous delivery).\nLes release trains sont une mauvaise idée.\nDomain coupling : il s'agit des interactions indispensables liées aux fonctionnalités elles-mêmes.\nOn ne peut pas les éliminer, mais on peut les agencer de telle sorte qu'elles aient un impact limité en termes de couplage.\nPar exemple, dans le cas de l'entreprise de vente de CD, le microservice de la commande doit communiquer au microservice de l'entrepôt quels CD ont été achetés et où ils doivent être acheminés.\nOn peut réduire au maximum les informations communiquées entre services, par exemple l'entrepôt recevrait seulement les données de packaging et pas l'ensemble des détails de la commande.\nOn peut faire en sorte que la commande inclut les infos nécessaires sur l'utilisateur (dont elle aura de toute façon besoin pour d'autres raisons) dans le message envoyé à l'entrepôt, plutôt qu'avoir l'entrepôt faisant un autre appel pour obtenir les infos de l'utilisateur.\nUne autre possibilité pourrait être que la commande émette un event, et que l'entrepôt le consomme.\nLe Domain Driven Design permet d'organiser les microservices efficacement autour de business domains.\nLes aggregates :\nOn peut les voir comme des représentations de choses réelles, avec un cycle de vie qu'on peut traiter avec une machine à état.\nPar exemple une commande, une facture, un objet en stock.\nUn microservice peut contenir un ou plusieurs aggregates.\nSi un autre microservice veut changer le contenu d'un aggregate, il doit soit envoyer un message au microservice qui en a la responsabilité, soit faire en sorte que ce microservice écoute des events que lui émet.\nIl y a de nombreux moyens d'organiser le système en aggregates, mais il vaut mieux commencer par celui qui colle le mieux au modèle mental des utilisateurs.\nL'event storming est un bon moyen pour ça.\nLes bounded contexts :\nIls permettent de cacher l'implémentation aux bounded contexts extérieurs.\nIls contiennent un ou plusieurs aggregates, dont certains peuvent être privés pour l'extérieur.\nConcernant la relation avec les microservices :\nAu début on cherche de gros microservices, donc les bounded contexts sont de bons candidats.\nA mesure qu'on avance, on va affiner nos microservices, et opter pour un aggregate par service.\nA noter que le groupe de microservices autour d'un bounded context peut cacher qu'il y a en fait plusieurs microservices (ce détail relevant de l'ordre de l'implémentation).\nNDLR : selon Vlad Khononov le microservice est de fait un bounded context, et va bien avec la taille d'un subdomain. Il ne peut pas être plus grand que le plus grand bounded context possible, ni plus petit qu'un aggregate. Mais la taille de l'aggregate marche rarement.","2---planning-a-migration#2 - Planning a Migration":"On peut vouloir adopter les microservices pour diverses raisons, et ces raisons peuvent fortement influencer ce sur quoi on va concentrer nos efforts.\nL'auteur pose en général 3 questions pour aider les entreprises à savoir si elles ont besoin des microservices :\nQu'est-ce que vous espérez accomplir ?\nOn devrait pouvoir trouver des choses qui sont alignées avec les besoins business et des utilisateurs finaux.\nEst-ce que vous avez considéré des alternatives ?\nComment saurez-vous si la transition fonctionne ?\nParmi les raisons de choisir les microservices :\nAméliorer l'autonomie des équipes / Scaler le nombre de développeurs.\nIl est notoire que les unités business autonomes sont plus efficaces. Et cette règle s'applique aussi à l'échelle de l'équipe, comme le modèle d'Amazon avec les équipes à deux pizzas.\nAvoir le contrôle exclusif sur des microservices permet aux équipes d'acquérir de l'autonomie, et de travailler en parallèle.\nAutres moyens d'obtenir ça :\nLe monolith modulaire peut répondre à ce point, avec une certaine coordination nécessaire quand même pour le déploiement commun.\nOn peut aussi penser de manière générale à des approches self-service où on provisionne des machines automatiquement au lieu d'avoir à passer par un ticket manuel auprès d'une autre équipe.\nRéduire le time to market.\nLe fait que les microservices permettent de déployer sans besoin de coordination fait qu'on peut amener des changements en production plus vite.\nAutres moyens d'obtenir ça :\nL'auteur recommande de faire l'analyse concrète du chemin et du temps réel de chaque étape entre l'idée obtenue en discovery, et la feature en production.\nOn trouve souvent des bottlenecks qui permettent de gagner un temps conséquent.\nScaler efficacement la charge.\nComme les microservices tournent dans des processus différents, on peut les scaler indépendamment, et donc maîtriser les coûts de notre infrastructure.\nAutres moyens d'obtenir ça :\nOn peut essayer de passer sur une plus grosse machine (scaling vertical).\nFaire tourner plusieurs copies du monolithe, derrière un load balancer (scaling horizontal). Le bottleneck risque d'être la DB, mais ça ne coûte pas très cher d'essayer.\nAméliorer la robustesse.\nComme on a plusieurs unités indépendantes et tournant sur des machines séparées, on peut concevoir le système de sorte qu'il continue à fonctionner même si certaines parties sont en échec.\nAttention quand même : il y a tout un effort à faire pour obtenir cette robustesse, le fait de distribuer le système ne suffit pas à le rendre robuste.\nAutres moyens d'obtenir ça :\nFaire tourner plusieurs copies du monolithe permet de répondre à cette problématique. Y compris par exemple dans des racks ou datacenters différents.\nAdopter de nouvelles technologies.\nLes microservices étant isolés et communiquant par réseau, on peut très bien tester un nouveau langage, une nouvelle techno etc. sur un seul microservice.\nAutres moyens d'obtenir ça :\nOn peut parfois switcher de langage, par exemple si on utilise la JVM, on peut basculer entre les langages supportés.\nPour les nouvelles DB c'est plus compliqué.\nOn peut toujours remplacer le monolithe par un nouveau avec une approche incrémentale type strangler fig.\nRéutiliser des composants.\nC'est une mauvaise raison.\nEn général on cherche à optimiser autre chose derrière la réutilisation, il vaut mieux se concentrer sur cette vraie raison.\nPar exemple, la réduction du time to market. Or le coût de coordination entre équipes peut impliquer que réécrire le composant serait plus rapide.\nQuand ne pas adopter les microservices :\nUn domaine pas très clair.\nDans le cas où on a un domaine encore jeune et pas très bien compris, la décomposition en microservices peut impliquer de se tromper de limites, et les changer coûte cher.\nEt donc typiquement il faut éviter les microservices dès le début.\nQuand on est une startup.\nLes microservices sont utiles pour les scale-ups ou les entreprises établies qui ont trouvé leur product market fit. Les startups le cherchent et donc seront amenées à beaucoup changer leur produit.\nOn peut éventuellement séparer ce qui est clairement à part dans un service, et laisser le reste dans le monolithe pour nous donner plus de temps pour le faire maturer.\nIl y a aussi la question de la capacité à gérer les microservices avec les effectifs de la boite : si on a du mal à en gérer 2, en gérer 10 va être vraiment difficile.\nQuand le logiciel est déployé chez le client.\nLe déploiement de microservices implique une grande complexité au niveau de l'infrastructure. On ne peut pas attendre des clients qu'ils puissent la gérer.\nQuand on n'a pas de bonne raison.\nMine de rien c'est un des cas les plus courants où les gens adoptent les microservices alors qu'ils ne devraient pas.\nOn a souvent plusieurs raisons d'adopter les microservices dans notre organisation. Il faut les prioriser.\nPar exemple, on décide qu'il nous faut des microservices pour gérer une augmentation de trafic. Puis on se dit que ce serait pas mal d'augmenter aussi l'autonomie des équipes, et d'adopter un nouveau langage.\nIl faut bien garder en tête que c'était l'augmentation du trafic qui était la plus importante. Et donc si on trouve un autre moyen plus simple de régler le problème, peut-être que les autres raisons devront attendre.\nUn bon moyen pour aider aux décisions est de représenter l'ensemble des raisons d'adopter les microservices avec des curseurs de 1 à 5 : si on augmente le curseur pour une raison, on doit le baisser pour une autre.\nPour réussir à créer un changement organisationnel (pour mettre en place des microservices ou autre chose), l'auteur propose la méthode en 8 étapes de John Kotter, décrite plus en détail dans son livre Leading Change.\nÉtape 1 : Establishing a sense of urgency. Le meilleur moment pour initier le changement c'est juste après une crise dont l'idée qu'on veut mettre en place règlerait le problème sous-jacent, avec l'idée \"Il faut le mettre en place maintenant\".\nÉtape 2 : Creating the guiding coalition. On a besoin de convaincre des personnes autour de nous. En fonction de l'impact de notre idée, il faudra avoir des personnes plus ou moins haut placées, et typiquement des personnes du business dans le cas où on introduit des systèmes distribuées qui vont impacter les utilisateurs.\nÉtape 3 : Developing a vision and strategy. La vision définit le \"quoi\", elle doit donner envie mais être réaliste. La stratégie définit le \"comment\".\nÉtape 4 : Communicating the change vision. Il vaut mieux privilégier la communication en face à face (plutôt que slack ou ce genre de chose) pour pouvoir ajuster le discours en fonction des réactions.\nÉtape 5 : Empowering employees for broad-based action. Souvent les organisations amènent de nouvelles personnes dans l'équipe pour aider au changement en donnant de la bande passante.\nÉtape 6 : Generating short-term wins. Pour éviter que l'engouement retombe, il faut obtenir des quick wins. Ça peut être par l'extraction de microservices \"faciles\" (à condition qu'ils aient un intérêt quand même).\nÉtape 7 : Consolidating gains and producing more change. On continue avec des changements plus profonds en fonction des succès ou échecs. Ça peut être la décomposition de la DB qu'on ne peut pas mettre de côté éternellement.\nÉtape 8 : Anchoring new approaches in the culture. A force de pratiquer la nouvelle manière de faire, la question de savoir si c'est la bonne approche ou non disparaît. Elle devient habituelle.\nLa décomposition d'un monolithe étant une chose difficile, il faut qu'elle soit faite de manière incrémentale. On sort un service à la fois, et on obtient du feedback pour s'améliorer sur la suite.\nLe feedback en question est aussi précieux parce que la plupart des problèmes complexes liés aux microservices sont remarqués une fois que c'est déployé en production.\nUne des raisons de la méthode incrémentale est de rendre les erreurs réversibles.\nMais il y a des décisions qui sont plus impactantes que d'autres, et donc il faut adapter le temps passé à analyser à la facilité à annuler la décision.\nExemple : changer de fournisseur cloud ou changer l'API qu'on fournit publiquement est très impactant, alors d'expérimenter une librairie open source ou un nouveau langage beaucoup moins.\nCertaines décisions liées aux microservices peuvent être difficiles à défaire, par exemple annuler une migration de DB ou défaire la réécriture d'une API utilisée par de nombreux consumers.\nDans ces cas, l'auteur recommande d'utiliser un tableau blanc pour simuler les divers use-cases et leurs conséquences en terme de communication entre services, pour voir s'il y a des problèmes apparents.\nPour ce qui est de savoir où on commence, il nous faut une décomposition en composants business. Et pour ça on utilise le Domain Driven Design.\nLa notion de bounded context et les relations entre les BCs nous permet de représenter un découpage possible en microservices.\nOn n'a pas besoin d'un modèle super détaillé des BCs, mais d'avoir juste assez d'information pour pouvoir commencer à faire des choix. Et comme on procède de manière incrémentale, une erreur est vite rattrapée.\nL'event storming est un outil recommandé par l'auteur pour obtenir une connaissance partagée du modèle, et pouvoir faire des choix pertinents à partir de là.\nPour approfondir il y a Introducing EventStorming, le livre pas encore terminé d'Alberto Brandolini.\nPour prioriser, on peut se servir du context mapping (le nom n'est pas mentionné par l'auteur).\nUn BC qui a beaucoup de liens avec d'autres BCs ne sera peut être pas le bon premier candidat pour être extrait en microservice parce qu'il impliquera beaucoup de communications réseau.\nA noter que le context map qu'on a à ce stade ne représente pas forcément le vrai découpage. Il va falloir regarder dans le code et vérifier ce que le BC fait dans base de données.\nIl faudra aussi mettre la facilité d'extraction en balance avec l'utilité d'extraire ce BC là.\nPar exemple, si notre objectif c'est d'améliorer le time to market, mais qu'on commence par extraire un BC en microservice alors qu'il n'est presque jamais modifié, on n'aura pas beaucoup d'impact sur ce qu'on voulait faire.\nOn peut placer les BCs sur un graphique à deux axes : en abscisses l'intérêt de la décomposition, et en ordonnée la facilité de la décomposition.\nOn va choisir en priorité les BCs qui se retrouvent en haut à droite.\nA propos de l'organisation des équipes.\nHistoriquement les équipes étaient organisées par compétences techniques : devs Java ensemble, DBA ensemble, testeurs ensemble etc.\nPour intervenir sur une fonctionnalité il fallait passer par plusieurs équipes.\nDe nos jours, avec des mouvements comme DevOps, les spécialités sont poussées vers les équipes de delivery, qui sont organisées autour de domaines fonctionnels, en vertical slices.\nLe rôle des équipes centrales qui restent s'est transformé : au lieu de faire eux-mêmes, ils aident les équipes delivery, en y envoyant des spécialistes, organisant des formations, et en créant des outils self-service.\nPour aller plus loin, l'auteur recommande Team Topologies et The Devops Handbook.\nIl faut faire attention à ne pas chercher à copier tel quel les autres organisations, sans prendre en compte le contexte, la culture d'entreprise etc. On peut en revanche s'en inspirer.\nLe changement prend du temps. Par exemple, on peut intégrer des ops dans des équipes de dev pour former petit à petit chacun aux problématiques de l'autre.\nPour commencer le changement, on peut réunir des personnes de chaque équipe, et faire un mapping des responsabilités liées à la delivery, en fonction de chaque équipe.\nEt ensuite on peut planifier un changement de responsabilités liées aux équipes, et de la structure des équipes, sur 6 mois à un an par exemple.\nConcernant la montée en compétence nécessaire pour la nouvelle organisation, l'auteur préconise de laisser les développeurs s'auto-évaluer avec une note sur chaque compétence nécessaire, et de les aider ensuite sur celles où ils se sont mis un faible score.\nCes auto-évaluations devraient être privées pour ne pas être faussées.\nPour savoir si on va dans la bonne direction :\nIl faut avoir quelques métriques quantitatives et qualitatives liées aux outcomes qu'on recherche avec la transition qu'on a entamé.\nLes métriques quantitatives dépendent des objectifs.\nPar exemple, si c'est le time to market, on peut mesurer le cycle time, le nombre de déploiements et le failure rate.\nSi on cherche la scalabilité, on peut se reporter au dernier test de performance réalisé.\nAttention aux métriques : elles peuvent pousser à des comportements non souhaités pour satisfaire la métrique.\nPour ce qui est des métriques qualitatives, il s'agit de vérifier si l'équipe est contente ou pas, s'ils sont débordés etc.\nIl faut organiser des checkpoints réguliers pour voir où on en est.\nOn vérifie que les raisons pour lesquelles on a commencé la transition sont toujours là.\nOn jette un œil aux métriques quantitatives pour voir l'avancée.\nOn demande du feedback qualitatif.\nOn décide d'éventuelles actions.","3---splitting-the-monolith#3 - Splitting the Monolith":"Ce chapitre décrit des patterns pour migrer le code dans des microservices de manière incrémentale.\nUn des critères à prendre en compte pour le choix des patterns c'est le fait qu'on ait ou non la possibilité de changer le code du monolithe.\nOn peut avoir de nombreuses raisons pour ne pas le pouvoir :\nSi on n'a plus le code source du monolithe.\nSi le monolithe est écrit dans une technologie pour laquelle on n'a pas les compétences.\nSi on a peur de trop impacter les autres développeurs du monolithe.\nDans le cas où on peut modifier le code du monolithe, si le code est en trop mauvais état, ça peut aussi parfois être plus rapide de le réécrire dans le microservice plutôt que de l'extraire.\nUne des grandes difficultés c'est d'isoler le code qu'on veut extraire dans notre microservice, c'est-à-dire modulariser le monolithe.\nEn général le code dans les monolithes est organisé autour de considérations techniques et pas de domaines métier, c'est pourtant ça qu'on veut extraire.\nPour aider à faire ça, l'auteur recommande le concept de seam, qu'on trouve dans Working Effectively with Legacy Code de Michael Feathers.\nUn seam est une délimitation autour d'une zone qu'on veut changer. On travaille ensuite à une nouvelle implémentation de la fonctionnalité, et à la fin on remplace l'ancienne par la nouvelle.\nÇa peut être plus ou moins grand, ici ce qui nous intéresse c'est un bounded context.\nRéorganiser le code pour obtenir un modular monolith peut être suffisant pour ce qu'on recherche, en fonction de nos objectifs (cf. chapitre précédent).\nEt ça peut aussi être une première étape pour aller vers l'extraction d'éventuels microservices ensuite. C'est en tout cas le conseil de l'auteur.\nPour autant, de nombreuses équipes préfèrent identifier une fonctionnalité, et la recoder dans un microservice sans refactorer le monolithe.\nDans tous les cas, l'auteur recommande une approche incrémentale : si la réécriture du service se compte en jours ou semaines ça peut être OK, si ça se compte en mois, il vaut mieux adopter une approche plus incrémentale.\nDans la suite du chapitre, on voit des patterns de migration, qui permettent d'extraire du code sous forme d'un microservice cohabitant avec le monolithe.\nChaque pattern a des avantages et des inconvénients, il faut les comprendre pour pouvoir prendre à chaque fois le plus adapté.\nOn extrait toujours les microservices un par un, en apprenant des erreurs pour le prochain.","pattern-strangler-fig-application#Pattern: Strangler Fig Application":"C'est un des patterns les plus utilisés, et ça se base sur l'image d'un figuier qui s'implante sur un arbre existant, plante ses racines, et petit à petit \"étrangle\" l'arbre qui finira par mourir sans ressources, laissant le figuier à sa place.\nCette technique permet d'avoir la nouvelle version en parallèle de l'ancienne. On fait grossir petit à petit les fonctionnalités de la nouvelle, puis on fait le switch quand le microservice est prêt à remplacer la fonctionnalité dans le monolithe.\nIl faut faire la différence entre deployment et release : on intègre et déploie régulièrement ce qu'on fait en production, pour éviter les problèmes de merge et dérisquer le plus possible de choses en production, mais on n'active la fonctionnalité que quand elle est prête.\nConcrètement, vu qu'on est en train de sortir un microservice qui va tourner sur un processus à part, le switch se passe au niveau réseau : tant que le microservice n'est pas prêt, les requêtes concernant sa fonctionnalité vont vers le monolithe, et quand on veut le release, on les redirige vers lui.\nSi attendre que le microservice soit fini n'est pas assez incrémental pour nous, on peut aussi commencer à rediriger une partie des requêtes du monolithe vers le microservice, en fonction de ce qui a déjà été implémenté.\nÇa va par contre nous obliger à partager temporairement la même DB entre la fonctionnalité dans le monolithe, et celle dans le microservice.\nCette technique a l'avantage de ne pas avoir à toucher au monolithe dans le cas où la portion de fonctionnalité qu'on sort est autonome.\nPour ça il faut qu'elle n'ait pas besoin de faire d'appel vers le monolithe, et que le monolithe n'ait pas besoin de faire d'appel vers elle non plus.\nDans le cas où la fonctionnalité doit faire des appels vers le monolithe, il faudra que le monolithe expose des endpoints, et donc on devra le modifier.\nSi c'est le monolithe qui doit faire des appels vers le microservice, alors on ne peut pas vraiment utiliser cette technique : on ne pourra pas faire le switch de la fonctionnalité au niveau réseau.\nOn pourra à la place utiliser le pattern Branch by Abstraction par exemple.\nExemple : HTTP Reverse Proxy : HTTP permet très facilement de faire de la redirection.\nSi notre monolithe reçoit des requêtes HTTP, on va pouvoir mettre en place un proxy pour router les requêtes entre le monolithe et le microservice.\nÉtape 1 : On met en place le proxy, et on le configure pour laisser passer les requêtes comme avant vers le monolithe.\nÇa nous permet de nous assurer que la latence additionnelle d'une étape réseau de plus ne pose pas problème.\nOn peut aussi dès cette étape tester le mécanisme de redirection pour vérifier qu'il n'y aura pas de problème à le faire.\nÉtape 2 : on implémente progressivement la fonctionnalité dans le microservice, vers lequel il n'y a aucun trafic.\nÉtape 3 : Quand le microservice est prêt, on redirige le trafic vers lui.\nOn peut remettre le trafic vers le monolithe s'il y a un problème.\nPour plus de facilité, la redirection peut être activée avec un feature toggle.\nPour ce qui est du proxy lui-même, ça va dépendre du protocole. Si on a du HTTP, on peut partir sur un serveur connu comme NGINX.\nCa peut être par exemple sur le path : rediriger /invoice/ vers le monolithe, et /payroll/ vers le microservice.\nSi on route sur un contenu se trouvant dans le body d'une requête POST (NDLR : comme GraphQL), ça risque d'être un peu plus compliqué.\nEn tout cas, l'auteur déconseille de coder soi-même son proxy si on a besoin de quelque chose de custom.\nLes quelques fois où il a essayé, il a obtenu de très mauvaises performances.\nIl conseille de plutôt partir d'un proxy existant comme NGINX, et de le personnaliser avec du code (du lua pour NGINX).\nDans le cas où on voudrait que notre microservice supporte un autre protocole que celui du monolithe (par exemple gRPC au lieu de SOAP), on pourrait envisager faire la traduction dans le proxy.\nPour l'auteur c'est une mauvaise idée : si on le fait pour plusieurs microservices, on va finir par complexifier ce proxy partagé, alors qu'on voulait que les microservices soient indépendants.\nL'auteur conseille plutôt de faire ce mapping de protocole dans chacun des microservices qui en ont besoin, et éventuellement de faire en sorte qu'ils supportent les deux protocoles.\nOn peut aussi aller vers le service mesh où chaque microservice a son proxy local, qui peut faire les redirections et mapping qu'il veut.\nLes outils les plus connus pour ça sont Linkerd et Istio.\nSquare a mis en place le service mesh et en a fait un article.\nExemple : FTP.\nL'entreprise suisse Homegate a utilisé le strangler fig pattern pour extraire des microservices, et en profiter pour changer le protocole utilisé pour uploader des fichiers : de FTP vers HTTP.\nMais ils voulaient qu'il n'y ait pas de changement pour les utilisateurs.\nDonc ils ont mis en place une interception des appels FTP, et le remapping vers du HTTP pour taper dans le microservice responsable de ça.\nExemple : Message Interception : dans le cas de messages asynchrones à router vers le nouveau microservice.\nUne première possibilité est le content-based routing, où un router va consommer tous les messages du message broker, et les queuer sur deux autres queues : une pour le monolithe, et une pour le microservice extrait.\nCe pattern vient d'Enterprise Integration Patterns. Et de manière générale l'auteur recommande ce livre pour des patterns de communication asynchrone.\nL'avantage c'est qu'on n'a pas à toucher au monolithe.\nL'inconvénient c'est qu'on complexifie là encore le système de communication plutôt que les programmes. Donc l'auteur est plutôt réticent.\nL'autre possibilité c'est la selective consumption, où le monolithe et le microservice consomment sur la même queue, mais sélectionnent les messages qui leur sont destinés.\nL'avantage c'est qu'il n'y a pas de complexité dans le mécanisme de communication.\nParmi les désavantages :\nLe message broker pourrait ne pas supporter la consommation sélective.\nIl faut déployer les changements dans le monolithe et dans le microservice en même temps pour que la consommation se passe bien.\nDans le cas où on veut ajouter des fonctionnalités ou fixer des bugs en même temps qu'on implémente le microservice, il faut bien garder en tête que le rollback sera alors plus difficile.\nIl n'y a pas de solution facile : soit on accepte que le rollback sera plus compliqué à faire, soit on freeze les features sur la partie extraite en microservice tant que l'extraction est en cours.","pattern-ui-composition#Pattern: UI Composition":"L'interface utilisateur doit aussi être découpée par considérations business, pour obtenir des slices verticaux avec les microservices.\nExemple : Page Composition.\nL'auteur a travaillé chez The Guardian, où la migration a été réalisée à plusieurs reprises page par page.\nLa 2ème fois en utilisant un CDN pour le routing redirigé progressivement vers les nouvelles pages.\nREA Group, une entreprise immobilière australienne, avait plusieurs équipes responsables de parties différentes du site, et donc la séparation par pages avait dans ce cas encore plus de sens.\nExemple : Widget Composition.\nDe nombreuses entreprises utilisent la séparation en widgets pour suivre les microservices.\nC'est le cas par exemple d'Orbits qui avait une UI décomposée en widgets majeurs sous la responsabilité d'équipes différentes.\nQuand ils ont voulu migrer vers des microservices, ils ont pu le faire incrémentalement, en suivant le découpage des widgets côté front.\nUn des avantages de cette séparation c'est que même quand un des widgets ne fonctionne pas, le reste peut être affiché.\nCôté applications mobiles (Android / iOS), on est face à des monolithes de fait, puisqu'il faut tout redéployer et faire retélécharger à l'utilisateur à chaque changement.\nDe nombreuses entreprises (comme Spotify cf. video) utilisent des composants affichés qui viennent du backend, pour ne pas avoir à redéployer l'app mobile quand ils y font un changement.\nExemple : Micro Frontends.\nIl s'agit de faire des composants indépendants dans un frontend de type SPA, avec des bouts de React, Vue, etc. cohabitant et partageant de l'information, mais sans se gêner.","pattern-branch-by-abstraction#Pattern: Branch by Abstraction":"Dans le cas où le Strangler fig pattern n'est pas possible parce que le composant qu'on veut extraire est profondément ancré dans le monolithe (il reçoit des appels des autres composants du monolithe par exemple), on peut utiliser cette technique.\nOn va travailler sur une version alternative du composant à l'intérieur même du monolithe, et l'activer à la fin.\nIl s'agit du même principe qu'une branche du gestionnaire de versions, à la différence que là on travaille en intégration continue, et déploiement continu (bien qu'on ne release qu'au moment où le microservice est prêt).\nL'auteur n'insiste pas trop sur les nombreux problèmes d'une branche de gestionnaire de version qui dure longtemps, mais nous conseille de jeter un œil au State of DevOps Report pour nous en convaincre.\nConcrètement :\n1 - On crée une abstraction devant la fonctionnalité qu'on va remplacer.\nCette étape peut être plus ou moins complexe, en fonction de la taille de l'API qu'on expose aux autres modules.\nIl peut être nécessaire de définir un seam à extraire.\n2 - On fait en sorte que les clients de notre fonctionnalité utilisent cette abstraction pour y accéder.\nLa migration doit être incrémentale.\n3 - On crée une nouvelle implémentation de la fonctionnalité.\nLa nouvelle implémentation dans le monolithe va juste faire des appels vers le microservice qu'on développe à l'extérieur du monolithe.\nOn migre les fonctionnalités de manière incrémentale.\n4 - On pointe l'abstraction sur la nouvelle fonctionnalité développée.\nComme avec le strangler fig pattern, on aimerait bien avoir un feature toggle pour pouvoir activer et désactiver la nouvelle fonctionnalité sans changer le code.\n5 - On enlève l'abstraction et l'ancienne implémentation.\nIl se peut que l'abstraction ait un intérêt en elle-même, dans cas on peut éventuellement la laisser.\nIl faut bien penser à enlever les éventuels feature toggles.\nIl existe une variante qui s'appelle Verify branch by abstraction : on appelle la nouvelle implémentation d'abord, et si elle échoue, on fallback sur l'ancienne.\nQuand l'utiliser :\nCette technique est à utiliser à chaque fois qu'un changement va prendre du temps, et qu'on veut ne pas empêcher les autres d'avancer sur ce qu'ils font, tout en restant sur de l'intégration continue.\nPour les microservices, l'auteur conseille d'utiliser en priorité le strangler fig pattern, parce qu'il est plus simple.\nSi on ne peut pas toucher au code du monolithe, alors il faut choisir une autre technique que celle-là.","pattern-parallel-run#Pattern: Parallel Run":"Quand on a besoin d'un grand degré de fiabilité, on peut jouer les deux implémentations en parallèle, pour vérifier que le résultat est bien le même.\nIl n'y a qu'une des implémentations qui sera la source de vérité : en général l'ancienne jusqu'à ce qu'on décide que la nouvelle a fait ses preuves et qu'on n'a plus besoin de l'ancienne.\nOn peut vérifier le résultat, mais aussi des éléments non-fonctionnels comme le temps de réponse et le nombre de timeouts.\nExemple : calcul de produits financiers dérivés.\nL'auteur a travaillé sur le système d'une banque, où il s'agissait de refaire le calcul de produits dérivés.\nL'enjeu financier étant important, ils ont décidé de jouer les deux systèmes en parallèle, et de recueillir la différence entre les deux par des batchs journaliers.\nIls ont fini par changer la source de vérité vers le nouveau système après un mois, et ont enlevé l'ancien après quelques mois de plus.\nPour vérifier des side-effects qu'on n'a envie de faire qu'une fois (comme le fait d'envoyer un email), on peut utiliser des spies, comme dans les unit tests mais en production.\nIl vaut mieux faire les vérifications à partir du microservice plutôt qu'à partir de la partie de la nouvelle fonctionnalité qui est dans le monolithe, pour tester le plus possible de choses.\nLes vérifications peuvent être faites en asynchrone, en enregistrant les appels quelque part, et en vérifiant plus tard qu'on a bien eu la même chose que pour l'autre implémentation.\nL'auteur conseille Github Scientist (existant en plusieurs langages) pour aider à implémenter ce pattern.\nLe canary release consiste à releaser d'abord auprès d'un nombre réduit de clients.\nLe dark launching consiste à déployer pour tester, mais à ne pas releaser auprès des clients.\nLe parallel run est une forme de dark launching.\nL'ensemble de ces techniques font partie de la progressive delivery.\nCe pattern est très utile dans certains cas, mais a un coût de mise en place.\nL'auteur ne l'a utilisé qu'une ou deux fois dans sa carrière.","pattern-decorating-collaborator#Pattern: Decorating Collaborator":"Cette technique permet de ne pas avoir à modifier le monolithe, tout en permettant de déclencher quelque chose à partir de ce que fait le monolithe.\nOn laisse l'appel aller dans le monolithe et en sortir, et on l'intercepte à la sortie, pour éventuellement faire un appel vers notre microservice.\nDe la logique va donc se retrouver dans le proxy qui décide de faire l'appel au microservice. Attention à ce que cette logique ne devienne pas trop complexe.\nExemple : Loyalty Program.\nOn a un monolithe qui traite un ordre d'achat. On veut y ajouter une fonctionnalité de points de fidélité, mais le monolithe est compliqué et on ne veut pas le modifier maintenant.\nLe proxy va récupérer la réponse du monolithe avant qu'elle n'aille au client, et faire un appel au microservice qui s'occupe d'allouer les points de fidélité.\nSi on n'a pas suffisamment d'infos suite à l'appel intercepté, on risque d'avoir besoin de refaire un appel au monolithe.\nL'auteur conseille d'y réfléchir à deux fois avant d'utiliser ce pattern dans le cas où l'information ne se trouve pas dans l'appel intercepté.\nUne alternative à ce pattern peut être le pattern change data capture.","pattern-change-data-capture#Pattern: Change Data Capture":"Cette technique est plus invasive que decorating collaborator : on va écouter les changements issus de la DB, et y faire réagir notre microservice.\nExemple : Issuing Loyalty Cards.\nOn a un monolithe qui permet de créer des comptes de fidélité, et renvoie simplement que la création a fonctionné. Et on aimerait imprimer des cartes de fidélité à chaque fois.\nSi on voulait utiliser le decorating collaborator pattern, il faudrait faire un appel supplémentaire au monolithe pour obtenir les informations manquantes, et que le monolithe expose une API pour ça.\nOn va donc plutôt écouter ce que dit la DB quand le monolithe insert le compte de fidélité, et alimenter notre microservice d'impression avec ça.\nPour ce qui est de la manière de l'implémenter :\nDatabase triggers : c'est un mécanisme de stored procedures, fournis par la plupart des bases de données.\nAttention à ne pas trop en utiliser, leur maintenance est difficile.\nTransaction log pollers : la plupart des DB écrivent leurs données dans un fichier de log qui précède l'écriture dans la base. On peut simplement lire ce fichier.\nC'est une des solutions les plus intéressantes selon l'auteur, avec la contrainte qu'on a besoin de s'adapter à la structure spécifique du log de cette DB.\nOn a de nombreux outils qui lisent les logs, y compris certains qui les mettent dans un message broker.\nBatch delta copier : il s'agirait d'écrire un programme qui compare régulièrement le contenu de la DB, et qui réagit s'il y a un changement.\nLe problème c'est de réussir à savoir s'il y a un changement. Certaines DB le permettent, mais pas forcément au niveau du row, auquel cas il faudrait ajouter nous-mêmes des timestamps pour savoir qu'est-ce qui a changé quand.\nCe pattern est utile quand on a besoin de réagir au monolithe, mais quand on ne peut pas vraiment mettre en place le strangler fig ou le decorating collaborator, et qu'on ne peut pas non plus changer le monolithe.","4---decomposing-the-database#4 - Decomposing the Database":"","pattern-the-shared-database#Pattern: The Shared Database":"Partager la DB veut dire ne pas avoir la possibilité de choisir ce qu'on cache et ce qu'on montre, et même ne pas savoir ce qui est utilisé par d'autres.\nDans le cas où plusieurs services peuvent modifier la DB partagée, on ne sait plus qui la contrôle. Et la logique de modification est dupliquée et peut diverger.\nLa DB doit être privée à chaque microservice. Le partage publique d'une DB n'est approprié que dans deux cas :\n1 - Une DB avec des données de référence read-only très stables (par exemple la liste des pays existants ou des codes postaux).\n2 - Dans le cas du pattern Database-as-a-Service Interface, où on partage une DB read-only, distincte de notre DB interne.\nMême si la bonne solution dans la plupart des cas c'est de splitter la DB du monolithe dans les nouveaux microservices, on a des techniques qui permettent d'aller dans la bonne direction à peu de frais et d'arrêter l'hémorragie, en ajoutant des abstractions par dessus la DB du monolithe.","pattern-database-view#Pattern: Database View":"On crée une view par dessus la DB, et on demande aux clients d'utiliser la view. De cette manière on peut modifier la DB source comme si elle était privée, en adaptant la view pour qu'elle soit stable pour les clients.\nExemple : Database as a public contract.\nL'auteur travaillait dans une banque, et ils ont remarqué qu'un problème de performance majeur pouvait être corrigé en restructurant le schéma de leur DB.\nMalheureusement la DB était utilisée par plus de 20 applications (sans même savoir lesquelles) partageant les mêmes credentials.\nPour la question des credentials, l'auteur conseille HashiCorp Vault qui permet de gérer un grand nombre de credentials facilement.\nIls ont mis en place des views comme solution temporaire pour pouvoir restructurer la DB sans impacter les clients.\nUne view peut aussi simplement cacher des champs ou des tables, et permettre de décider ce qu'on veut montrer ou non publiquement.\nLes views ont quelques limitations :\nElles peuvent poser des problèmes de performance, et la version materialized de la view est plus efficace, mais contiendra des données anciennes, datant de la dernière fois qu'on a fait un update.\nElles sont read-only.\nToutes les DB n'ont pas la fonctionnalité. Les DB relationnelles l'ont, et certaines DB NoSQL aussi (c'est le cas de Cassandra et Mongo par exemple).\nIl est probable que le schéma de la view doive se trouver sur la même database engine que le schéma initial.\nEn termes d'ownership, l'auteur conseille de le donner à l'équipe qui a la charge de la DB source.\nCette étape va dans la bonne direction, mais l'auteur déconseille de faire ça à la place d'une décomposition de la DB sans avoir de bonnes raisons.","pattern-database-wrapping-service#Pattern: Database Wrapping Service":"Une autre manière de cacher la DB pour arrêter l'hémorragie c'est de la mettre derrière un service, et demander aux clients d'y accéder via ce service.\nExemple : banque australienne.\nL'auteur a travaillé pour une banque qui avait un problème de scalabilité de sa DB.\nMalheureusement les autorisations étaient implémentées sous forme de stored procedures, et les toucher était trop dangereux.\nAlors ils ont décidé de créer un service pour cacher la DB, et faire en sorte que la nouvelle logique autour des d'autorisations ne soit plus dans la DB elle-même, mais implémentée chez les clients.\nL'avantage de ce pattern par rapport à la database view c'est qu'on peut mettre de la logique dans le service, et qu'on peut aussi proposer à nos clients d'écrire.\nPar contre, il faudra que nos clients utilisent l'API pour y accéder, et pas du SQL.\nComme avec la database view, il s'agit plutôt d'une solution temporaire avant de faire des changements plus profonds pour séparer la DB dans les bons services.","pattern-database-as-a-service-interface#Pattern: Database-as-a-Service Interface":"Un des cas où on peut exposer une DB de manière publique c'est si les clients ont besoin de jouer des requêtes SQL directement sur les données qu'on propose.\nPar exemple pour obtenir des insights business avec des outils comme Tableau. Martin Fowler parle du reporting database pattern, mais l'auteur préfère généraliser le nom du pattern.\nPar contre, il faut bien qu'on sépare cette DB publique de notre DB privée.\nLa DB exposée ne peut être que read-only.\nPour implémenter, il propose que la DB publique soit synchronisée avec la DB privée au travers d'un mapping engine.\nLe mapping engine permet de garantir que les deux DB publique et privée peuvent diverger et fonctionner ensemble quand même.\nLes écritures doivent se faire via API.\nIl y aura donc une latence entre ce qu'on écrit, et ce qu'on lit de la DB publique qui pourrait être en retard dans la synchronisation.\nPour ce qui est de la manière d'implémenter le mapping engine :\nUne première solution robuste peut être d'utiliser le change data capture de la DB. Pour l'exploiter, il y a des outils comme Debezium.\nUne autre solution serait d'avoir un batch process qui met à jour régulièrement la DB publique.\nEt une 3ème option peut être d'émettre des events, et de reconstruire la DB à l'extérieur à partir de ceux-ci.\nCette solution est plus avancée que le database view pattern, et aussi plus difficile à mettre en place d'un point de vue technique.","transferring-ownership#Transferring Ownership":"Les précédents patterns permettaient juste de mettre en pansement sur une grosse DB, mais il faut que les bonnes données aillent au bon endroit.","pattern-aggregate-exposing-monolith#Pattern: Aggregate Exposing Monolith":"Quand on extrait un microservice, il y a parfois besoin d'accéder aux données qui sont encore dans le monolithe, ou de les modifier.\nS'il est légitime d'un point de vue domaine que ces données soient possédées par le monolithe, alors il peut exposer des endpoints.\nLes données sont regroupées en aggregates, et sont associées à des machines à état sous forme de code. Quand le monolithe ou un microservice fournit des endpoints, il donne en fait la possibilité d'accéder à la machine à état pour savoir ce qu'on va pouvoir ou non faire avec les données.\nLe fait pour le monolithe de fournir des endpoints pour travailler avec certaines données, peut être une étape vers l'extraction d'un microservice organisé autour de ces données.\nFaire des appels représente plus de travail que de faire des queries directement en DB, mais c'est bien mieux sur le long terme. Il ne faut recourir aux autres techniques (database view pattern etc.) que si on ne peut pas changer le monolithe.","pattern-change-data-ownership#Pattern: Change Data Ownership":"Dans le cas où la donnée dont le microservice a besoin se trouve encore dans la DB du monolithe, mais que c'est le microservice qui devrait la posséder, il faut la déplacer dans le microservice.\nLe monolithe devra alors appeler le microservice pour obtenir la donnée, ou demander des changements.\nLa question de savoir si les données doivent appartenir au micorservice se résout en se demandant si la logique qui contrôle la donnée (automate à état de l'aggragate, contrôle des règles de consistance etc.) se trouve dans le microservice.\nDéplacer de la donnée est difficile. Ça peut impliquer de devoir casser des foreign keys, des transactions etc. ce sujet est traité plus tard dans le chapitre.","data-synchronization#Data Synchronization":"On peut avoir besoin de synchronisation entre la DB extraite dans le microservice et celle restée dans le monolithe.\nPar exemple, si on utilise le strangler fig pattern et qu'on switch vers le nouveau microservice, puis qu'on veut revenir en arrière, il faut que la DB qui est restée dans le monolithe soit synchronisée avec ce qui a pu être fait par le microservice et sa DB.","pattern-synchronize-data-in-application#Pattern: Synchronize Data in Application":"Cette technique a été utilisée par l'auteur pour la migration des données des données médicales danoises, d'une DB MySQL à une DB Riak, pour des raisons de performance.\nLe système ne pouvait pas être arrêté pour suffisamment de temps, donc le changement a dû se faire de manière incrémentale.\nCa se passe en 3 étapes :\nÉtape 1 : Bulk Synchronize Data.\nOn migre les données vers la nouvelle DB, par exemple avec un batch job.\nQuand le job est terminé, il peut y avoir encore les nouvelles données manquantes : on va tout de suite mettre en place un processus de change data capture pour alimenter la nouvelle DB depuis l'ancienne à chaque changement.\nÉtape 2 : Synchronize on Write, Read from Old Schema.\nLes fonctionnalités de production ne changent pas et utilisent toujours l'ancienne DB.\nLa nouvelle DB est mise à jour régulièrement, et on peut faire des vérifications dessus pour s'assurer qu'elle est mise à jour correctement.\nÉtape 3 : Synchronize on Write, Read from New Schema.\nOn continue d'écrire dans les deux DB, mais on lit depuis la nouvelle pour alimenter la production.\nUne fois qu'on a suffisamment confiance dans la nouvelle DB, on peut éliminer l'ancienne.\nCe pattern peut être pertinent quand on migre la DB avant de migrer le code, pendant une extraction de microservice.\nCe pattern nous donne l'avantage de facilement pouvoir faire le switch du microservice vers le monolithe, avec une DB déjà séparée.\nAttention à ne pas utiliser ce pattern si à un moment donné, à la fois le monolithe et le microservice doivent écrire dans leurs DBs.\nC'est par exemple une mauvaise idée si on utilise un système de canary release.","pattern-tracer-write#Pattern: Tracer Write":"Ce pattern consiste à déplacer les données progressivement vers le microservice.\nLes données sont initialement lues et écrites dans le monolithe.\nDès qu'un bloc de données est déplacé, le microservice devient la source de vérité pour ce bloc, et le monolithe comme les autres microservices devront le lire depuis là.\nOn continue jusqu'à ce que l'ensemble des données à déplacer soient lues depuis le microservice (tout en restant synchronisées dans le monolithe en cas de besoin de switch).\nPour ce qui est de la synchronisation elle-même :\nL'auteur conseille d'envoyer les commandes d'écriture à la bonne source de vérité, et d'éviter les mécanisme de synchronisation à deux sens (écrire dans n'importe quelle source et avoir une synchronisation en arrière-plan), parce que c'est difficile à implémenter.\nIl faut s'attendre à avoir une certaine latence entre les deux sources, et donc prévoir une eventual consistency, étant donné que pour certaines données la première sera la source de vérité, et pour d'autres données ce sera la 2ème.\nSi on a un système event driven, ou un mécanisme de change data capture, on aura des facilités à implémenter le pattern.\nL'auteur conseille de prévoir un mécanisme de vérification de l'intégrité des données entre les deux sources (par exemple des scripts SQL), pour s'assurer que tout va bien.\nCe pattern a notamment été utilisé chez Square, pour extraire de manière incrémentale un microservice pour découpler leur système de commande de nourriture.","splitting-apart-the-database#Splitting Apart the Database":"On parle ici de séparation de schémas logiques, qui peuvent être sur une même database engine, ou sur différentes database engines.\nL'auteur conseille d'utiliser un outil pour gérer les migrations sur chaque DB. Il conseille en particulier FlywayDB.\nPour extraire un microservice, on doit extraire le code, et la donnée qui lui appartient. On peut faire :\nD'abord le split de la DB.\nOn se donne alors la possibilité de switcher vers le nouveau microservice ou vers le monolithe sans perte de données.\nLe problème c'est qu'on n'aura pas de gain court terme quand on aura extrait la donnée.\nPattern: Repository per bounded context.\nOn a en général une couche de repository qui permet de mapper le code à la DB. Il s'agit ici de découper ce repository en suivant les lignes de nos bounded contexts.\nÇa va nous aider à y voir plus clair sur quel bounded context utilise quelle donnée extérieure à lui.\nOn peut ajouter à ça un outil pour visualiser la structure de la DB, comme SchemaSpy.\nPattern: Database per bounded context.\nAvant de séparer le code de chaque bounded context en microservice, on va ici séparer les données.\nChez ThoughtWorks, un mécanisme de calcul a été modularisé, avec chaque module ayant sa DB, ce qui a fait un excellent exemple de modular monolith. L'extraction de microservices n'a jamais été faite.\nL'auteur recommande ce pattern aux startups qui ne devraient pas encore extraire de microservices, mais qui peuvent comme ça réduire la complexité de leur système.\nD'abord le split du code.\nL'avantage c'est qu'on va avoir des résultats rapides en ayant un code plus modulaire, et un artefact déployable indépendamment.\nL'inconvénient c'est que parfois les équipes migrent le code, et ne migrent pas la donnée ensuite, ce qui amène de la douleur à long terme.\nPattern: Monolith as data access layer.\nAu lieu de laisser notre microservice accéder à de la donnée qui devrait lui appartenir dans la DB du monolithe, on expose une API dans le monolithe, et le microservice l'utilise.\nIl faut le faire seulement si du code gérant cette donnée est encore dans le monolithe.\nÇa permet de cacher l'information, le temps de migrer la donnée et son code vers le microservice (ou dans le cas où on aurait décidé de ne pas déplacer la donnée).\nPattern: Multischema storage.\nLa migration des données prend du temps, mais on peut profiter des nouvelles features pour aller dans le bon sens et placer la donnée dans le microservice.\nOn se retrouve alors temporairement avec de la donnée dans le microservice, et de la donnée pas encore migrée dans le monolithe.\nSplit les deux en même temps.\nL'auteur le déconseille : c'est une trop grosse étape qui ne permet pas d'avoir du feedback suffisamment rapidement sur ce qu'on fait.","schema-separation-examples#Schema Separation Examples":"","pattern-split-table#Pattern: Split Table":"On peut avoir des cas où une table contient des données qui doivent aller dans deux microservices différents. Dans ce cas, il faut les séparer dans deux tables avant de les extraire aux bons endroits.\nUn exemple peut être une table qui contient une colonne item et une colonne stock level, avec les données de ces colonnes devant respectivement aller vers le service Catalog et Warehouse.\nOn va dans ce cas simplement extraire les deux colonnes dans deux tables différentes.\nUn exemple plus complexe peut être d'avoir une colonne status, qui est écrite par du code appartenant à deux bounded contexts à extraire : Customer Management le met à jour pour indiquer si l'utilisateur est vérifié ou non, et Finance peut le mettre à jour pour indiquer que l'utilisateur est suspendu parce qu'il n'a pas payé.\nDans ce cas précis, on pourrait décider que cette colonne doit appartenir à Customer Management _parce qu'il s'agit de gérer les utilisateurs, auquel cas _Finance devra faire un appel vers Customer Management à chaque fois qu'il faudra suspendre un utilisateur.\nIl faut bien un seul bounded context qui \"possède\" chaque donnée.","pattern-move-foreign-key-relationship-to-code#Pattern: Move Foreign-Key Relationship to Code":"On a régulièrement besoin de faire des foreign keys d'une table à une autre, à la fois pour que la DB garantisse la consistance des données, et aussi pour des raisons de performance quand on fait des jointures.\nDans le cas où l'association doit être faite vers une table possédée par un autre bounded context, on ne peut pas faire de foreign key DB, puisque les deux tables sont dans des DB différentes.\nLa solution est de casser la relation de foreign key dans la DB, et de faire le lien dans le code.\nCôté performance ce sera beaucoup moins bien. En fonction du besoin, on peut être amené à faire des recherches groupées, ou à créer un cache local de la donnée de l'autre microservice, contre laquelle on veut faire une jointure.\nDes outils comme Jaeger permettent de mesurer la latence, pour voir si même plus lente, elle n'est pas acceptable quand même.\nL'autre problème c'est la consistance qui n'est plus garantie par la DB : la donnée qui est liée à notre donnée peut être supprimée sans qu'on ne le sache.\nOn pourrait vérifier qu'aucun microservice n'a de lien vers nos données avant d'en supprimer. Mais il faudrait alors un mécanisme de lock, et il faudrait le faire avec de nombreux services. L'auteur le déconseille fortement.\nUne meilleure solution peut être que le service ayant besoin du lien prenne en compte que l'autre peut à tout moment répondre que l'objet n'existe plus.\nOn pourrait aussi consommer des events de surpression dans les services qui ont besoin du lien, pour pouvoir mettre à jour leur cache local de la DB du service visé par le lien.\nEt une dernière solution acceptable peut aussi être de ne pas permettre la suppression de rows qui pourraient être liées dans d'autres microservices.\nLa solution peut être dans ce cas de faire un soft delete avec un champ.\nAttention à ne pas casser une relation entre des choses qui devraient faire partie du même aggregate.\nPar exemple, si on a une table Order, et une table OrderLines qui contient le détail des articles commandés, ils devraient clairement faire partie du même aggregate et rester ensemble. Les séparer mènerait à des problèmes d'intégrité.","exemple--shared-static-data#Exemple : Shared Static Data":"On parle ici de reference data, par exemple des codes de pays qu'on finit par mettre en base.\nPattern: duplicate static reference data.\nChaque microservice va dupliquer la donnée dans sa DB.\nEn fonction du contexte, une inconsistance de ces données entre microservices peut ou non être un problème important.\nDans le cas où ça l'est, on peut avoir un système de synchronisation qui fonctionne en arrière-plan.\nCe pattern est à utiliser rarement, si on a de grands sets de données, ou si on doit faire des jointures contre ces données.\nPattern: Dedicated reference data schema.\nOn met en place une DB publique, partagée entre plusieurs microservies.\nLe fait qu'elle soit stable fait que la rendre publique n'est pas très dangereux.\nAttention par contre aux potentiels changements de structure qui seraient très difficiles à gérer.\nPattern: Static reference data library.\nDans le cas où notre jeu de données est petit (comme les codes de pays), on peut les mettre sous forme de code dans une librairie installée par les microservices qui en ont besoin.\nPar contre, un point négatif c'est que la librairie peut créer un besoin de déploiement de plusieurs microservices en même temps dans le cas où son contenu change : on crée de la dépendance entre microservices.\nCe pattern est particulièrement utile quand il est acceptable d'avoir plusieurs versions de la donnée dans divers microservices.\nPattern: Static reference data service.\nOn peut créer un microservice à consulter pour connaître les reference data.\nSi la création et le déploiement d'un microservice simple prend des jours ou plus dans notre organisation, alors ce pattern ne vaut pas le coup, sinon on peut le faire.\nTypiquement ce serait un bon candidat pour du Function-as-a-Service dans des plateformes cloud.\nCôté performance, ce pattern peut être plus rapide que de mettre les données en base puisque le microservice pourra les avoir sous forme de code.\nPour résumer :\nSi on n'a pas besoin que les données soient consistantes entre les services, on peut utiliser une librairie partagée.\nSi la donnée est plus grosse on peut la mettre en DB, localement dans chaque service.\nSi la donnée a besoin d'être consistante, on peut créer un service dédié.\nSi la création d'un service coûte trop cher, on peut créer une DB partagée pour ces données.","transactions#Transactions":"En général les DB garantissent les transactions ACID :\nAtomicity : soit l'ensemble de la transaction réussit, soit rien ne réussit.\nConsistency : l'état de la DB reste consistant avec toutes les contraintes respectées avant et après la transaction.\nIsolation : plusieurs transactions peuvent avoir lieu en même temps sans se gêner.\nDurability : une transaction validée reste en base, même en cas de panne.\nEn séparant les données en plusieurs schémas de DB, on perd la possibilité d'utiliser les transactions pour garantir la consistance de nos données entre bounded contexts.\nLes transactions sont quand même utilisées pour les données au sein des bounded contexts.\nL'atomicity notamment peut poser problème d'un point de vue consistance des données.\nPour répondre à ce problème on a plusieurs solutions, parmi elles il y a les transactions distribuées.\nLe Two-Phase Commit (2PC) est la technique la plus connue de transaction distribuée.\nLa transaction est séparée en deux phases :\n1 - Une phase de vote où le coordinateur central contacte toutes les parties prenantes de la transaction, pour leur demander si la transaction est possible pour eux.\nElles mettent un lock sur les données à modifier puis répondent OK.\nSi toutes les parties prenantes sont OK, on peut aller à la phase 2, sinon on arrête la transaction.\nSi on arrête la transaction, un message d'annulation sera envoyé à ceux qui avaient dit OK pour les débloquer.\n2 - Une phase de commit où le coordinateur demande à toutes les parties prenantes (qui ont déjà préparé le changement à faire et ont bloqué les données) d'appliquer les modifications.\nCe système 2PC a des problèmes :\nLa latence peut conduire à avoir des incohérences temporaires sur les données de chaque système.\nOn a un gros risque de problèmes type deadlock où un des serveurs ne répond pas alors qu'il était censé avoir bloqué les données etc.\nOn lock des tables pour des durées potentiellement importantes, qui impactent la capacité à traiter d'autres opérations.\nPour l'auteur, les transactions distribuées ont trop de problèmes pour valloir le coup : il vaut mieux ne pas les utiliser.\nUne autre alternative simple peut être tout simplement de laisser les données fortement liées ensemble dans la même DB, temporairement ou de manière permanente.","sagas#Sagas":"Une dernière solution pour répondre au problème des transactions distribuées est l'utilisation de sagas.\nIl s'agit de découper la transaction en plus petites tâches, réalisables chacune au sein de transactions ACID garanties par des DBs.\nL'ensemble n'est pas atomique, et c'est à nous de choisir quelle action de compensation il faut faire dans le cas où la saga échoue à une des étapes de la saga.\nIl faut penser à sauvegarder les informations d'une saga qui n'a pas pu aller jusqu'au bout.\nIl existe deux manières de gérer l'échec à une des étapes de la saga :\nLa backward recovery consiste à exécuter des actions qui vont annuler ce qui a été fait.\nLa forward recovery consiste à continuer la transaction, en réessayant des étapes par exemple.\nIl n'est pas toujours possible de défaire entièrement une saga.\nPar exemple, si un email a été envoyé pour confirmer une commande qui a ensuite échoué, l'action de compensation qu'on peut faire c'est renvoyer un email pour dire qu'on est désolé et qu'on s'est trompé.\nOn peut mixer les manières, par exemple en considérant qu'on rollback une commande jusqu'au moment où on arrive à l'étape où il ne reste que l'étape d'envoi, qu'on va plutôt réessayer si elle échoue.\nOn peut essayer de placer les étapes qui nécessiteraient les actions de compensation les plus coûteuses vers la fin de la saga, pour faciliter le rollback dans la majorité des cas.\nExemple : mettre l'attribution de points de fidélité après les étapes les plus critiques du processus de validation d'une commande.\nCôté implémentation, on a deux types de sagas :\nLes orchestrated sagas : on a un coordinateur (ou orchestrateur) qui va contenir l'ensemble de la logique de la saga, et qui va s'occuper d'appeller les divers services dans l'ordre, à la fois pour faire les actions nécessaires, mais aussi pour faire les actions de compensation.\nNDLR : Vlad Khononov appelle ça des process managers.\nL'avantage d'avoir la logique de la saga dans un même endroit c'est qu'on peut plus facilement comprendre ce qui est fait.\nLe désavantage c'est d'avoir une forme de couplage entre microservices, et d'avoir une tendance à ce que la logique aille dans l'orchestrateur, rendant les microservices anémiques.\nPour mitiger un peu le problème, on peut s'assurer d'avoir des orchestrateurs bien séparés pour chaque saga, aidant à pousser à garder la logique (du coup réutilisée par chaque orchestrateur) dans les microservices.\nIl existe des outils appelés BPM (business process modeling), utilisés par les non développeurs pour définir des process business par drag-and-drop.\nL'auteur les déconseille parce qu'ils sont en général utilisés par les développeurs, et sont difficiles à versionner ou à tester de manière automatisée.\nIl est plus facile de projeter des représentation des process business à partir du code (si c'est ce qu'on veut faire).\nSi vraiment on veut explorer des solutions BPM qui essayent d'être developer-friendly, il y a Camunda et Zeebe.\nLes choreographed sagas : on a un ensemble de services qui émettent des events, et réagissent eux-mêmes à des events, et l'ensemble de ces noeuds forme la saga.\nLes services ne savent pas qui écoute leur event, elles émettent simplement le bon event au bon moment, et il peut être géré par qui est intéressé.\nExemple : le service Warehouse réagit à l'event de commande initiée, et émet un event pour dire que son étape est faite, ou alors qu'il n'a pas pu le faire. Et d'autres services gèreront les conséquences en écoutant les bons events.\nComme on n'a pas d'entité centrale qui coordonne les choses, la logique ne peut pas se centraliser non plus, et on obtient naturellement une forme de découplage entre microservices.\nLe désavantage c'est qu'il est plus difficile d'appréhender le process de la saga dans son ensemble. On a aussi du mal à savoir dans quel état se trouve la saga.\nLa solution est d'ajouter une correlation ID dans l'ensemble des events de la saga, puis de les consommer pour en projeter une vue compréhensible. On peut avoir des services dont c'est justement le rôle.\nOn peut utiliser l'un ou l'autre des types de sagas en fonction des cas, mais aussi mixer les types au sein d'une même saga.\nPar exemple une choreographed saga plus large, dont une des étapes est gérée sous forme d'orchestrated saga.\nSelon l'auteur, même si les choreographed sagas ont la difficulté d'utiliser l'event driven architecture, les avantages en termes de découplage qu'elles apportent valent le coup.\nL'autre conseil c'est que l'orchestrated saga est acceptable si une même équipe est en charge de l'ensemble de la saga. Si plusieurs équipes sont impliquées, il vaut mieux une version choreographed.\nL'utilisation des sagas a l'avantage de modéliser explicitement les process business importants.\nPour aller plus en profondeur sur ce sujet, l'auteur recommande la chapitre 4 de Building Microservices, et Enterprise Integration Patterns.","chapter-5---growing-pains#Chapter 5 - Growing Pains":"Ce chapitre présente les principaux problèmes qu'on rencontre habituellement avec les microservices, avec le moment (nombre de microservices) où on les rencontre en général.\nOwnership at Scale.\nL'auteur reprend le modèle de types d'ownerships de Martin Fowler :\nStrong code ownership : chaque service a une équipe owner qui peut y apporter des modifications. Les autres doivent faire des PRs.\nWeak code ownership : chaque service a une équipe owner, mais les autres équipes peuvent aussi y faire des changements sans demander.\nCollective code ownership : il n'y a pas d'équipes assignées aux services.\nOn a souvent un collective ownership au début, et jusqu'à environ 20 personnes c'est OK pour l'auteur. Mais avec l'effectif de développeurs qui grandit, si on garde ce type d'ownership on obtiendra un distributed big ball of mud.\nLa solution c'est d'utiliser le strong code ownership, ce qui permettra aussi d'avoir des équipes centrées autour de domaines business.\nBreaking changes.\nLes microservices communiquent entre eux, mais doivent être modifiables sans impacter les autres microservices.\nSi la donnée qu'ils envoient ou leur comportement changent, ils impacteront les autres microservices, cassant des fonctionnalités.\nCe problème survient en général assez tôt, dès que plusieurs équipes gèrent des microservices qui communiquent entre eux.\nUn des signes peut être aussi le fait de chercher à déployer plusieurs services en même temps pour plusieurs équipes.\nLes solutions sont :\nDe faire du contract testing, ou au moins rendre le schéma de l'API publique du microservice explicite, et difficile à changer.\nDes outils comme protolock permettent d'empêcher les changements dans les schémas de communication.\nD'éviter de casser les contrats le plus possible.\nPar exemple ajouter une nouvelle fonction exposée en laissant l'ancienne aussi.\nDe donner le temps aux autres services de migrer si on casse le contrat.\nOn peut faire tourner deux versions du microservice en même temps, mais il faudra que ça dure peu de temps.\nL'autre solution, privilégiée par l'auteur, est d'implémenter les deux versions de l'API dans le même microservice.\nReporting.\nLes cas d'usage de reporting, de type OLAP, deviennent plus difficiles à faire quand la DB est séparée en plusieurs morceaux.\nCe problème survient assez tôt, quand on commence à décomposer le schéma de DB de notre monolithe.\nParfois on oublie les personnes qui font de l'analyse par dessus la DB du monolithe, et on s'en rend compte quand le travail de migration est déjà avancé.\nSi on veut garder la possibilité pour les personnes qui font l'analyse de continuer à le faire avec des requêtes SQL dans une DB, on peut créer une DB publique juste pour eux.\nCette DB publique serait alors alimentée par les microservices qui sont maintenant les owners des bouts de la DB qui doit être utilisée pour l'analyse.\nL'auteur renvoie vers le chapitre 5 de Building Microservices pour plus de détails sur ce sujet.\nMonitoring and Troubleshooting.\nPlus le nombre de microservices augmente, plus on a du mal à savoir si le système va bien ou pas, et quel est l'origine exacte des problèmes.\nParmi les solutions :\nAjouter un log aggregation system : collecter les logs de l'ensemble des microservices en un même endroit.\nParmi les outils il y a la ELT stack (Elastic search, Logstash/Fluent D, Kibana) et Humio.\nNDLR : l'auteur ne le mentionne pas, mais il y a aussi Datadog comme log aggregation system en SaaS.\nL'auteur conseille de commencer par le mettre en place avant de sortir des microservices : c'est utile dès le début, et en plus si on n'en est pas capable, c'est peut être qu'on n'est pas encore mature pour commencer à sortir des microservices.\nAjouter du tracing :\nAjouter un correlation ID à nos messages qui vont de microservice en microservice, pour repérer les flows internes liés à une même demande initiale.\nUtiliser un outil qui permet de tracer le temps mis pour les appels. Exemple d'outil : Jaeger.\nFaire des tests en production : il s'agit des synthetic transactions, où on va faire des tests end to end contre la production, pour se rendre compte des problèmes.\nAjouter de l'observability : comme on ne sait pas quels problèmes on aura réellement, il faut collecter beaucoup de données, et utiliser des outils qui nous permettront de faire des requêtes pour investiguer ce qu'on n'avait pas prévu.\nL'auteur conseille Distributed Systems Observability pour creuser la question.\nLocal Developer Experience.\nQuand les devs ont besoin de faire des tests en local avec une dizaine de microservices ou plus, ils risquent de ne plus pouvoir le faire à cause de la performance de leur machine.\nCe problème arrive surtout dans les organisations où il n'y a pas de strong ownership, et donc où on a besoin de toucher au code de plusieurs microservices.\nParmi les solutions :\nStubber les microservices avec lesquels le microservice modifié interagit.\nAvoir un environnement distant auquel le développeur peut se connecter, pour que son microservice local puisse communiquer avec des microservices tournant dans un environnement de développement distant.\nCette solution amène à consommer plus de ressources.\nTelepresence est un exemple d'outil qui permet de faire un workflow de dev local/remote avec Kubernetes.\nRunning Too Many Things.\nPlus on a de microservices, et plus on va avoir de process à gérer en production.\nAvec des dizaines ou des centaines de microservices, la configuration et le choix du nombre d'instances de chaque microservice vont devenir compliqués.\nOn va avoir besoin de plus en plus de personnes pour gérer l'aspect infrastructure.\nLa solution peut être l'utilisation de Kubernetes pour gérer les instances de microservice.\nDans le cas où on héberge notre solution dans un cloud public, l'auteur conseille de partir d'abord sur du FaaS (Function as a Service), et de ne partir sur des options plus complexes comme Kubernetes que si les limitations du serverless nous posent problème.\nKubernetes lui-même n'est pas très developer-friendly, on peut opter pour une solution plus abstraite comme OpenShift de RedHat.\nL'utilisation de Kubernetes n'est pas obligatoire pour faire du microservice. Si on extrait par exemple 5 microservices en tout, et qu'on est OK comme ça, on n'en aura pas besoin.\nEnd-to-End Testing.\nLes tests end to end prennent beaucoup de temps, et sont sujets aux faux positifs. Ils le sont encore plus quand il s'agit de tester des cas d'usage impliquant plusieurs microservices.\nLe risque c'est qu'on se mette à passer de plus en plus de temps sur ces tests.\nUn chapitre entier de Building Microservices détaille la manière de tester avec les microservices.\nParmi les solutions :\nLimiter le scope des tests : les tests peuvent couvrir plusieurs microservices, à condition qu'ils soient gérés par la même équipe.\nUtiliser des consumer-driven contracts (CDC) : au lieu d'avoir des tests end to end, chaque service consommateur crée des tests pour spécifier ce qu'il attend du microservice qu'il va utiliser.\nVu que c'est le consommateur qui définit le contrat, il y a moins de chances de casser un contrat implicite sans faire exprès.\nParmi les outils pour faire du CDC il y a Pact.\nMettre en place une progressive delivery, et une automated release remediation : on peut faire une canary release auprès de peu de clients, puis avoir un système automatique qui rollback si jamais des mesures clés (par exemple le 95ème percentile de latence et de taux d'erreur) ne sont pas bonnes.\nMême sans avoir la partie automatique, une simple canary release manuelle est déjà une avancée.\nGarder un œil sur ce qui n'est pas assez testé et où il y a beaucoup de problèmes, et ce qui est éventuellement trop testé pour en enlever.\nGlobal Versus Local Optimization.\nSi on adopte un strong code ownership, avec des équipes autonomes, on risque de se retrouver au bout d'un certain temps à avoir certaines duplications qui d'un point de vue global ne sont pas optimales pour l'entreprise.\nExemple : on a 3 équipes qui utilisent chacune une DB différentes (Oracle, PostgreSQL etc.), toutes pour une bonne raison dans leur contexte. Mais si on regarde les choses d'un point de vue global, il est possible que ça n'en vaille pas le coup.\nAutre exemple : avoir des manières différentes de déployer les microservices dans chaque équipe fait qu'à chaque changement d'équipe, les développeurs doivent réapprendre comment faire ça.\nL'autonomie des équipes apporte beaucoup d'avantages, notamment le fait d'avancer vite, alors que la centralisation permet d'optimiser au niveau global, au détriment d'une nécessité de consensus avec plus de monde.\nIl faut trouver un équilibre, et réajuster régulièrement.\nParmi les solutions :\nFaire comprendre aux équipes qu'en fonction du type réversible / irréversible des décisions qu'elles prennent, il faut impliquer plus ou moins de personnes, y compris si nécessaire en dehors de l'équipe.\nAvoir une manière de synchroniser les équipes entre-elles, par exemple un membre de l'équipe qui pourrait participer à des groupes cross-team pour traiter de problématiques globales.\nPar exemple, chez Monzo, les équipes peuvent soumettre des propositions sur des sujets importants, qui peuvent être ensuite discutés publiquement par les autres équipes jusqu'à arriver à une solution.\nRobustness and Resiliency.\nQuand on est dans un système distribué, on a un risque d'instabilité de notre système. Et ce risque augmente en fonction du nombre de services qu'on a et de leur interconnection.\nParmi les solutions :\nOn peut se demander à chaque appel si on a pris en compte le fait qu'il pouvait échouer, et toutes les manières dont il pouvait le faire.\nUtiliser des techniques pour isoler les services les uns des autres, par exemple par des communications asynchrones.\nUtiliser des timeouts raisonnables pour éviter les blocages trop longs.\nLancer plusieurs instances de chaque microservice pour le cas où une instance est en échec.\nPlus généralement, il faut avoir un état d'esprit qui permet la résilience, par exemple en documentant les problèmes survenus, et les solutions appliquées. Et en tirer les conséquences sur notre organisation.\nL'auteur recommande de lire le chapitre 11 de Building Microservices ou le livre Release It! de Michael Nygard pour plus de détails.\nOrphaned Services.\nOn se retrouve parfois (au bout d'une très longue période d'utilisation de microservices) à avoir des services qui tournent, mais dont personne ne se rappelle ce qu'ils sont censés faire, et qu'on a peur d'éteindre.\nParfois on a perdu le code source.\nLa solution peut être d'avoir une forme ou une autre de registry alimentée par les équipes, ou par un outil automatique qui crawl les repositories."}},"/books/outcomes-over-output":{"title":"Outcomes Over Output","data":{"1---what-are-outcomes-#1 - What are outcomes ?":"L'auteur a travaillé dans une entreprise de trading qui avait besoin de diversifier son offre. Le CEO a proposé de travailler sur une nouvelle app, mais au bout de 2 ans le projet a été abandonné sans avoir apporté de valeur : c'était le mauvais output.\nIls auraient pourtant pu changer de cap pour faire des choses qui apportaient de la valeur beaucoup plus vite, mais sont restés sur un more waterfall.\nLa raison pour laquelle on est obsédé par la réalisation de tâches est liée à l'ère industrielle où il fallait réaliser des objets qui étaient vendus une fois prêts.\nCette approche ne marche pas avec le logiciel parce qu'une feature peut très bien n'apporter aucune valeur.\nExemple : les popups qui invitent à s'inscrire à une newsletter fonctionnent techniquement, mais n'apportent pas de valeur parce que les gens s'en vont.\nIl faut donc se concentrer sur le fait d'apporter de la valeur, avec le moins de features possibles : c'est le principe des outcomes.\nDéfinitions utilisées dans ce livre :\nUn outcome est un changement dans le comportement d'un être humain qui amène des résultats business.\nL'impact caractérise les résultats business en question.\nL'output représente les éléments concrets qui ont été mis en place pour provoquer l'outcome.\nDu point de vue des managers :\nSi on demande à une équipe de réaliser un impact, ce sera trop abstrait, et le résultat de trop de facteurs pour qu'ils puissent suivre leur avancement.\nSi on leur demande de se concentrer sur un output, ils seront limités dans leur créativité et l'output risque de ne pas apporter de valeur.\nC'est pour ça qu'il faut leur proposer de travailler sur un outcome : ils pourront trouver des solutions et mesurer la valeur qu'ils apportent.\nL'auteur propose d'étendre le premier principe agile qui dit qu'il faut délivrer un logiciel de valeur en continu en parlant de délivrer de la valeur en continu.\nIl propose l'outcome comme définition de la valeur : il faut donc faire avancer nos outcomes en continu.\nL'autre outil complémentaire de l'outcome c'est l'expérimentation. En réalisant de petites expérimentations pour essayer de faire avancer l'outcome, on réalise un projet agile.\nPour l'auteur, le MVP n'est rien d'autre qu'une expérimentation.","2---using-outcomes#2 - Using outcomes":"Les êtres humains dont le comportement vise à être modifié par un outcome peuvent être les clients externes, tout comme des employés internes à l'entreprise. Tant qu'on a une modification de comportement qui amène un impact business on est bon.\nLes impacts sont constitués de la somme d'un grand nombre d'outcomes.\nExemple de recherche d'outcome : on a un impact qui est de passer d'une visite des clients sur le site web d'une fois par mois à deux fois par mois. On va chercher quel comportement plus spécifique peut les faire venir plus souvent.\nSi on sait qu'ils visitent le site après avoir ouvert la newsletter, on peut choisir comme outcome de les faire ouvrir la newsletter plus souvent.\nSi on sait qu'ils visitent le site après un partage d'un élément de notre entreprise de la part d'un de leurs amis sur un réseau social, on peut choisir comme outcome de faire en sorte que nos contenus soient plus partagés sur les réseaux sociaux.\nVu que les outcomes sont des comportements humains, ils sont mesurables facilement.\nL'outcome doit idéalement être un leading indicator, c'est-à-dire que l'action mesurée va déclencher un impact plus important. Elle vient avant, et donc permet de prédire l'impact.\nEn général on n'est pas sûr qu'un outcome donné permet de driver un impact. Il faut alors poser les hypothèses pour que ce soit vrai.\nLe mouvement Lean Startup propose un cadre pour formuler ces hypothèses. Il s'agit de (1) poser ce qu'on croit, et (2) les preuves qu'on cherche pour être sûr que c'est vrai.\nExemple :\n1 - On pense que partager des contenus de l'entreprise sur des réseaux sociaux provoque un afflux plus important de nos clients sur notre site.\n2 - Nous saurons que c'est vrai quand nous constaterons une corrélation entre les partages sur les réseaux et l'afflux sur le site.\nPour vérifier notre hypothèse, on va mener des expérimentations. L'une des manières de le faire est de réaliser un MVP, c'est-à-dire la plus petite feature pouvant prouver que notre hypothèse est vraie.\nIl y a 3 magic questions à se poser pour traiter les outcomes :\n1 - Quels sont les comportements utilisateurs qui drivent des résultats business ?\nIl s'agit de chercher l'outcome.\nOn va naturellement se concentrer sur l'utilisateur, et chercher à comprendre ce qui le motive, ses besoins, ses problèmes.\nOn va donc dès le départ partir de l'utilisateur plutôt que de partir des features qu'on avait en tête : il s'agit d'un shift sur l'approche.\n2 - Comment faire en sorte que ces utilisateurs aient plus de ces comportements ?\nIl s'agit de faire avancer l'outcome choisi.\nVu qu'on se concentre sur l'utilisateur tout au long, on peut imaginer diverses features, et même des actions qui ne sont pas des features : notre pricing, notre positionnement, notre manière d'interagir avec eux etc.\n3 - Comment savoir si on a raison ?\nIl s'agit de faire le lien entre outcome et impact.\nIl faut lister ce qu'on sait et ce qui nous manque, et trouver des moyens de tester ce qui nous manque.\nLes leaders et les subordonnés n'arrivent en général pas à se comprendre parce qu'ils pensent à différents niveaux d'abstraction : les impacts pour les leaders et les outputs pour les subordonnés.\nLe solution est d'introduire la notion intermédiaire d'outcome, qui permet à la fois aux leaders de comprendre le lien avec l'impact, et aux subordonnés d'en dériver des outputs.\nLes outcomes permettent de faciliter le suivi de l'avancement des deux côtés.\nPour commencer à utiliser les outcomes dans un système où on mesure les outputs, on peut chercher les comportements utilisateur modifiés par ces features, et se concentrer sur leur mesure.\nÇa va pousser les développeurs à délivrer de la valeur de manière plus incrémentale, pour faire avancer l'outcome.\nC'est en particulier vrai pour les initiatives de refonte internes qui ont tendance à être suivies par l'empilement des outputs plutôt que par la recherche de valeur incrémentale.\nLes OKR sont souvent mal utilisés, parce qu'on part des outputs qu'on a déjà en tête, et qu'on essaye de les exprimer dans le langage OKR, alors que leur but est de laisser les outputs ouverts pour pouvoir expérimenter et itérer.\nLa bonne manière d'être sûr d'obtenir de bons OKR est d'exprimer les KR sous forme d'outcome.","3---outcome-based-planning#3 - Outcome-based planning":"On a en général plusieurs outcomes au sein d'une équipe, et un grand nombre si on a la responsabilité de plusieurs équipes. Ça nous amène à penser en termes de systèmes d'outcomes.\nLes roadmaps basées sur les outputs posent problème parce qu'elles amènent à promettre de délivrer des choses concrètes, mais ne correspondent en général à aucune réalité.\nC'est un peu comme promettre qu'on traversera un désert de taille inconnue en un temps précis.\nL'auteur propose plutôt les roadmaps basées sur les outcomes, où il s'agit d'identifier un ensemble d'outcomes qui permettent d'arriver à l'impact business.\nIl existe plusieurs méthodes pour faire une telle roadmap :\nLe customer journey qui est détaillée dans ce livre.\nIl s'agit de créer un schéma représentant chaque acteur sur une ligne horizontale, avec la liste des activités qu'il est susceptible de faire.\nPar exemple, un client qui va sur notre boutique, trouve un produit, demande de l'aide, paye etc.\nCe schéma permet de visualiser la liste des comportements de nos utilisateurs, et donc de réfléchir à ceux qu'on veut favoriser ou inhiber pour se les choisir en tant qu'outcome.\nOn va identifier les activités boosters qui favorisent l'impact qu'on a en tête, et des activités blockers qui le défavorisent.\nOn peut par exemple faire ça avec des post-its, dans une activité collaborative.\nL'Impact Mapping.\nL'Outcome Mapping.\nUne fois qu'on a les outcomes qui nous intéressent, on peut chercher des idées d'output qui permettent d'y répondre. Et on va typiquement formuler notre roadmap de cette manière :\n1 - Nous pensons que si on fait [outcome], on aura un meilleur [impact].\n2 - Nous pensons que nous pourrons augmenter [outcome] avec [output], [output] et [output].\n3 - Nous testerons ces idées d'output pendant Q1 de l'année qui vient.","4---organizing-for-outcomes#4 - Organizing for outcomes":"Les organisations sont généralement structurées autour des notions de produit ou de canal, ce qui les pousse à adopter une vision centrée autour des outputs.\nOn pourrait plutôt les structurer autour de la notion de comportements ou de parcours utilisateur pour se concentrer naturellement sur les outcomes.\nExemple de l'entreprise HBR (Harvard Business Review), qui a adopté une organisation en outcomes en 2016 :\nHBR propose un site d'e-commerce pour vendre des articles, livres et autres contenus.\nL'équipe produit souffrait des problèmes habituels : une backlog qui ne se vidait pas, des difficultés négocier et prioriser, la sensation que les résultats n'étaient pas là par rapport à l'effort fourni.\nIls ont commencé à s'intéresser au management par outcome pour un projet consistant à réduire le bounce rate, c'est-à-dire le nombre de clients qui ne complètent pas leur achat.\nL'impact (ou outcome business) était d'augmenter les ventes, avec l'hypothèse que réduire le bounce rate mènerait à l'augmentation des ventes.\nIls ont creusé les données et réalisé des interviews pour comprendre le contexte dans lequel les bounces survenaient, et pourquoi.\nIls ont par exemple remarqué que le prix n'était pas clair dès le départ, ou mal adapté à la localisation géographique de l'utilisateur. Et ils ont fait des features pour y remédier.\nCôté suivi du travail, le management a dû passer d'une estimation d'output à une estimation d'outcome : ils définissent en amont de valeur à atteindre sur un outcome à une date donnée, et font le point régulièrement sur l'évolution de la métrique.\nLes leaders étaient chargés de prioriser 3 outcomes pour un trimestre donné. Une des manières pour les faire se mettre d'accord était de leur poser la question : “quels sont vos sujets d'inquiétude ?”.\nIls ont entamé des changements côté workflow :\nAu départ il y avait :\nDes PM responsables chacun d'une page ou d'un bout de page de l'application.\nDes designers travaillant en amont par projets.\nDes développeurs prenant le relai des designers, et travaillant aussi par projets.\nParmi les changements mis en place :\nLes designers et les développeurs étaient appelés à collaborer dès le début du projet, pour être dans une démarche moins waterfall.\nEn priorisant les outcomes, ils se sont rendu compte qu'ils tournaient principalement autour de deux impacts : (1) faire en sorte que les utilisateurs consomment plus de choses sur leur site, et (2) faire en sorte qu'ils achètent plus de choses.\nIls ont donc organisé les effectifs produit en 3 équipes :\nUne équipe pour augmenter la consommation.\nUne équipe pour augmenter la vente.\nUne équipe “Opérations” chargée de résoudre les problématiques qui survenaient au quotidien.\nLa 3ème équipe avait un fonctionnement par output (ce qui est OK d'après l'auteur, parce que leur travail n'est pas sujet à incertitude), contrairement aux deux autres qui fonctionnent par outcome.\nLe fonctionnement par outcome implique de séparer les responsabilités entre l'équipe produit qui choisit quoi faire, et les leaders qui sont responsables du succès de l'ensemble.\nIl s'agit donc pour les leaders d'abandonner une partie du contrôle sur ce qui est fait.\nIl est cependant nécessaire de compenser ça par une collaboration plus étroite et plus fréquente entre leaders et équipe produit. Les leaders ayant une vision plus large des problématiques de l'entreprise, ils sont à même d'apporter des choses dont l'équipe produit n'est pas au courant.","5---outcomes-for-transformation#5 - Outcomes for transformation":"L'approche basée sur les outcomes peut être aussi utilisée pour entamer un changement organisationnel : on va considérer le changement de comportement des employés comme objet de l'outcome, menant à un impact business, et les personnes voulant mener la transformation comme ceux qui doivent faire avancer l'outcome.\nL'auteur conseille :\nDe considérer que nos collègues sont nos clients.\nOn peut lister les activités de ces collègues, et se demander lesquelles sont utiles pour notre transformation, pour trouver l'outcome à faire avancer.\nOn va alors nous intéresser à leurs besoins, problèmes, et réfléchir à la manière dont on leur communique la valeur que le changement va leur apporter.\nD'adopter une démarche incrémentale et expérimentale.\nLes solutions pour faire avancer nos outcomes sont incertaines. Il faut les tenter pour voir lesquelles marchent et avancer itérativement, de manière agile.\nPar exemple, si on se dit que mettre en place une formation permettrait d'améliorer les compétences de nos collègues, on peut envoyer un email pour demander qui est intéressé, et voir si notre hypothèse de fort intérêt se vérifie."}},"/books/reinventing-organizations":{"title":"Reinventing Organizations","data":{"introduction#Introduction":"Il est possible que nous ne voyions pas certaines choses évidentes par aveuglement idéologique.\nExemple : jusqu'à très récemment (années 90), les scientifiques n'avaient pas \"pensé\" à vérifier le fait qu'on ait plusieurs cerveaux. On en a en fait 3 : le cerveau dans la tête, un autre dans les intestins (avec autant de neurones que le cerveau d'un chien), et un autre dans le cœur (avec autant de neurones que le cerveau d'une souris).\nL'aveuglement en question vient sans doute du fait qu'on n'ose pas envisager plusieurs sources de décision.\nLes organisations modernes ont permis depuis 2 siècles un progrès fulgurant au regard de l'histoire.\nMais ces derniers temps, on voit bien que quelque chose ne va pas.\nIl nous faut un cadre conceptuel pour envisager quelque chose d'autre qui soit concret.\nOrganisation de ce livre :\nPartie 1 : il s'agit de regarder les changements historiques qui ont pu avoir lieu, en particulier au sujet des formes l'organisation. Comprendre le passé peut permettre d'entrevoir les formes organisations futures.\nPartie 2 : l'auteur a étudié 12 organisations (de plus de 100 personnes et plus de 5 ans d'existence) qu'il considère comme fonctionnant selon le nouveau paradigme.\nCes organisations ne se connaissent pas, et pourtant leurs pratiques convergent.\nPartie 3 : l'auteur a étudié les conditions de réussite de ces organisations.","1---histoire-et-développement-des-organisations#1 - Histoire et développement des organisations":"","11-dun-paradigme-à-lautre--modèles-dorganisation-dhier-et-daujourdhui#1.1 D'un paradigme à l'autre : modèles d'organisation d'hier et d'aujourd'hui":"Selon l'auteur, l'évolution de l'humanité a par le passé avancé par bonds en avant successifs, et ça dans de nombreux domaines (même si ce livre s'intéresse spécifiquement à l'évolution des organisations).\nIl désigne chaque stade majeur de l'évolution de l'humanité par une couleur, tiré du code couleur proposé par Ken Wilber dans sa \"Théorie intégrale\" (dans une version légèrement remaniée).\nStade Réactif - Paradigme Infrarouge\nStade primitif de l'humanité de -100 000 à -50 000 ans avant JC.\nPetits groupes d'une douzaine de membres.\nPas de division du travail (sauf les femmes qui élèvent les enfants), pas de chefs, pas de hiérarchie, pas d'entreprise.\nViolence omniprésente.\nStade Magique - Paradigme Magenta\nA partir de -50 000 ans avant JC.\nTribus de quelques centaines de personnes.\nViolence toujours omniprésente.\nLa magie, les chamans etc. sont omniprésents.\nLes anciens ont une certaine autorité, mais la hiérarchie est très faible.\nStade Impulsif - Paradigme Rouge\nA partir de -10 000 ans avant JC.\nPremières chefferies, et des dizaines de milliers de sujets.\nViolence omniprésente.\nOn a une division des tâches, et le début de l'esclavage à grande échelle.\nLes structures sont encore très instables.\nExemple contemporain :\nOn peut comparer ça aux gangs ou mafias.\nStade Conformiste - Paradigme Ambre\nOn parle ici de l'agriculture, des Etats, institutions, administrations.\nOn est sur de l'ethnocentrisme : les individus s'intègrent à leur groupe, mais rejettent ceux qui sont en dehors.\nLa morale est très déontologiste, avec la figure du prêtre qui fait autorité.\nLa société est stratifiée, en castes, classes. On a une forte hiérarchisation. On pense en haut, on exécute en bas.\nLes structures deviennent plus stables, ce qui permet de pouvoir accomplir de grandes choses collectivement par rapport au stade précédent.\nLe management se fonde sur la surveillance.\nLes organisations fonctionnent en silos qui se rejettent la faute, mais qui obéissent quand même aux règles.\nExemple contemporain :\nOn peut comparer ça aux administrations actuelles.\nStade de la Réussite - Paradigme Orange\nÇa commence à la Renaissance, et se développe plus avec les Lumières et la Révolution Industrielle.\nNDLR : le libéralisme.\nÇa favorise la recherche scientifique, l'entreprenariat.\nL'innovation est mise en avant, plutôt que le fait de suivre des processus immuables.\nOn responsabilise les gens, en leur donnant des objectifs, et en les laissant trouver comment les atteindre.\nNDLR : un peu comme les OKR ?\nMalheureusement ça ne marche pas très bien : la direction refuse souvent de décentraliser les décisions, et la fixation des objectifs est une lutte constante.\nOn optimise le fonctionnement de l'entreprise de manière rationnelle.\nPar contre, il n'est pas question de se poser la question du sens et de la finalité de ce qui est fait.\nOn a des dérives importantes : recherche du profit à tout prix, invention de nouveaux besoins sans intérêt, destruction de l'environnement.\nExemple contemporain :\nOn peut citer les grandes multinationales (Amazon, Nike etc.).\nStade Pluraliste - Paradigme Vert\nLa partie de l'universalisme que promet le stade Orange sans vraiment le donner, le stade Vert propose de le réaliser : castes, classes sociales, patriarcat, religions, il s'agit de s'attaquer à tout ça.\nAvant le 19ème siècle très peu de gens étaient dans ce paradigme (ex : abolition de l'eslcavage, lutte pour le droit des femmes).\nA partir du 20ème siècle il y en a de plus en plus, en particulier dans le monde universitaire et associatif.\nPour autant, même s'il arrive à abattre les anciens paradigmes, il n'arrive pas à proposer une alternative réellement crédible et durable : le pouvoir éliminé finit par revenir par la fenêtre.\nLe consensus est mis en avant comme processus de décision, et les chefs sont au service du bas plutôt que l'inverse.\nLes cadres intermédiaires sont choisis et formés pour permettre au bas d'exprimer son plein potentiel et son autonomie.\nLes cadres peuvent même être directement choisis par le bas.\nLa culture d'entreprise et les valeurs sont primordiales, devant même la vision stratégique.\nElles doivent être réellement \"vécues\".\nLe paradigme Vert utilise la métaphore de la famille ou du village.\nL'intérêt des actionnaires est mis en balance avec l'intérêt des autres parties prenantes, et la responsabilité sociétale.\nLà où dans le paradigme Orange, l'intérêt des actionnaires est le principal mis en avant.\nExemple contemporain :\nOn peut citer Southwest Airlines, Ben & Jerry's et Starbucks.\nParadigme Opale\nC'est la next step 🙂\nLes nouveaux stades apparaissent de plus en plus rapidement au cours de l'histoire, on trouve aujourd'hui de nombreux stades coexistant au sein d'une même ville.\nVu l'accélération apparente, l'auteur imaginerait bien une ou deux évolutions majeures supplémentaires de notre vivant (Opale et au-delà).","12---a-propos-des-stades-de-développement#1.2 - A propos des stades de développement":"Pour éviter de penser que les formes plus avancées sont supérieures aux formes précédentes, l'auteur propose d'aborder la question par :\nLa complexité : les formes avancées permettent d'aborder le monde avec plus de nuance.\nEx : les points de vues antagonistes du Vert vs l'impulsivité du Rouge.\nLe contexte : en situation de guerre civile, la forme Rouge sera sans doute plus efficace, par contre en temps de paix et pour gérer un ensemble de process mondialisés hyper complexes, ça le sera beaucoup moins.\nEt pour éviter de catégoriser trop facilement les personnes comme étant à tel ou tel stade, on peut y ajouter que le modèle des stades ne fait que simplifier la réalité :\nChacun peut adopter un fonctionnement d'un stade ou d'un autre en fonction du moment et de son propre entourage.\nIl y a différentes dimensions (cognitive, morale, psychologique, sociale, spirituelle etc.) dans lesquelles on peut être plutôt vers un stade ou un autre.\nOn peut éventuellement parler du stade principal utilisé par une personne.\nLa catégorisation des entreprises en stade les catégorise d'abord en tant que structure et pas en tant que personnes qui seraient à tel ou tel stade.\nEt d'ailleurs même à l'intérieur de l'entreprise on peut avoir des différences : par exemple le siège social en Orange et l'usine en Ambre.\nExemple du cas de la rémunération pour chaque stade :\nSi le patron décide sur un coup de tête des augmentations, on est dans le Rouge.\nSi on a des salaires déterminés par les diplômes ou le rôle, on est sur de l'Ambre.\nSi on a des primes individuelles sur objectif, on est plutôt sur de l'Orange.\nSi on a des primes d'équipe, on est plutôt sur du Vert.\nLes dirigeants ont une influence capitale sur le fonctionnement et donc le stade d'une entreprise : si eux-mêmes utilisent principalement un stade moins avancé, l'entreprise ne pourra pas arriver à un stade plus avancé.\nL'inverse par contre est possible : exemple : des cadres initialement habitués à l'Ambre très hiérarchique, qui seraient incités par des consignes, des primes etc. à donner de l'autonomie, à faire des évaluations 360° etc. pourraient basculer petit à petit vers le Vert.\nLe stade Vert étant efficace, ses principes sont copiés : par exemple le fait d'établir des valeurs.\nMais les organisations avec fonctionnement principal Orange se distinguent par le fait que seul le résultat compte vraiment, et qu'en cas d'arbitrage, les valeurs cèdent immédiatement. La conséquence c'est que ces valeurs ne sont pas vraiment \"vécues\" (= valeurs bullshit).","13---le-stade-évolutif-opale#1.3 - Le stade évolutif Opale":"Le prochain stade de l'évolution de l'humanité peut être caractérisé comme \"Authentique\", \"Intégral\", \"Opale\" (en anglais \"Teal\").\nOn est au stade que Maslow appelle 'l'accomplissement de soi'.\nOn peut considérer qu'il y a une coupure entre les stades précédents et le stade Opale.\nLe stade Opale est celui où on se détache de son égo.\nIl s'agit de faire confiance à ce qu'on trouve juste, au fond de soi, et d'agir de manière authentique. Laisser notre nature profonde nous guider pour qu'elle s'exprime à travers nous.\nSi on considère les stades au regard de la décision :\nRouge : on décide en fonction de ce qui me permet d'obtenir ce que je veux.\nAmbre : on décide en fonction de ce qui respecte les normes sociales.\nOrange : on décide en fonction de ce qui mène à l'efficacité et au succès.\nVert : on décide en fonction de l'intégration de tous et de l'harmonie.\nOpale : on décide en fonction de ce qu'on trouve juste en son for intérieur, y compris si ça implique de prendre des risques.\nComme on est authentique et qu'on ne se cache pas derrière un masque, ça nous permet aussi de voir ce que les autres ont à offrir plutôt que leur manque.\nOn a une approche stoïcienne de l'adversité : elle est une occasion pour nous faire grandir petit à petit.\nOn n'est ni sur le pure rationnel de l'Orange, ni sur du pur émotionnel / intuitif du Vert, mais sur une synthèse prenant en compte les deux, une approche holistique.\nQuand on se recentre sur son for intérieur, on se rend compte qu'on est en fait une partie d'un tout, partie intégrante de la vie et de la nature.\nA propos des organisations :\nLe vocabulaire \"équilibre vie professionnelle - vie privée\" est révélateur du fait qu'on n'est pas intégralement soi-même au travail.\nComme les stades plus avancés permettent d'appréhender plus de complexité, les personnes utilisant principalement le paradigme Opale sont plus efficaces que les autres pour résoudre des problèmes.\nIl en est de même pour les entreprises dont le paradigme principal est l'Opale.\nDe nombreuses pratiques problématiques sont le résultat des peurs de l'égo : processus bureaucratiques, paralysie de l'analyse, culture du secret, silos en lutte, concentration du pouvoir etc.\nA l'inverse, si on laisse les collaborateurs être authentiques par rapport à ce qu'ils sont au fond d'eux, il n'y a pas besoin d'autant de règles, budgets détaillés, d'objectifs fixés etc.","2---structures-modes-de-fonctionnement-et-culture-des-organisations-opales#2 - Structures, modes de fonctionnement et culture des organisations Opales":"","21---trois-avancées-et-une-métaphore#2.1 - Trois avancées et une métaphore":"Là où dans le paradigme Orange la métaphore était une mécanique, dans le paradigme Vert elle était une famille, dans le paradigme Opale elle est un organisme vivant.\nMême bienveillant et au service d'eux, un dirigeant d'entreprise qui se présente comme père de famille par rapport à ses subordonnés n'occupe pas forcément le rôle le plus émancipateur possible.\nLes entreprises Opales ont trois caractéristiques majeures :\nL'autogouvernance : il n'y a pas de hiérarchie, on fonctionne d'égal à égal.\nL'affirmation de soi (wholeness) : il s'agit de venir au travail tel qu'on est dans sa totalité et son authenticité, y compris ses vulnérabilités, ses émotions, etc.\nLa raison d'être évolutive : comme un organisme vivant, les cellules de l'entreprise avancent dans la bonne direction de manière \"organique\".\nLes 12 entreprises étudiées :\nAES : entreprise d'énergie américaine, 40 000 collaborateurs.\nBSO/origin : entreprise de conseil en informatique aux Pays Bas, 10 000 collaborateurs.\nBuurtzorg : entreprise à but non lucratif de soin aux personnes âgées, aux Pays Bas, 7000 collaborateurs.\nESBZ : collège et lycée en Allemagne, 1500 personnes (élèves inclus).\nFAVI : entreprise de métallurgie française, 500 collaborateurs.\nHeiligenfeld : hôpital psychiatrique en Allemagne, 700 collaborateurs.\nHolacracy : entreprise de formation américaine.\nMorning Star : entreprise de transport de tomates américaine, 400 à 2400 collaborateurs.\nPatagonia : entreprise de fabrication de vêtements américaine, 1350 collaborateurs.\nRHD : entreprise à but non lucratif pour aider les personnes handicapées et sans abri, aux Etats-Unis, 4000 collaborateurs.\nSounds True : entreprise qui diffuse des contenus spirituels aux Etats-Unis, 90 collaborateurs et 20 chiens.\nSun Hydraulics : entreprise multinationale américaine qui fabrique des composants hydrauliques, 900 collaborateurs.\nCertaines de ces entreprises (comme AES et BSO/origin) étaient Opales et sont revenues à des pratiques plus traditionnelles.\nLa plupart sont Opales sur certains aspects et pas d'autres, Morning Star par exemple a une autogouvernance très élaborée, mais néglige d'autres aspects.\nL'étude de l'ensemble de ces entreprises permet d'avoir une vue d'ensemble des pratiques qui reviennent.","22---autogouvernance-structures#2.2 - Autogouvernance (structures)":"Dans la plupart des entreprises la concentration du pouvoir fait qu'il y a une grande partie des salariés qui sont démotivés, parce qu'ils n'ont aucune prise sur ce qu'ils font, et n'y trouvent donc pas de sens.\nLa forme Verte donne le pouvoir au bas de l'échelle, mais elle implique qu'il y ait en haut des dirigeants qui œuvrent pour favoriser ce fonctionnement.\nLa forme Opale va plus loin en impliquant qu'aucune personne n'ait de pouvoir sur une autre, par construction.\nExemple de Buurtzorg qui a fait passer tout un secteur d'Orange à Opale :\nLe contexte c'est des infirmières employées par des organisations de plus en plus grosses, avec une division des tâches de plus en plus marquée, et des critères de rentabilité décidés par le haut.\nRésultat : des actes chronométrés, des infirmières et des patients de plus en plus déshumanisés.\nBuurtzorg arrive là dedans avec :\nDes équipes autonomes : des groupes de 10 à 12 personnes travaillant sur un périmètre restreint, et s'occupant eux-mêmes des décisions d'organisation, planning, partenariats, résolution des problèmes etc.\nRésultat :\nLes infirmières prennent beaucoup plus le temps de s'occuper de leurs patients.\nElles veillent aussi à autonomiser un maximum leurs patients, pour qu'ils n'aient plus besoin d'elles.\nL'entreprise est particulièrement performante, par exemple : les patients guérissent 40% plus vite.\nEn 2013 Buurtzorg employait les 2/3 des infirmières de quartier des Pays Bas.\nLes équipes d'infirmières n'ont pas de chef : pour que ça marche, chaque recrue est formée aux techniques de prise de décision collective.\nTypiquement dans une réunion où il faut décider, le groupe choisit un facilitateur qui ne peut pas suggérer ni décider, mais est chargé de demander aux autres ce qu'ils proposent et pourquoi.\nPuis on discute et améliore collectivement les propositions.\nEt enfin le groupe décide et choisit une solution à laquelle il n'y a pas d'objection de principe.\nL'absence de subordination n'enlève pas les rapports de reconnaissance, d'influence et de talent. On remplace la hiérarchie de pouvoir par une \"hiérarchie naturelle\" où chacun trouve une place particulière en fonction de ce qu'il apporte au groupe.\nIl n'y a pas de middle management : il n'y a que des coachs régionaux, s'occupant chacune de 50 équipes (un nombre élevé à dessin, pour les empêcher de trop s'y impliquer).\nLes coachs n'ont pas de pouvoir ni de responsabilité, elles ne sont là que pour aider dans le cas où les équipes demandent de l'aide.\nQuelques règles que les équipes s'engagent à respecter :\nLes équipes doivent se scinder au-delà de 12 personnes.\nLes équipes doivent faire attention à se partager les responsabilités, pour éviter qu'une hiérarchie traditionnelle réémerge.\nLes équipes doivent faire certaines réunions de partage de connaissance, s'évaluer chaque année en fonction de critères qu'elles établissent, établir des plans annuels pour mettre en œuvre leurs initiatives.\nElles ont pour objectif de facturer 60 à 65% du temps.\nLes fonctions de support sont réduites au minimum : en dehors de la dizaine de coachs et du fondateur, le siège compte une trentaine de personnes, pour 600 équipes d'une douzaine d'infirmières (7000 personnes).\nDans les entreprises classiques on a beaucoup de fonctions de support : RH, planning stratégique, juridique, finance, communication, relations internes, contrôle qualité etc.\nLes fonctions de support permettent des économies d'échelle grâce à la spécialisation, mais d'un autre côté elles font perdre encore plus sous forme de démotivation des salariés.\nL'autre raison c'est l'impression de pouvoir que ça donne aux dirigeants : les fonctions support sont comme des manettes utilisées pour contrôler ce qui se passe en bas.\nChez Buurtzorg la quasi-totalité des choses est faite directement par les équipes, y compris par exemple le recrutement qui se fait en général par cooptation.\nLa spécialisation (sur des questions médicales par exemple) est principalement gérée en encourageant les infirmières qui sont dans les équipes à acquérir une spécialité, et à servir de référente sur cette spécialité pour les autres équipes.\nSi besoin, on engage une personne indépendante (sur du juridique par exemple) sans lui confier de pouvoir, plutôt que de l'embaucher au siège.\nAutre exemple d'entreprise Opale : FAVI.\nAlors que toutes les entreprises de métallurgie délocalisent en Chine, celle-ci est restée en France.\nElle est passée d'une entreprise traditionnelle de 80 personnes avec une grosse hiérarchie et pas mal de fonctions de support, à 500 personnes avec très peu de hiérarchie suite à un changement de direction.\nIls font une bonne marge et leurs salaires sont au dessus de la moyenne malgré la concurrence chinoise.\nLe personnel est divisé en équipes de 15 à 50 personnes, en général centrées autour d'un client (Audi, Volkswagen etc.).\nL'encadrement intermédiaire et la plupart des effectifs de fonction de support ont disparu, les équipes décident elles-mêmes.\nLes directeurs de clientèle sont au sein des équipes, ils partagent l'information des commandes passées par les clients chaque semaine, et l'équipe décide immédiatement du planning, en cherchant des solutions si besoin.\nLes directeurs de clientèle n'ont pas d'objectif de chiffre d'affaires.\nLà où avec les entreprises pyramidales les personnes qui sont en haut passent leur temps à se réunir pour coordonner l'ensemble, dans les entreprises horizontales il n'y a quasiment pas de réunions en haut, on se réunit en bas quand ça apporte de la valeur.\nPour revenir à Buurtzorg, le fondateur et les coachs ne se réunissent que 4 fois par an pour faire le point et voir ce qu'il y a à régler. Ce nombre est très faible à dessein, pour éviter d'interférer avec l'autonomie des équipes.\nA propos de la coordination chez FAVI :\nPour se répartir temporairement entre équipes en fonction de la charge de travail, une personne de chaque équipe se réunit régulièrement pour faire le point, puis chacun retourne dans son équipe demander qui voudrait aller dans telle autre équipe surchargée pendant quelques jours.\nC'est basé sur le volontariat.\nL'investissement est décidé par les équipes elles-mêmes. Si une équipe décidait de plaquer en or leur machine, les autres s'en apercevraient vite et exerceraient une \"pression amicale\" pour les en dissuader.\nSi le budget d'investissement total est trop élevé, le DG demande aux équipes de revenir vers lui avec un autre plan.\nCertaines fonctions support émergent d'elles-mêmes parce qu'il y a un besoin particulier à un moment donné, et doivent disparaître si elles ne suscitent pas ou plus l'intérêt des équipes.\nElles n'ont pas de pouvoir sur les équipes.\nLe contrôle laisse la place à la confiance :\nLes salariés savent mieux que la direction ou que les fonctions support quelle cadence est la plus optimale. Une fois les pointeuses et autres outils de contrôle supprimés, la productivité a augmenté.\nL'émulation qui vient avec la responsabilité et la pression des collègues régule bien mieux les comportements que la surveillance.\nLes salariés n'ont pas besoin de la direction pour s'organiser exceptionnellement pour répondre à une demande inhabituelle d'un client, et ça se fait plus efficacement parce qu'ils sont fiers de leur exploit.\nExemple de Sun Hydraulics.\nLà aussi il n'y a pas de contrôle qualité, de service achats, ni de pointeuses.\nL'entreprise est largement bénéficiaire et en croissance depuis 50 ans.\nLa gestion de projet classique est éliminée :\nIl n'y a pas d'échéances et de budgets à fixer, ni d'avancement à suivre, et pas non plus de statistiques à surveiller. Pas non plus de coupable à trouver sur qui rejeter la faute au cas où le plan serait dépassé.\nLes salariés doivent occuper leur temps à ce qu'ils estiment le plus productif. C'est la règle du \"20% de temps\" de Google, mais étendue à 100% du temps.\nLes projets qui ne trouvent personne ne sont tout simplement pas assez importants.\nExemple d'AES.\nC'est un exemple qui montre que même avec des dizaines de milliers de collaborateurs, il est possible d'avoir des pratiques d'auto-gouvernance.\nIl montre aussi qu'on peut dériver vers un mode traditionnel si on ne fait pas attention.\nLes effectifs étaient organisés en équipes de 15 à 20 personnes, avec 15 à 20 équipes par site, pour pouvoir mettre un visage sur les collègues.\nSeulement 100 personnes au siège pour 40 000 salariés.\nLes équipes avaient de nombreuses responsabilités :\nBudget, planning, charge de travail, horaires de travail.\nRecrutement et licenciement.\nFormation, évaluation.\nInvestissement, achat et contrôle qualité, stratégie de long terme.\nL'idée est que plus on leur donne de responsabilité, plus ils se sentent investis et trouvent le travail amusant.\nVu sa taille impressionnante (et avant de repasser au mode traditionnel), AES avait mis en place la règle du 80-20 : chaque salarié devait passer 80% de son temps à son métier de base, et 20% à des groupes de travail dans l'entreprise.\nCes groupes de travail remplaçaient les fonctions transverses : définition du budget, réalisation des audits etc.\nCes groupes apportaient un authentique sentiment d'appartenance du fait de la responsabilité confiée.\nLes responsabilités étant très partagées au sein des entreprises Opales, il n'y a pas d'organigramme, les titres étant considérés comme le \"sucre de l'égo\" (mauvais pour la santé).\nPlutôt que de faire rentrer les salariés dans des rôles prédéfinis, on fait émerger les \"rôles\" naturellement en fonction de l'investissement de chacun.\nEt comme elles ne sont pas figées par un titre, ces activités peuvent varier dans le temps.\nC'est très marqué chez Buurtzorg où les infirmières font tourner le management.\nCa l'est un peu moins chez FAVI qui a des \"Team leaders\" qui ont l'essentiel de ce rôle. La raison est qu'il est plus difficile de le faire tourner chez les ouvriers qui devraient arrêter leur machine, alors que ce genre de problème ne se pose pas chez les infirmières.\nCeci dit les \"Team leaders\" des équipes de FAVI ont un pouvoir limité, et s'ils se comportent de manière autoritaire, les membres des équipes peuvent tout simplement changer d'équipe librement.\nOn retrouve parfois une liste de certains rôles occupés actuellement, par exemple dans l'intranet de Buurtzorg, on peut trouver les collègues qui ont une spécialité particulière pour savoir qui contacter en cas de besoin sur un sujet spécifique.\nL'exemple d'ESBZ permet de représenter le paradigme Opale dans le milieu scolaire.\nL'école est généralement l'une des organisations les plus éloignées de l'auto-gouvernance, avec les enfants qui doivent se conformer à rester assis toute la journée, et capter le savoir du professeur, sans participer eux-mêmes.\nESBZ a adopté une manière de fonctionner très différente : chaque enfant est encouragé à incarner ce qu'il est vraiment, dans sa singularité, et dans le talent qu'il veut développer.\nLe résultat c'est que les enfants ne traînent pas des pieds mais sont heureux d'aller étudier, parce qu'ils sont les moteurs de leur propre apprentissage.\nConcrètement, la plupart des cours magistraux ont été abandonnés, et remplacés par des fiches qu'on donne aux enfants, en les laissant étudier eux-mêmes à leur propre rythme, seuls et en groupe.\nLes professeurs sont disponibles principalement pour répondre aux sollicitations des élèves par un accompagnement personnalisé, quand les autres élèves n'ont pas pu aider leur camarade.\nChaque élève peut décider de passer plus ou moins de temps sur chaque matière et thématique, avec beaucoup de modules optionnels.\nLes classes sont composés de différents niveaux, les plus grands apprennent aux plus jeunes, ce qui leur permet eux-mêmes de mieux assimiler.\nCa permet aussi aux enfants handicapés d'être intégrés aux mêmes classes, et d'avancer eux aussi à leur rythme.\nPour quand même faire en sorte que les enfants aillent dans la bonne direction, ils voient un professeur chaque semaine pour faire le point sur ce qu'ils ont fait et parler aussi de ce qui se passe bien ou moins bien.\nIls se fixent aussi des objectifs deux fois par an.\nLes objectifs sont de différents ordres, pas seulement les connaissances, ça peut par exemple aussi être le fait d'être plus à l'aise au niveau de la prise de parole en public.\nUne partie du temps est réservé à l'apprentissage chacun à son rythme, et l'autre partie est consacrée au travail sur des projets individuels et collectifs qui ont un impact dans la vraie vie, et qui sont issus de sujets que les enfants ont identifiés comme comptant pour eux.\nPar exemple : coordonner la rénovation des bâtiments de l'école, tenter d'obtenir des normes environnementales plus élevées de la part du conseil municipal etc.\nIls ont aussi des sessions dédiées à sortir de leur zone de confort, par exemple un bivouac à plusieurs dans les bois, ou méditer en silence dans un monastère pendant plusieurs semaines, ou encore parcourir l'Allemagne en bicyclette, en sollicitant le gîte et le couvert.\nCôté professeurs, ils fonctionnent en équipe : deux professeurs par classe, et une \"mini-école\" autonome toutes les 3 classes, collaborant sur une base régulière et capable de répondre aux problèmes, l'équivalent des équipes de FAVI, Buurtzorg ou AES.\nLes parents aussi sont partie prenante : comme l'école ne reçoit pas d'argent en dehors du salaire des professeurs, les parents contribuent en fonction de leurs revenus, mais aussi en donnant 3 heures de leur temps par mois.\nIls sont en auto-gouvernance pour ce temps accordé qui peut servir par exemple à rénover les locaux de l'école.\nESBZ fonctionne avec le même nombre de professeurs par élève que les autres écoles, et avec un budget inférieur, même en tenant compte de la contribution des parents. Donc ce qu'elle fait est à la portée des autres écoles.","23---autogouvernance-processus#2.3 - Autogouvernance (processus)":"Le chapitre précédent traitait de la structuration des entreprises auto-gouvernées (absence de hiérarchie et fonction support limitées, équipes de taille limitée et sur-responsabilisées), celui-ci traite des processus : comment sont prises les décisions, qui peut dépenser l'argent de l'entreprise, comment décider des primes et augmentations etc.\nLes entreprises Opales ne prennent pas la majorité des décisions au consensus, ce serait trop long.\nElles utilisent plutôt une version de la sollicitation d'avis (advice process) : toute personne peut prendre une décision, mais pour ce faire elle doit : 1) recueillir l'avis des concernés, et 2) recueillir l'avis des experts du sujet.\nLa décision est prise par la personne qui a identifié le problème ou l'opportunité, ou la personne concernée par le sujet.\nPlus la décision est importante, plus elle doit ratisser large pour recueillir les avis.\nLa personne qui prend la décision n'a pas à prendre en compte l'ensemble des avis dans une synthèse consensuelle (comme dans le cas du consensus), elle doit juste les recueillir pour en avoir connaissance au moment de sa prise de décision.\nLes avis contraires, même de la part du DG, n'invalident pas la décision.\nLa prise des avis en soi invite à l'humilité, à l'apprentissage, et à la responsabilisation. La personne qui prend la décision devra en général gérer les conséquences.\nNe pas solliciter l'avis avant de prendre une décision était une des rares causes de licenciement chez AES et d'autres.\nUn variante a été présentée dans le chapitre 2.2, au moment d'analyser le fonctionnement de Buurtzorg : un facilitateur désigné qui aide à recueillir les propositions, un 2ème tour pour les questionner et les améliorer, et un dernier tour pour que le groupe en choisisse un, tant que personne ne présente \"d'objection de principe\" (non pas juste un désaccord parce qu'il préfèrerait sa solution, mais un désaccord grave qui va à l'encontre de ses valeurs par exemple).\nLa prise de décision au consensus est typique des entreprises au stade Vert. Elle a plusieurs problèmes :\nN'importe qui peut bloquer la décision au moindre désaccord, et donc on se retrouve souvent à ne pas avancer.\nElle dilue la responsabilité : personne en particulier ne se sentant responsable, et donc personne ne s'enthousiasme à tout faire pour que ça marche, et n'assume les conséquences.\nPour ce qui est des décisions en situation de crise (par exemple si la santé financière de l'entreprise nécessite des licenciements ou ce genre de chose qui peut provoquer le chaos) :\nLe DG ne peut pas simplement reprendre le contrôle quand il a envie sans entacher la confiance dans la nature auto-gouvernée de la structure.\nLe pire serait de céder à la peur et faire des actions en cachette.\nLa solution idéale est de garder la sollicitation d'avis même en période de crise.\nExemple de FAVI : juste après la première guerre du Golf, les commandes avaient significativement baissé, et maintenir les effectifs n'était pas possible. Le DG a demandé à l'ensemble des salariés d'arrêter les machines et de se rassembler pour leur exposer la situation délicate. Un des salariés a proposé de réduire le temps de travail de l'ensemble des salariés de 25% sur les prochains mois. La proposition a été adoptée à l'unanimité.\nSi ce n'est vraiment pas possible, le DG peut reprendre le contrôle temporairement pour gérer la crise, mais il faut absolument qu'il donne des gages à ses salariés : indiquer la nature et la temporalité du changement du mode de décision, et nommer pour le faire une personne qui ne pourra pas être soupçonnée de vouloir continuer à exercer le pouvoir ensuite.\nExemple d'AES : en 2001, suite à une situation de crise, l'action de l'entreprise avait chuté, et l'accès au marché de capitaux n'était plus possible. Il fallait rapidement décider quelles centrales fermer pour ne pas faire faillite. Le nombre de salariés étant trop grand, et la décision complexe, le DG a indiqué à tout le monde qu'il allait prendre une décision unilatérale sur ce sujet, et a nommé une personne externe pour mener à bien la mission.\nA propos des décisions d'achat et d'investissement pour l'entreprise :\nDans les entreprises Opales, il n'y a pas de seuil à ne pas dépasser ni d'autorisation à demander, ni non plus de service qui fait les commandes à la place des salariés. Ce sont les salariés qui négocient et commandent ce dont ils ont besoin.\nLa règle reste qu'il faut demander l'avis des personnes concernées et expertes, et plus le montant est élevé, plus il faudra demander d'avis.\nPour ce qui de la question des achats groupés pour des raisons d'économie, ou pour avoir le même fournisseur, ça se règle en faisant confiance et en responsabilisant : une personne finira par s'en rendre compte et fera le tour des équipes pour prendre les quantités et passer la commande.\nC'est par exemple ce qui s'est passé chez Morning Star où le matériel d'emballage est utilisé en grande quantité dans les équipes.\nLa coordination émerge d'elle-même dès que le besoin s'en fait sentir.\nA propos de la communication des informations en interne : tout est transparent, et surtout les informations les plus sensibles.\nIl y a plusieurs raisons à cette transparence :\nSans hiérarchie, les équipes autonomes doivent avoir accès à toute l'information pour prendre leurs décisions.\nToute information qui serait cachée serait source de suspicion : \"Pourquoi on prend la peine de nous cacher ces infos ?\".\nDes hiérarchies officieuses réapparaîtraient entre ceux qui sont dans la confidence et ceux qui ne le sont pas.\nParmi les informations transparentes, on trouve par exemple les résultats de l'entreprise, mais aussi les salaires de chacun, et les performances de chaque équipe.\nIl n'y a pas de culture de la peur, et si certaines équipes traversent une mauvaise passe, elles peuvent entrer en contact avec celles qui réussissent mieux pour s'en inspirer.\nLes informations sont en général mises dans l'intranet de l'entreprise pour pouvoir être consultables facilement par tous, sans être déformées.\nUne autre manière de communiquer les informations importantes c'est en assemblée générale. Elles sont imprévisibles et donc risquées, et c'est pour ça qu'elles permettent de réaffirmer et renforcer la relation de confiance entre collaborateurs.\nA propos de la résolution de conflits : vu qu'il n'y a pas de hiérarchie, ils sont traités d'égal à égal, selon une méthode de résolution de conflits à laquelle les nouvelles recrues sont formées.\nExemple de Morning Star :\nElle produit 40% de la purée de tomates consommée aux Etats Unis.\nLes effectifs varient entre 400 et 2400 en fonction de la saison, et sont divisés en 23 équipes entièrement autonomes comme dans les autres entreprises qu'on a vues plus haut.\nIls ont deux principes fondateurs : \"personne ne devrait jamais employer la force contre qui que ce soit\", et \"les engagements devraient toujours être tenus\".\nLa méthode de résolution de conflits découle de ce principe, elle est utilisée en fait pour tous types de désaccords, qu'ils soient techniques, interpersonnels ou autres :\nD'abord les deux personnes en désaccord se rencontrent et discutent. Celui qui est à l'initiative de la rencontre doit formuler une requête claire, l'autre répond par oui, non, ou une contre-proposition.\nSi ça n'aboutit pas à un accord, ils trouvent une personne qui a leur confiance pour jouer le rôle de médiateur. Il les aide à trouver une solution mais ne peut pas l'imposer.\nSi ça n'aboutit pas, on réunit un jury de collègues, qui vont jouer le même rôle que le médiateur, et n'auront pas non plus de pouvoir d'imposition. Ils servent d'autorité morale, et en général ça suffit à trouver une solution.\nLa dernière carte c'est d'ajouter le DG au jury pour renforcer encore plus l'aspect d'autorité morale et régler la question.\nLe conflit doit rester privé, y compris après la résolution.\nEt les protagonistes sont dissuadés d'aller chercher des alliés pour défendre leur cause, et constituer des factions rivales.\nL'auteur a constaté que le fonctionnement de résolution de conflits de Morning Star est quasi exactement le même que celui utilisé dans d'autres entreprises étudiées.\nCe mécanisme est plus qu'un mécanisme de résolution des conflits, c'est aussi la manière dont les collaborateurs se rendent des comptes dans les entreprises auto-gouvernées.\nPar exemple, quand un collègue ne fait pas ce qu'on attend de lui, ou ne tient pas ses engagements, les collègues sont tenus d'aller le confronter directement.\nPour que ça marche, il faut que la culture de l'entreprise encourage à aller demander des comptes à ses collègues.\nOn a vu qu'il n'y avait pas de titres, mais il y a bien des rôles occupés par des personnes à un moment donné.\nLes rôles se créent en général naturellement et de manière informelle : quand un besoin se fait sentir, une personne se propose et occupe automatiquement le rôle en question. Quand le besoin n'est plus là, elle n'a plus à l'occuper.\nC'est le cas par exemple chez FAVI, AES, Sun et Buurtzorg.\nParfois il y a un processus plus officiel comme chez Morning Star.\nChaque année les collègues discutent entre eux pour formaliser mutuellement un document qui indique quels rôles ils s'engagent à occuper dans le processus de transformation des tomates, avec les indicateurs qui permettront de savoir si le travail est bien fait.\nLa raison de ce formalisme chez Morning Star, et qui n'existe pas dans les autres, c'est que le processus de transformation de la tomate est déjà très codifié, parce qu'il s'agit d'un processus en continu, sur une commodité à faible marge.\nComme le partage des rôles se fait entre collègues, il n'y a plus la possibilité de chercher à bien se faire voir pour avoir une promotion, parce qu'il n'y a pas de supérieur hiérarchique auprès de qui il faudrait jouer un rôle.\nUne autre variante de rôle explicite se trouve chez Holacracy :\nA propos de l'entreprise : initialement il s'agissait d'une entreprise informatique où les fondateurs menaient des expérimentations sur le meilleur fonctionnement possible.\nIls ont fini par fonder une entreprise de conseil en organisation chargée d'aider à appliquer la méthode Holacracy qu'ils avaient créée.\nDans leur méthode, les rôles sont créés ou modifiés/supprimés en fonction d'un processus particulier :\nUn nouveau rôle peut être proposé pendant une réunion de gouvernance (réunions spécifiquement dédiées aux rôles et au fonctionnement de la collaboration, en général sur une base mensuelle et à la demande).\nLa réunion de gouvernance se déroule comme suit :\nUn facilitateur guide le déroulement et traite chaque question mise à l'ordre du jour par les membres.\n1- La proposition est présentée par l'auteur.\n2- Les autres posent des questions de clarification pour mieux comprendre, sans réagir à la proposition.\n3- Chaque personne réagit à la proposition, sans qu'il n'y ait de réponse.\n4- L'auteur peut clarifier ou amender sa proposition en fonction de ce qui a été dit.\n5- On fait le tour à nouveau pour récolter les objections sans les discuter. Si il n'y en a pas la proposition sera adoptée.\n6- S'il y a des objections, on les traite une à une, en essayant à chaque fois d'élaborer une proposition qui l'intègre, tout en restant fidèle à l'intention initiale.\nIl s'agit d'une variante de la sollicitation d'avis, mais dans ce cas c'est le groupe entier qui sollicite et prend la décision.\nEt comme avec la sollicitation d'avis, on ne reste pas bloqué avec de longues discussions.\nMalgré les rôles, dans les entreprises auto-gouvernées chaque collaborateur est en situation de responsabilité totale et a le devoir d'agir s'il voit un problème, quel que soit le périmètre.\nIl est donc inacceptable de dire \"On devrait faire quelque chose pour régler ce problème\" et de passer à autre chose.\nEt à l'inverse personne ne peut dire \"Ça ne te regarde pas\".\nHolacracy propose de régler les \"tensions\" en réunion de gouvernance ou en réunion tactique (de travail), en fonction du type de tension, avec des processus particuliers pour chacun.\nLa \"nomination\" aux rôles, y compris pour ceux qui émergent, se fait en général de manière informelle et naturelle : les personnes qui sont volontaires et sont perçues par leurs collègues comme les plus aptes prennent le rôle.\nParfois ça peut être un peu plus explicite, par exemple chez Sun, à chaque fois qu'un poste se libère, il y a un processus en interne pour permettre aux candidats de rencontrer les futurs équipiers.\nChez FAVI, le DG a aussi mis en place un processus de confirmation pour sa propre place : tous les 5 ans il demande aux salariés de voter pour dire s'il doit rester DG. Ca permet de renforcer encore plus l'idée d'auto-gouvernance.\nA propos de la gestion de la performance :\nLes études montrent que quiconque poursuit un objectif qui a du sens, et qui a le pouvoir de décision et les ressources nécessaires, n'a pas besoin qu'on lui prodigue des paroles d'encouragement ni qu'on surdimensionne ses objectifs (on peut trouver un bon état de la recherche dans La Vérité sur ce qui nous motive de Daniel Pink).\nIl n'y a donc pas besoin de pression hiérarchique.\nPar contre, on a besoin de mesure, principalement par équipe, pour savoir si on va dans la bonne direction.\nEt on a besoin du regard des autres pour nous motiver de manière naturelle.\nC'est pour ça que les indicateurs par équipe sont transparents.\nChez Buurtzorg et FAVI les équipes peuvent facilement comparer leurs performances respectives.\nChez Morning Star, vu la plus grande diversité des rôles dans la chaîne de traitement de la tomate, les équipes s'auto-évaluent devant des groupes de collègues.\nConcernant la performance individuelle, elle est secondaire par rapport à l'évaluation par équipe.\nElle peut être évaluée par les collègues avec qui on travaille, de même qu'on les évalue.\nChez Morning Star, on reçoit un retour de chacun des collègues avec qui on a travaillé à la fin de l'année.\nChez AES, chacun se réunissait une fois par an avec son 1er cercle de collaborateurs au cours d'un dîner, et s'auto-évaluait pendant que les autres commentaient.\nChez Buurtzorg, les équipes sont libres de choisir leur mode d'évaluation annuel. Une des équipes a par exemple mis en place un processus où chaque collègue prépare la perception qu'il a de son travail et celui qu'il a des autres, et ensuite ils comparent ce qu'ils ont mis mutuellement.\nSur la question des licenciements :\nIls sont plutôt rares parce que les rôles ne sont pas fixes, et une personne qui n'est pas bonne dans un rôle peut en trouver d'autres où elle est plus douée.\nPour autant on a quand même des cas où une personne soit n'arrive pas à s'intégrer, ne respecte pas les valeurs, soit n'est pas au niveau attendu par ses collègues.\nLe licenciement n'est actionnable que par les collègues.\nLa plupart du temps, la personne va partir d'elle-même, voyant que ses collègues ne lui font plus confiance.\nChez Buurtzorg par exemple, c'est l'équipe qui peut enclencher la procédure dans le cas où un des membres perd la confiance des autres. Ils font appel à un coach régional en tant que facilitateur pour voir si le problème ne peut pas être résolu, et en dernier recours ils demandent au fondateur de licencier la personne.\nA propos de la rémunération :\nLe montant du salaire est fixé par la personne et ses collègues.\nChez Holacracy par exemple, une fois par an chaque personne répond à deux questions sur ses collègues, sur une échelle +3 / -3 :\n\"Cette personne apporte à l'entreprise une contribution supérieure à la mienne.\" et \"Cette personne dispose de bons éléments pour m'évaluer.\"\nPuis en un algorithme s'occupe de ranger les salariés par ordre, ceux du bas ayant un salaire plus faible que ceux du haut.\nChez AES la version était plus radicale : chaque personne devait solliciter des avis, puis fixer elle-même son salaire.\nChez Morning Star il s'agit d'un processus un peu plus formel : chaque personne écrit une lettre à la commission élue des salaires, et y met l'augmentation qu'elle pense juste pour elle, en l'argumentant avec les commentaires reçus de ses collègues pendant son évaluation.\nLa plupart des entreprises étudiées ici ont abandonné les primes, et n'ont gardé que la participation aux bénéfices.\nEnfin, la plupart des entreprises étudiées ici veillent à réduire l'écart entre les salaires les plus hauts et les plus bas.\nMais cet écart reste élevé, et contraste avec l'absence de hiérarchie dans le travail.\nSelon l'auteur, cette différence n'est pas forcément légitime et pourrait s'amoindrir, voire disparaître à l'avenir.\nQuelques malentendus à dissiper :\nTout le monde sur un pied d'égalité :\nMettre tout le monde à égalité c'est l'objectif du paradigme Vert. Le paradigme Opale cherche à donner du pouvoir à tous, dans une mesure suffisante pour qu'ils puissent réaliser leur potentiel.\nLes hiérarchies naturelles enchevêtrées (compétence, talent, reconnaissance etc.) empêchent de toute façon une égalité de pouvoir. On peut voir ça plutôt comme un système vivant où les relations de pouvoir sont constamment remodelées.\nAucune des entreprises étudiées ici comme Opale n'est la propriété de ses salariés. Les coopératives sont là aussi plutôt recherchées par le paradigme Vert.\nTant que le pouvoir est correctement réparti, la propriété ne semble pas importante.\nIl s'agit d'autonomisation :\nSi on parle de dirigeants qui doivent faire un effort pour autonomiser les salariés, c'est que les salariés sont en position de victime, à se battre constamment pour du pouvoir.\nL'entreprise Opales est déjà organisée selon une structuration et un fonctionnement où le dirigeant n'a pas besoin de faire d'efforts pour que le pouvoir revienne aux salariés naturellement.\nOn en est encore au stade expérimental :\nLes exemples étudiés dans ce livre montrent que non, et il y en a plein d'autres.\nOn peut ajouter à ça que les jeunes qui ont grandi avec internet sont taillés pour vouloir l'auto-gouvernance.","24---en-quête-de-plénitude-pratiques-générales#2.4 - En quête de plénitude (pratiques générales)":"Habituellement on vient au travail en masquant une part de soi. En général, on masque notre vulnérabilité et notre dimension émotionnelle.\nDans l'entreprise Opale on vient sans masque, avec la totalité de ce qu'on est.\nLe fait de ne pas avoir de promotions à aller chercher aide à être plus authentique.\nChez Sounds True, la fondatrice venait initialement avec son chien. Les autres salariés ont demandé s'ils pouvaient ramener le leur, et finalement les 20 chiens amenés régulièrement contribuent à une ambiance fraternelle, un surcroît d'humanité.\nChez Patagonia il y a un centre de développement de l'enfant sur le lieu de travail. Les salariés y amènent leurs enfants en bas âge, et peuvent les voir à la cafétéria ou au déjeuner.\nPour que nous puissions exprimer pleinement ce que nous sommes, il faut que nous soyons dans un environnement protecteur et bienveillant.\nLa plupart des entreprises ont fini par rédiger des valeurs et des chartes pour instaurer cet environnement protecteur.\nExemple d'RHD, association qui aide les personnes handicapées mentales, mais aussi les sans-abris et les alcooliques.\nIls existent depuis 50 ans, ont une croissance de 30% par an, et offrent des services d'une valeur de 200 Millions de dollars à travers le monde.\nIls fonctionnent avec le même modèle que FAVI ou Buurtzorg, avec des équipes auto-organisées, une hiérarchie existante, et des fonctions support minimales.\nLeurs 3 postulats sont :\nQue tout le monde est d'égale valeur humaine.\nQue, sauf preuve du contraire, tout le monde est fondamentalement bon.\nQu'il n'y a jamais une seule bonne façon de résoudre les problèmes qui se posent dans l'entreprise.\nLeur charte détaille 5 façons inacceptables d'exprimer son hostilité :\nLes propos et comportements dégradants : le fait de chercher à rabaisser l'autre dans sa dignité, sa valeur en tant que personne.\nLes messages négatifs indirects.\nLa menace d'abandon.\nLa négation de la réalité de l'autre.\nL'intimidation/explosion.\nRédiger des documents ne suffit pas, il faut aussi y consacrer du temps. Parmi les possibilité il y a :\nFaire suivre une formation aux nouvelles recrues pour leur présenter les valeurs et les règles de base.\nLa Journée des valeurs, qui consiste à se poser la question individuellement et collectivement de la manière dont on met en œuvre les valeurs.\nLa Réunion des valeurs, qui consiste à exposer les problèmes qu'on a pu rencontrer sur la mise en œuvre des valeurs, et suggérer d'apporter des modifications à la charte.\nL'Enquête annuelle, qui consiste à diffuser un jeu de questions au sujet des valeurs, et de l'analyser pour en tirer des conséquences.\nLa plupart des entreprises étudiées ont des pratiques qu'on pourrait appeler \"d'introspection\", où il s'agit de chercher au fond de soi, individuellement et collectivement.\nLe silence est une première manière de le faire.\nBeaucoup de structures ont des lieux destinés au silence (méditation, yoga, etc.).\nCertaines structures ont des temps dédiés à des moments de silence collectifs : dans la journée, la semaine, des jours spécifiques etc.\nOn a aussi des sessions d'éducation populaire (l'auteur ne cite pas ce terme, et appelle ça \"introspection de groupe\") où un sujet est introduit, et les salariés se séparent en groupes pour y réfléchir collectivement à partir de leurs propres expériences, pour en tirer des choses et renforcer leurs liens.\nC'est le cas de l'hôpital Heiligenfeld qui consacre 1h15 chaque semaine à cette pratique pour l'ensemble des salariés disponibles (en général la moitié des effectifs).\nDes exemples de sujets traités collectivement peuvent être : la résolution de conflit, la gestion de l'échec, la bureaucratie, la communication interpersonnelle, la gestion du risque etc.\nC'est des moments où les salariés peuvent partager leur humanité profonde, dans la beauté de leur force et de leur vulnérabilité.\nHeiligenfeld a mis en place des ateliers pour travailler les questions organisationnelles et interpersonnelles (conflits par exemple) avec des spécialistes, en moyenne plusieurs fois par mois, à la demande des équipes.\nChez Buurtzorg, ils ont mis en place des sessions basées sur la technique Intervisie, où une des infirmières exprime une problématique qu'elle a avec des clients, et le reste de l'équipe l'écoute et la soutient pour l'aider à trouver une solution qui lui appartiendrait.\nPour créer des relations profondes et authentiques avec ses collègues, il faut qu'il y ait de la narration (storytelling), c'est-à-dire raconter des histoires sur nous, et écouter celles des autres.\nPar exemple, Center for Courage & Renewal mène des ateliers où ils demandent à ceux qui veulent répondre des choses comme \"Parlez-nous d'un aîné qui a compté dans votre vie\" ou \"Parlez-nous du premier dollar que vous avez gagné\".\nChez ESBZ, chaque vendredi les élèves et professeurs se retrouvent pendant une heure. Ils commencent par chanter ensemble, puis ceux qui le veulent peuvent aller prendre le micro, et remercier ou congratuler quelqu'un pour quelque chose de spécifique. Il s'agit en fait de petites histoires.\nChez Ozvision, petite entreprise japonaise, chaque salarié a chaque année une journée de congé où on lui offre 200$ à dépenser pour faire un cadeau à quelqu'un qu'il connaît, et ensuite de le raconter aux collègues.\nA propos des réunions :\nPresque toutes les entreprises étudiées ont mis en place des techniques pour aider à maîtriser son égo et interagir avec sincérité.\nPar exemple un tour au début et un tour à la fin de chaque réunion pour indiquer comment on se sent sur l'instant.\nCa peut aussi être de commencer ou ponctuer la réunion par des moments de silence pour s'enraciner et prendre du recul.\nOu encore lire un texte, ou féliciter quelqu'un qu'on estime avoir fait quelque chose de remarquable.\nOn peut aussi inviter un facilitateur à se joindre à la réunion.\nElles ont aussi en général des procédures spécifiques pour empêcher que les réunions soient accaparées par certains.\nA propos des conflits :\n1- Il faut favoriser l'émergence des tensions :\nPar exemple, chez ESBZ, chaque classe se réunit une fois par semaine pour parler des tensions au sein du groupe.\n2- Il faut une procédure défini de résolution des conflits (comme expliqué au chapitre précédent), pour que les personnes puissent plus facilement prendre l'initiative de le traiter.\n3- Certaines structures comme ESBZ ou Sounds True forment leurs salariés à la communication non-violente.\nIl s'agit d'une méthode où on exprime son ressenti, ses besoins, et on écoute ceux des autres.\nLa gestion des locaux de travail est importante, il faut qu'elle soit accueillante. Par exemple, avoir une cuisinière pour se sentir comme chez soi, pouvoir personnaliser la décoration, pouvoir être proche de la nature quand on est au travail.\nLes préoccupations sociales et environnementales sont fondamentales, elles nous permettent de nous relier au reste du vivant et d'affirmer notre intégrité.\nIl s'agit avant tout de se demander ce qu'on trouve juste, et de voir seulement ensuite combien ça va coûter.\nLes paris qui ont l'air risqués mais qui vont dans le sens de ce qu'on trouve juste se révèlent souvent gagnants.\nLes initiatives de ce genre dans les entreprises Opales viennent des salariés eux-mêmes.\nExemple : chez AES, une salariée a eu l'initiative de faire planter des arbres pour compenser les émissions carbone de l'entreprise. Et depuis, des millions d'arbres ont été replantés.","25---en-quête-de-plénitude-processus-rh#2.5 - En quête de plénitude (processus RH)":"Recrutement :\nEn général, les candidats comme les entreprises jouent un rôle pendant les entretiens.\nDans les entreprises Opales, les entretiens sont faits par les futurs collègues de la personne qu'on veut embaucher.\nDe cette manière, ils auront tout intérêt à être honnêtes (puisqu'ils devront en assumer les conséquences au quotidien), ce qui incitera le candidat à être honnête et authentique aussi.\nVu le décalage avec les entreprises habituelles, les entreprises auto-organisées vont consacrer beaucoup d'énergie à expliquer le fonctionnement et les valeurs de l'entreprise pour s'assurer que la personne saura s'y intégrer : prendre les responsabilités, faire confiance aux autres, prendre des initiatives, être elle-même etc.\nZappos.com offre un chèque de 3000$ aux employés nouvellement embauchés qui font le choix de partir pendant la période d'essai : il vaut mieux qu'ils partent rapidement s'ils n'aiment pas la culture.\nIntégration :\nLes entreprises Opales investissent beaucoup dans l'intégration, qui ressemble souvent à une sorte de formation.\nOn y aborde les 3 piliers :\nL'autonomie : expliquer ce qu'implique d'avoir d'importantes responsabilités, et de les exercer avec un groupe.\nEn général, ceux qui ont le plus de mal sont les anciens cadres dirigeants. Selon Paul Green Jr. de Morning Star, 50% d'entre eux partent au bout d'un an parce qu'ils \"ont du mal à s'adapter à un système où ils ne peuvent pas jouer à Dieu\".\nL'intégrité : former à des techniques de communication non violente, à la maîtrise de soi, la gestion de l'échec etc. Le but étant de se montrer tel qu'on est.\nLa raison d'être évolutive : examiner la raison d'être de l'entreprise, et voir en quoi elle peut rejoindre notre propre vocation personnelle.\nSouvent les nouvelles recrues vont aussi recevoir une formation technique, en étant intégré aux divers rôles de l'entreprise pour nouer des relations.\nC'est le cas chez Sun Hydraulics où les nouveaux passent plusieurs mois dans d'autres rôles avant de rejoindre l'équipe où ils étaient censés aller initialement.\nFormation :\nEn général, n'importe quel salarié peut s'inscrire à une formation, en sollicitant des avis d'abord.\nParfois au lieu de la sollicitation d'avis il y a plutôt une limitation du montant.\nC'est le cas chez Buurtzorg, où toutes les infirmières ont 3% de leur chiffre d'affaires à dépenser en formation.\nLes entreprises Opales n'ont pas de formations pour gravir les échelons en s'améliorant en management puisqu'il n'y a pas d'échelons à gravir.\nElles ont des formations pour créer une culture partagée, des formations de développement personnel, et des formations techniques qui sont dispensées par les collègues.\nQuantité de travail :\nDans les entreprises classiques on a les ouvriers bas à qui on demande de respecter des horaires strictes et surveillées, et les cadres en haut à qui on demande de faire passer le travail avant tout et d'être joignables tout le temps.\nLes entreprises Opales proposent d'être dans la transparence, et de prendre en compte les besoins de chacun.\nLes obligations du travail sont importantes, mais les obligations personnelles aussi, elles sont à mettre en balance.\nOn demande régulièrement à chacun combien de temps et d'énergie il souhaite consacrer à son travail dans l'entreprise en ce moment.\nC'est le cas par exemple chez Holacracy et Morning Star.\nÇa peut varier, parfois selon la saison, parfois selon la période dans la vie (enfant, personne malade dont il faut s'occuper, maison à retaper etc.).\nLa procédure ne passe pas par des fonctions support RH, mais se fait de pair à pair entre collègues. Chacun essaye d'arranger l'autre, sachant qu'il sera bien content d'être arrangé quand il en aura besoin.\nPerformance individuelle :\nLa performance individuelle est mesurée par les pairs, mais il faut faire attention à ce qu'elle ne soit pas un moment de jugement et de surveillance.\nPour que ça marche, il faut :\nSe mettre en condition pour aborder la question avec bienveillance, sans réponses toutes faites, mais sans avoir peur de s'exprimer non plus.\nNe pas donner l'impression qu'on fait des remarques objectives, mais s'impliquer en parlant à la première personne, en montrant que c'est bien nous qui sommes enthousiasmés, touchés, intrigués, blessés, déçus etc. par ce qu'a fait la personne.\nAborder la question de l'évaluation non pas comme un instantané mais comme un déroulement : d'où on vient, ce qu'on a fait, où on veut aller.\nLicenciement :\nPour ce qui est du licenciement individuel :\nIl faut prendre la question comme une porte qui se ferme et qui ne correspondait pas à la voie de la personne licenciée, pour d'autres qui s'ouvrent et qui lui permettront de s'épanouir.\nPour ce qui est du licenciement collectif pour problèmes économiques :\nQuand le problème est temporaire, en général les entreprises Opales tiennent le coup parce qu'elles sont résilientes.\nQuand il s'agit de problèmes permanents (par exemple AES qui rachète une centrale en sureffectif), alors il vaut mieux ne pas garder trop de personnes.","26---a-lécoute-du-projet-des-entreprises-opales#2.6 - A l'écoute du projet des entreprises Opales":"Les entreprises classiques fonctionnent sur la peur de ne pas réussir à préserver l'entreprise. Les valeurs et la raison d'être de l'entreprise ne sont pas du tout vécues.\nLes entreprises Opales consacrent une part très importante à leur mission.\nLes entreprises Opales ne se soucient pas de leurs concurrents, puisqu'elles travaillent pour leur raison d'être, elles n'ont pas vraiment de concurrents.\nAu contraire, elles ont plutôt tendance à aider les entreprises qui font la même chose à fonctionner sur le même principe qu'elles.\nLa croissance n'est pas un objectif en soi pour les entreprises Opales.\nPar exemple Buurtzorg préfère aider les malades à guérir plus vite, quitte à les avoir pour clients pour moins longtemps, parce que c'est dans leur raison d'être.\nPareil pour Patagonia qui fabrique des vêtements qui durent plus, et qui sont réparables, quitte à vendre moins sur le coup.\nLe profit est toujours secondaire. Il s'agit de poursuivre sa raison d'être, et créer une activité viable dans ce cadre.\nEn général, le profit arrive bien plus pour les entreprises qui se lancent sincèrement dans ce qu'elles croient être porteur de sens.\nLes entreprises étant comme des êtres vivants, il s'agit d'écouter où elles veulent aller en tant que structure porteuse de sens.\nChaque membre de l'entreprise participe à cette direction par ses idées, à travers l'intérêt et l'implication qu'elles génèrent auprès des autres.\nLa stratégie est portée par l'ensemble des salariés au quotidien par des initiatives. Elle n'est pas précisée dans un endroit central, mais mise en œuvre par les salariés qui sont comme des cellules.\nLa raison d'être de Buurtzorg est d'aider les gens à retrouver une existence riche, intéressante et autonome.\nMais même en allant du côté des industriels on peut trouver du sens.\nPar exemple, FAVI a pour raison d'être de donner un travail intéressant aux personnes vivant dans la ville où ils sont, et aimer et se faire aimer de leurs clients. Ils ont décidé de ça tous ensemble.\nL'auteur pense que les pratiques transrationnelles (spiritualité, méditation etc.) peuvent aider à trouver et faire évoluer la raison d'être de l'entreprise.\nUne des techniques consiste à laisser une chaise vide représentant l'entreprise et sa raison d'être pendant les réunions. Chaque personne peut s'asseoir dessus et se poser la question de savoir si ce qui est fait va dans le sens de cette raison d'être et quelle est sa nature.\nUne autre solution est de réunir tous les salariés de l'entreprise, et d'essayer de faire émerger collectivement la direction qu'elle doit prendre.\nLes dirigeants doivent accepter que leur voix compte autant que celle des autres salariés, sinon ça ne peut pas marcher dans un mode ascendant.\nLe marketing :\nLes entreprises classiques font beaucoup de marketing, d'études de marché, et font appel à des techniques pour créer des besoins, en jouant sur les émotions.\nLes entreprises Opales font peu de marketing. Elles sont à l'écoute des besoins, mais surtout à l'écoute de leur raison d'être, pour développer un produit dont ils seront fiers, et qui répondra à un besoin authentique.\nLeur stratégie produit est plus guidée par l'intuition que par l'analyse.\nPar exemple Sounds True vendrait sans doute bien plus de livres s'ils se concentraient sur le marché des livres, mais ils pensent que les autres supports sont une plus grande source de clarté pour leur domaine.\nPlanning, budgets :\nLes entreprises Opales font très peu de planification. Ils n'essayent pas de prévoir et contrôler, mais sont plutôt dans l'intuition, et le fait d'être dans la réponse à ce qui se passe.\nL'auteur évoque la distinction système compliqué / système complexe pour dire que les systèmes complexes ne peuvent pas être planifiés.\nCes entreprises se contentent de solutions réalistes, et n'attendent pas d'avoir plus de données avant d'agir, en se corrigeant souvent.\nElles utilisent des itérations rapides (dans l'idée du lean manufacturing et du manifeste agile).\nPour les objectifs :\nLa notion d'objectif peut poser problème parce qu'elle peut nous pousser dans une direction qui ne sera pas forcément la bonne, et qui peut empêcher de s'adapter au contexte.\nLes objectifs peuvent être bénéfiques s'ils sont fixés par les personnes elles-mêmes, ou par les équipes elles-mêmes, si ça les aide à se motiver. Mais ça ne doit pas être une obligation.\nPour les budgets :\nLa plupart des entreprises étudiées ne font pas de budget du tout.\nCertaines en font, mais seulement quand il faut un certain degré de prévision.\nQuand il y en a, on ne perd pas de temps à savoir pourquoi on n'était pas dans les clous.\nComme l'entreprise est une structure vivante, la question du changement de process ne se pose pas. Le changement survient naturellement et constamment en fonction des besoins.\nTransparence vis-à-vis de l'extérieur :\nLes entreprises Opales choisissent d'assumer pleinement leur projet et leurs valeurs. Ils vont donc en parler en toute transparence avec leurs fournisseurs, et leurs clients.\nIls vont aussi en général être transparents sur les process internes, vis-à-vis des clients et partenaires.\nCertaines mettent en ligne des enregistrements de processus clés, des compte-rendus de réunion, des indicateurs.\nIl est important de se poser la question de son projet personnel, sa vocation, et de le mettre en lien avec le projet, la vocation de l'entreprise.\nL'entreprise n'est pas là que pour faire que les choses se fassent, mais aussi pour aider les salariés à trouver leur voie. Le salarié n'a pas forcément fait ce travail d'introspection.\nQuand les deux sont alignés, on obtient une efficacité exceptionnelle.\nPour les entretiens d'embauche, les entreprises Opales insistent beaucoup sur l'adéquation entre la raison d'être de l'entreprise et le projet individuel de la personne.\nExemple de questions à poser : Comment voyez-vous votre parcours de vie ? Comment le fait de travailler ici s'intègre-t-il à votre perception de ce que vous êtes appelé à être et à faire dans le monde ?\nChez Heiligenfeld, ils posent deux questions aux entretiens annuels : Est-ce que je mets mon cœur dans mon travail ? Est-ce que j'ai le sentiment d'être à ma place ?\nAlors que dans le modèle Vert, les dirigeants se mettaient au service de l'entreprise, et veillaient à ce que tout se passe bien dans ce sens, dans le modèle Opale l'entreprise est comme un être vivant, un champ d'énergie indépendant qui évolue par lui-même. Il n'y a donc pas besoin de chef pour en assurer la cohérence.\nL'auteur se demande quel sens il y a à \"posséder\" un champ d'énergie indépendant, et propose d'inventer de nouveaux cadres juridiques pour que les investisseurs aient la place qui leur revient, en laissant l'autonomie à l'entreprise.","27---traits-culturels-communs#2.7 - Traits culturels communs":"La culture d'entreprise est la façon dont les choses se font spontanément, sans qu'on ait besoin d'y penser.\nElle détermine pour une large part si l'entreprise va échouer ou perdurer.\nLes entreprises dans le paradigme Vert vont typiquement considérer que c'est la chose la plus importante, avec la notion de culture familiale.\nKen Wilber propose un modèle basé sur des quadrants pour obtenir une compréhension intégrale de la réalité.\nD'un côté on a le point de vue intérieur (pensées, sentiments, sensations) et extérieur (tangible, matériel, mesurable).\nDe l'autre on a le point de vue individuel et le point de vue collectif.\nOn obtient le quadrant suivant :\n\tIntérieur\tExtérieur\tIndividuel\tOpinions et mentalités des personnes\tComportements des personnes\tCollectif\tCulture de l'entreprise\tSystèmes de l'entreprise (structures, processus, et pratiques)\t\nCe modèle permet de se rendre compte que tous ces aspects sont interdépendants.\nLes entreprises Ambres ou Oranges mettent l'accent sur le côté extérieur, et obtiennent des salariés peu motivés.\nLes entreprises Vertes mettent l'accent sur l'immatériel et sont parfois face à des incohérences, comme par exemple avoir des structures hiérarchiques où on demande aux cadres de donner le maximum d'autonomie à leurs subordonnés : résultat c'est une difficulté permanente.\nLes entreprises Opales mettent l'accent sur l'autonomisation et la responsabilisation, et à partir de là la culture émerge naturellement sans faire d'efforts particuliers.\nLes entreprises ont chacune leur culture, qui est en partie propre à leur domaine et unique à chacune, et en partie commune avec toutes les autres entreprises du même stade de développement.\nPar exemple, les entreprises Ambres partagent le trait commun de l'obéissance aux ordres.\nLes entreprises Opales ont les traits culturels communs suivants :\nAutogouvernance.\nConfiance : jusqu'à preuve du contraire, s'engager avec les collègues sur la base de la confiance.\nAccès à toutes les informations de manière transparente.\nPrise de décision collective par sollicitation d'avis.\nResponsabilité de ce qui se passe dans l'entreprise pour tous les salariés.\nResponsabilité de faire en sorte que les collègues respectent leurs engagements.\nPlénitude.\nTous les salariés ont une valeur égale en tant que personne.\nPour autant chaque salarié a un apport singulier qui doit être reconnu comme tel.\nL'environnement permet la sécurité psychologique pour être dans l'authenticité.\nLa bienveillance, la reconnaissance, la gratitude, la curiosité, l'amusement sont mis en avant.\nOn réconcilie le rationnel et l'intuitif.\nOn reconnait être étroitement liés les uns aux autres et à la nature.\nLes problèmes et les confrontations respectueuses sont sources d'apprentissage.\nOn règle les conflits en face à face, sans propager de rumeurs.\nRaison d'être évolutive.\nL'entreprise a un projet, une âme, qu'il s'agit d'écouter.\nLe projet individuel doit être questionné pour voir dans quelle mesure il est en résonance avec celui de l'entreprise.\nIl n'y a pas besoin de vouloir maîtriser l'avenir, au lieu de ça on se met à l'écoute et on ajuste.\nOn se concentre sur la mission, le profit viendra de lui-même.\nQuand on a une culture qui se dessine, on peut aller dans son sens pour la renforcer par 3 manières (même si l'autonomie devrait de toute façon laisser la culture se développer librement) :\nMettre en place des structures, pratiques et processus qui vont dans le sens de cette culture (quadrant inférieur droit).\nS'assurer que ceux qui détiennent l'autorité morale sont en phase avec la culture dans leurs actes (quadrant supérieur droit).\nInviter chacun à examiner dans quelle mesure il est en résonance avec cette culture (quadrant supérieur gauche).","3---lémergence-des-organisations-opales#3 - L'émergence des organisations Opales":"","31---les-conditions-nécessaires#3.1 - Les conditions nécessaires":"Selon l'auteur, il y a deux conditions décisives pour pouvoir créer une organisation Opale :\n1- Le dirigeant (fondateur) doit avoir intégré les principes de fonctionnement Opale, et y adhérer personnellement.\n2- Les propriétaires (actionnaires) doivent aussi avoir compris le modèle Opale, et y adhérer.\nIl est possible qu'ils fassent confiance au un dirigeant même sans comprendre, mais en cas de turbulences ils vont avoir tendance à vouloir reprendre la main.\nParmi les paramètres qui n'ont pas l'air d'avoir d'importance :\nLe secteur peut aussi bien être lucratif que non lucratif, dans la santé, l'industrie, le commerce, les services etc.\nLa taille de l'entreprise : ça marche avec les petites comme les grandes.\nLa géographie et la culture ne comptent pas non plus, cf. l'exemple d'AES.\nL'auteur déconseille de tenter de transformer une partie de l'entreprise si on n'est pas à sa tête, et si le dirigeant / les propriétaires ne sont pas sur un paradigme Opale.\nL'auteur n'a vu aucun exemple d'une partie de l'entreprise qui devient Opale et le reste alors que les dirigeants ne sont pas sur ce paradigme.\nA la limite le paradigme Vert peut être un peu plus tolérant, mais pas l'Orange qui peut laisser faire un temps avant de tout annuler en voyant de quoi il s'agit.\nIl déconseille aussi de tenter de convaincre des dirigeants qui ne sont pas sur ce paradigme.\nGravir les échelons des paradigmes est un processus personnel et complexe, et ne peut pas être imposé de l'extérieur.\nTous les exemples de consultants qui, chiffres et arguments Oranges à l'appui, ont essayé de convaincre de l'efficacité des organisations Vertes et Opales, ont échoué.\nLes dirigeants écoutent avec intérêt jusqu'au moment où ils comprennent quelles fonctions sont en cause et quel pouvoir ils vont devoir abandonner.\nCe qui est possible pour un cadre travaillant dans une entreprise Orange c'est de la faire évoluer vers de l'Orange moins toxique.\nPar exemple, au lieu d'enlever complètement les objectifs (ce qui serait inacceptable), on peut dire aux subalternes qu'ils peuvent fixer leurs objectifs eux-mêmes.\nEt pour aller plus loin, le cadre peut ne même pas participer à la réunion de définition des objectifs, en laissant à son équipe le soin de se les définir.\nLe dirigeant :\nIl reste l'interlocuteur pour les entreprises extérieures qui demandent à \"parler au un chef\".\nIl n'a plus les rôles classiques de dirigeant : stratégie, promotions, budgets etc.\nIl doit occuper quand même deux rôles :\nCréer et garantir l'espace où se déploient les modes opératoires Opales.\nAu moindre souci, il va y avoir diverses personnes proposant de revenir à plus de règles et de surveillance. Le dirigeant est là pour empêcher qu'on aille dans cette direction.\nExemple : Chez RHD une employée avait fait un abus de bien social en donnant une des voitures de la société à son fils. Elle a été renvoyée mais certains collègues ont proposé qu'on mette en place des règles et un contrôle pour empêcher que ça se reproduise.\nLes cas les plus compliqués sont quand la pression vient de l'extérieur, par exemple apposer un tampon qualité alors qu'on n'a pas de département qualité (fonction support supprimée). Dans ce cas, il faut être créatif…\nÊtre un modèle exemplaire de comportement Opale (en tant qu'autorité morale).\nIncarner l'auto-gouvernance : le dirigeant doit accepter que son pouvoir soit sévèrement limité par la sollicitation d'avis.\nIncarner l'authenticité : en donnant l'exemple, on incite tous les autres à faire de même.\nPar exemple manifester sa vulnérabilité et son humilité en avouant avoir fait l'erreur de ne pas avoir sollicité d'avis avant de décider, et revenir dessus pour respecter la procédure.\nIncarner l'écoute de la raison d'être : typiquement poser des questions du genre \"Quelle décision sera le plus en ligne avec notre raison d'être ?\", \"Est-ce que la collaboration avec ce client va dans le sens de notre raison d'être ?\".\nIl est un collègue comme les autres.\nEn dehors des deux rôles (garantir l'espace, et être exemplaire), le dirigeant participe aux activités normales comme tous les autres, occupe des rôles où il doit à chaque fois prouver son utilité.\nIl va en général être à l'origine de décisions plutôt importantes, et donc la plupart du temps impliquer beaucoup de personnes dont il faut prendre les avis.\nPour les petites entreprises ça peut être faire le tour de l'entreprise, comme chez FAVI.\nPour les plus grosses, ça peut être poster la proposition sur un blog interne, et voir les résultats qu'on obtient, comme chez Buurtzorg.\nL'auteur évoque le modèle proposé par Norman Wolfe dans The Living Organization, pour caractériser l'énergie mise en œuvre en entreprise.\nElle se divise en 3 catégories :\nL'activité : le contenu de ce qui est fait et pourquoi.\nLes relations : la manière dont c'est dit pour entrer en relation.\nLe contexte : le rapport au sens et à la raison d'être.\nDans les entreprises Oranges l'activité prend tout l'espace des décisions venant des dirigeants, les relations sont au minimum, et le contexte inexistant.\nDans les entreprises Opales, les décisions mobilisent en priorité le contexte et les relations, et ensuite seulement l'activité.\nLes propriétaires (et conseil d'administration) :\nParmi les entreprises étudiées, deux sont revenues à des paradigmes plus classiques suite à un changement de dirigeant par les propriétaires :\nBSO/Origin :\nInitialement l'entreprise a atteint 10 000 personnes, avec des équipes autonomes, peu de fonctions support et quasi personne au siège.\n20 ans après sa création elle avait été associée à une autre entreprise liée à Philips, qui a pris la majorité du capital de BSO/Origin en 2 ans.\nLes deux mondes sont entrés en conflit, et l'entreprise liée à Philips a assez rapidement réimposé des pratiques managériales classiques.\nAES :\nL'entreprise a atteint 40 000 personnes et est entrée en bourse.\nPendant des années les actionnaires soutenaient le modèle, mais ils le faisaient parce que ça rapportait.\nSuite à des problèmes économiques liés à des crises, l'action ayant chuté, ils ont paniqué et petit à petit ont tout fait pour revenir à du management classique, jusqu'à la démission du fondateur.\nCes exemples montrent que pour une entreprise Opale il faut faire très attention aux propriétaires qu'on fait entrer dans le capital :\nSoit ne pas faire entrer de personnes extérieures et croître éventuellement moins vite.\nSoit ne faire entrer que des personnes convaincues par le paradigme Opale.\nSelon le paradigme Vert, le pouvoir des actionnaires doit être sévèrement limité au profit des salariés, clients, voisinage, fournisseurs.\nLe paradigme Opale ne propose pas de limiter leur pouvoir, mais propose à la place qu'ils soient convaincus par la raison d'être de l'entreprise, et le fait qu'elle permettra de rapporter plus.\nHolacracy propose un projet législatif pour faire entrer le fonctionnement Opale dans le type juridique de l'entreprise, et empêcher qu'elle puisse facilement se transformer en entreprise classique.\nAux Etats Unis le modèle juridique B-Corporation permet aux entreprises de prendre en compte les problématiques environnementales et sociales, contrairement aux C-Corporations classiques qui ne doivent légalement prendre en compte que l'intérêt financier des actionnaires.","32---créer-une-entreprise-opale#3.2 - Créer une entreprise Opale":"Pour créer une entreprise Opale, il faut d'abord se poser la question de la raison d'être qu'aurait cette entreprise, comme si on cherchait à déterminer la volonté d'un être vivant.\nPour le choix d'éventuels autres cofondateurs :\nIl faut qu'ils soient sensibles à la raison d'être de l'entreprise.\nIl faut qu'ils soient favorables au paradigme Opale.\nIl faut réfléchir aux divers aspects assez rapidement, parce qu'à mesure que l'entreprise grandit, elle va se stratifier si on n'y fait pas attention.\nIl est utile d'établir des postulats en équipe, pour pouvoir ensuite s'appuyer dessus.\nPar exemple chez RHD :\nTout le monde est d'égale valeur ; sauf preuve du contraire.\nTout le monde est fondamentalement bon.\nIl n'y a jamais une seule bonne façon de résoudre les problèmes qui se posent dans l'entreprise.\nIl peut être utile d'expliciter les postulats des entreprises classiques :\nLes ouvriers sont paresseux et ne sont pas dignes de confiance.\nLes échelons supérieurs ont la réponse à toutes les questions.\nLes salariés ne sont pas capables de supporter les mauvaises nouvelles.\netc.\nPour les pratiques liées à l'auto-gouvernance :\nOn peut soit appliquer un cadre existant, et dans ce cas l'auteur conseille de regarder du côté de l'Holacracy : il y a de la documentation et des consultants si besoin.\nOu alors on peut personnaliser le fonctionnement, et dans ce cas il faut faire attention à 3 points :\nLa sollicitation d'avis : personne n'est là pour \"approuver\" les décisions.\nUn mécanisme de résolution des conflits : en face à face et éventuellement avec un médiateur, voire un jury.\nUn processus d'évaluation entre pairs : les pairs s'évaluent, y compris au niveau du montant du salaire.\nPour les pratiques liées à la plénitude :\nIl faut s'assurer d'instaurer une sécurité psychologique, en rédigeant une charte de comportements admis ou exclus, toujours en équipe.\nOrganiser le lieu de travail pour qu'il permette de bien s'y sentir.\nPréparer le processus d'intégration : accueil, formation initiale etc.\nÉtablir une méthode de réunion : ça peut être des choses simples comme commencer par une minute de silence pour se reconnecter à son âme, ou quelque chose de plus structuré comme les réunions de Holacracy ou Buurtzorg.\nPour les pratiques liées à la raison d'être :\nDeux outils pour aider à travailler la raison d'être :\nLe recrutement : les candidats peuvent examiner leur vocation personnelle et sa résonance avec la raison d'être de l'entreprise.\nLe rituel de la chaise vide : à la fin (ou à tout moment) de chaque réunion, un membre de l'équipe peut occuper la chaise représentant la raison d'être de l'entreprise, et répondre à une question comme \"Est-ce que cette réunion a été vraiment utile à l'entreprise ?\".","33---transformer-une-entreprise#3.3 - Transformer une entreprise":"3 des entreprises étudiées sont intéressantes pour ce chapitre :\nFAVI est passée d'une méthode classique hiérarchique à Opale.\nAES était Opale au début, mais elle a par la suite racheté une dizaine de centrales existantes, et les a converties à son fonctionnement (c'était des années avant qu'elle-même ne revienne vers un mode classique).\nHolacracyOne aide justement des entreprises existantes à passer au mode Opale.\nPour commencer il faut les deux conditions indispensables : un DG à qui le mode Opale tient à cœur, et des propriétaires qui comprennent et soutiennent l'idée.\nOn ne peut pas tout bouleverser d'un coup. Il vaut mieux commencer par un des 3 piliers (auto-gouvernance, plénitude, raison d'être évolutive).\nLes trois piliers sont de toute façon liés, les uns déclenchant les autres.\nEtablir l'auto-gouvernance :\nEn général les salariés qui sont en bas de l'échelle s'habituent vite à l'autonomie et en sont contents. Seules quelques rares personnes quittent l'entreprise.\nSi on a des salariés qui ont intégré le mode de fonctionnement hiérarchique pendant des années, il se peut qu'ils mettent du temps à acquérir le sens de la responsabilité qui va avec la liberté donnée par l'autonomie. On peut les y aider.\nLa raison d'être : elle doit être claire et bien réfléchie pour motiver.\nL'émulation : on peut proposer aux salariés de préparer des objectifs, un plan et un budget, avec dans l'idée de faire voter les équipes entre elles pour élire les meilleurs plans. La partie importante ici est le processus de préparation qui va faire entrer les équipes dans un cercle vertueux.\nLa pression du marché : laisser les équipes au contact des clients (si c'est possible), pour qu'il sachent ce que le client pense de leur travail, s'ils ont un prix compétitif etc.\nLes cadres et les fonctions support vont être plus problématiques, parce qu'ils vont au mieux perdre du pouvoir, et au pire perdre leur travail.\nIl faut leur faire comprendre qu'il n'y aura pas de retour en arrière, et qu'on peut les recaser dans d'autres rôles, au sein des équipes auto-organisées.\nUne bonne partie reste, et quelques-uns partent, c'est du moins ce qui s'est passé chez FAVI et AES.\nOn peut établir l'auto-organisation de 3 manières :\nLe chaos récréatif : il s'agit pour le dirigeant de prendre une décision forte de se séparer d'une fonction support (par exemple planning), d'un niveau hiérarchique (par exemple contremaîtres) ou d'un outil stratégique (par exemple pointeuses dans une usine).\nEt espérer que le chaos qui en résultera laissera rapidement place à l'auto-organisation des salariés.\nÇa marche mieux si les salariés se sont déjà appropriés leur travail et font confiance au dirigeant.\nLa redéfinition par la base : organiser des ateliers avec l'ensemble des salariés, pour définir la nouvelle structure et les nouveaux processus (comme la sollicitation d'avis, la transparence de l'information et l'évaluation par les pairs etc.).\nIl faut que les salariés soient suffisamment impliqués et fassent confiance au dirigeant, et que les fonctions support et les cadres n'aillent pas jusqu'à saboter l'initiative.\nOn peut commencer par faire des présentations du concept d'auto-organisation, acheter ce livre ou d'autres du genre pour l'offrir aux salariés, leur faire visiter des entreprises auto-organisées.\nLe modèle prêt à l'emploi : utiliser par exemple le modèle de Holacracy.\nOn définit une structure constituée de cercles emboîtés, avec des modes de fonctionnement holacratiques, et on décide d'un jour de bascule où on adoptera la nouvelle organisation.\nLe changement peut être progressifet s'adapter petit à petit à la raison d'être de l'entreprise.\nOn a aussi d'autres sources que Holacracy :\nL'institut de l'autogouvernance créé par Morning Star donne des formations.\nBuurtzorg a publié de nombreux documents sur leur fonctionnement.\nCréer les conditions de la plénitude :\nIntroduire des processus qui permettent à chacun d'être pleinement soi-même est plus facile qu'introduire l'auto-gouvernance.\nOn n'aura pas les cadres contre soi, et on peut y aller avec un rythme plus tranquille, contrairement à l'auto-gouvernance où il faut mettre en place des pratiques de remplacement rapidement (gestion des conflits, rôles, salaires etc.).\nOn a deux manière d'introduire les pratiques de la plénitude :\nL'introduction graduelle :\nOn doit d'abord être soi-même dans l'authenticité, et ensuite on peut raconter l'importance que ça a pour nous à travers des histoires personnelles.\nUne autre possibilité est aussi de réfléchir à la raison d'être de l'entreprise, et de son lien avec le fait que chacun soit lui-même, en général on finit par trouver un lien.\nOn peut alors introduire les techniques petit à petit, d'abord en réunion ou à petite échelle, puis en généralisant si ça prend bien.\nL'introduction globale :\nOn va organiser des événements globaux impliquant l'ensemble des salariés par groupes, pour les faire réfléchir à ce que représente le fait d'être soi-même pour eux.\nOn leur demande de raconter des histoires personnelles de moments où ils étaient eux-mêmes.\nPuis on leur demande de trouver des éléments concrets qui pourraient faire qu'ils pourraient être eux-mêmes au travail, et enfin ce qu'ils seraient prêts à mettre en place.\nCréer les conditions de la réalisation de la raison d'être évolutive :\nIl ne s'agit pas de trouver une raison d'être bullshit du genre \"Nous mettons toute notre énergie à produire les meilleurs bidules du pays, dépassant les attentes de nos clients, offrant des perspectives passionnantes à nos collaborateurs et des dividendes copieux à nos actionnaires\".\nIl faut que la raison d'être soit ce qu'on pense que l'entreprise doit faire d'utile dans le monde de par son existence, c'est pour ça qu'on prend la métaphore de l'être vivant qui a une raison d'être qu'il faut écouter.\nTrouver la raison d'être peut prendre du temps. Ça peut passer par des moments de silence, l'utilisation de méthodes particulières comme la théorie U ou la démarche appréciative.\nUne fois qu'on a trouvé la raison d'être, il faut s'appuyer dessus pour les décisions importantes, mais aussi pour les décisions ordinaires.\nOn peut aussi s'appuyer dessus pour entamer d'autres transformations : revoir le processus de recrutement, la stratégie marketing etc.","34---les-résultats#3.4 - Les résultats":"De même que ça a été le cas par le passé avec les précédents paradigmes, nous sommes arrivés à un point où nous sommes limités par le paradigme dominant actuel.\nLes entreprises Opales sont plus efficaces, mais elles ne le sont pas pour les mêmes raisons que les entreprises Oranges par exemple. Elles le sont pour poursuivre une raison d'être qui a du sens.\nL'étude faite dans le cadre de ce livre ne permet pas de valider quoi que ce soit d'un point de vue scientifique pour plusieurs raisons : taille de l'échantillon, difficulté à définir et à chiffrer la performance (dans le cas des entreprises Opales ce serait principalement au regard de leur raison d'être) etc.\nPar contre ce qu'on peut faire c'est examiner les exemples :\nBuurtzorg :\nIls sont passés de 10 salariés en 2006 à 7000 en 2013.\nEn 2012, ils ont dégagé un excédent de 7% de leur chiffre d'affaires.\nAu regard de leur raison d'être, qui est la qualité de la prise en charge :\nRéduction de 40% du temps pendant lequel les patients ont besoin de soin par rapport aux concurrents.\nDemande d'admission aux urgences réduites d'un tiers, et séjour en hôpital plus court.\nErnst & Young estime que les économies pour la sécurité sociale néerlandaise monteraient à 2 milliards d'euros si toutes les entreprises de service à domicile avaient la même performance.\nFAVI :\nEntre les années 1980 où elle est passée au mode Opale et 2015, elle est passée de 80 salariés à 500 (ce qui est d'ailleurs une de leur raison d'être : donner du travail aux personnes de cette région rurale).\nLes salariés sont payés l'équivalent de 17 ou 18 mois de salaire par rapport au marché, et l'entreprise réalise quand même 5 à 7% de marge.\nElle a aussi montré une grande résilience en situation de crise en maintenant les effectifs malgré une chute de 30% de son chiffre d'affaires en 2008.\nIls sont connus pour la qualité de leur pièces et leur absence de retard de livraison.\nLes autres organisations étudiées ont plus ou moins le même genre de caractéristiques : croissance, réussite financière, salaires plus élevés, résilience aux crises.\nPourquoi une telle performance ?\nLa puissance est démultipliée quand tout le monde a du pouvoir.\nLe pouvoir est utilisé à meilleur escient quand chacun est lui-même.\nOn est plus efficace quand tout le monde s'aligne sur la raison d'être de l'entreprise.\nLa fluidité des rôles permet d'être plus flexible.\nOn perd moins de temps à défendre son égo, à jouer des coudes pour une promotion, rejeter la faute sur les autres etc.\nOn perd moins de temps sur des mécanismes de contrôle.\nOn perd moins de temps en réunion puisqu'il n'y a pas besoin de faire remonter et descendre l'info dans une hiérarchie qui n'existe pas.\nLa sollicitation d'avis permet d'intégrer les bonnes personnes au bon niveau de décision.\nLes décisions étant décentralisées, plus de décisions peuvent être prises.","35---entreprises-opales-et-sociétés-opales#3.5 - Entreprises Opales et sociétés Opales":"Ce chapitre tente une analyse prospective plus globale de la société en comparant avec l'émergence des paradigmes passés et poussant plus loin les tendances actuelles.\nLe passage au stade Ambre a fait passer la civilisation au modèle féodal agricole et à la religion organisée, le passage à l'Orange a permis des révolutions scientifiques et industrielles, la république libérale.\nIl est tout à fait probable selon l'auteur que le paradigme Opale permette des changements tout aussi conséquents.\nLes entreprises Opales d'aujourd'hui sont des pionnières, les analyser c'est comme analyser l'automobile en 1900 : elles ont fini par se multiplier, et mener à goudronner les routes et allonger les distances.\nA quoi pourrait ressembler une société évolutive Opale ?\nCroissance zéro : étant donné les problèmes environnementaux et le manque de ressources, il s'agira de zéro déchet, zéro toxicité et 100% recyclage.\nCroissance dans le domaine relationnel et spirituel : les services sont amenés à remplacer les objets de consommation. La publicité et les centres commerciaux vont disparaître.\nTous les secteurs seront bouleversés : l'agriculture intensive pour des pratiques biologiques, l'éducation laissant la place à une approche plus holistique, la médecine qui serait plus spirituelle, le milieu judiciaire et carcéral basé sur la réparation plutôt que la punition.\nL'apparition de monnaies alternatives : typiquement des monnaies fondantes où il ne s'agirait plus d'être en confiance parce qu'on met de côté, mais parce qu'on a un tissu serré de relations au sein de la communauté.\nLa propriété d'usage : la propriété d'usage (que l'auteur appelle “gérance”) pourrait remplacer le modèle de propriété actuel. On aurait le contrôle sur quelque chose qu'on utilise, et on perdrait ce contrôle si on n'en a plus l'usage.\nCommunautés globales : alors que les problèmes énergétiques pourraient nous obliger à relocaliser, les technologies de télécommunications pourraient permettre l'émergence de communautés virtuelles, en poursuivant la tendance actuelle.\nLa fin du travail contraint : avec la mécanisation et la technologie numérique, la plupart des tâches indispensables seront automatisées, ce qui permettra aux gens de ne plus avoir à travailler au sens courant du mot, mais à “exprimer ce qu'il est de manière créative”.\nLa démocratie évolutive : faire participer les citoyens grâce à la technologie, et peut être se mettre collectivement à l'écoute du monde plutôt que projeter sur le monde ce que veulent les citoyens.\nLe ré-enchantement spirituel : plutôt que les anciennes religions (Ambre et avant) ou le matérialisme athée (Orange), le paradigme Opale irait vers une spiritualité non religieuse.\nEffondrement ou transition progressive ? La transition pourrait passer par une prise de conscience et la multiplication des entreprises Opales, mais nous pourrions aussi être rattrapés par les problématiques écologiques, dont certaines risquent de poser de sérieux problèmes dans quelques décennies.\nA quoi ressembleraient les entreprises Opales dans une société Opale ?\nSi on part du principe que la propriété d'usage est généralisée, et que la monnaie perd en valeur dans le temps de manière à ne pas pouvoir être mise de côté, alors investir dans une entreprise pour tirer un revenu plus grand n'a juste plus de sens.\nLes notions de lucratif et non lucratif perdent leur spécificité pour ce qui est de caractériser les entreprises : il n'y aurait que des salariés associés contribuant à l'entreprise, et en cas de difficulté de l'un d'entre eux, l'entreprise pourrait l'aider en fonction de ses excédents.\nLes entreprises Opales permettent une plus grande flexibilité : on peut imaginer réduire son temps de travail pour faire autre chose ou s'engager en partie ailleurs. Tout est négocié entre collègues.\nLa raison d'être étant au centre, les entreprises elles-mêmes sont moins importantes, et donc leurs frontières peuvent devenir poreuses. Des personnes ou des équipes pourraient contribuer dans une structure pendant un temps, puis (ou même en parallèle) dans une autre au service de la même raison d'être.\nEn fait, le concept même d'entreprise peut perdre en pertinence au profit de la raison d'être évolutive, autour de laquelle graviteraient des personnes et des groupes de personnes.\nLe modèle des entreprises Opales ne demande qu'à être généralisé et poussé plus loin. C'est à nous de construire le futur."}},"/books/team-topologies":{"title":"Team Topologies","data":{"1---the-problem-with-org-charts#1 - The Problem with Org Charts":"La plupart des organisations utilisent l'organigramme de l'entreprise pour représenter les intéractions et la division du travail.\nMais dans la réalité une telle représentation statique est inefficace, et les gens contactent ceux qu'ils ont besoin de contacter pour mener à bien leur tâches.\nOn pourrait comparer ça à un document d'architecture, qui devient obsolète dès qu'il est écrit.\nLa manière de voir les organisations évolue :\nLe livre Improving Performance de Geary Rummler et Alan Brache pose une première étape d'amélioration continue du business.\nLe livre Project to Product de Mik Kersten va un cran plus loin en mettant l'accent sur le produit, et la centralité des équipes.\nTeam Topologies se veut être une étape de plus dans cette direction.\nNiels Pflaeging, dans Organize for Complexity :\nIdentifie 3 structures dans l'organisation :\nFormal structure : l'organigramme.\nInformal structure : le domaine d'influence entre individus.\nValue creation structure : la manière concrète dont le travail se fait, sur la notion de réputation au sein de l'équipe et entre équipes.\nLui et d'autres auteurs comme Frédéric Laloux, ou Brian Robertson (Holacracy), pensent que le point de plus important dans les organisations est le lien entre informal structure et value creation structure (c'est-à-dire l'interaction entre les personnes et les équipes).\nCe livre adopte le même point de vue, et oeuvre à renforcer la cohésion au sein de l'équipe en la rendant autonome, et améliorer la confiance inter-équipe en clarifiant les interactions attendues.\nCe livre s'appuie sur la loi de Conway :\nMel Conway avait fait un article de 1968, finissant par dire que le design des systèmes produits par les organisations représentait des copies de leur structure de communication.\nConway parle bien de la communication réelle, c'est-à-dire de la value creation structure (au sens de Pflaeging).\nQuand l'architecture voulue est en contradiction avec la structure de l'organisation, l'un des deux est amené à changer.\nA l'inverse, James Lewis a eu l'idée de mettre en œuvre une “reverse Conway maneuver”, où il s'agit de mettre en place une organisation spécifique qui permet l'émergence de l'architecture voulue.\nLa charge cognitive d'une équipe a tendance à grossir avec le temps.\nSi on veut que l'équipe puisse être efficace et motivée, il faut la limiter explicitement.\nTraiter le développement logiciel comme une sorte d'usine mène à des équipes inefficaces et démotivées.\nLes mouvements Agile, Lean et DevOps offrent une solution au travers des équipes autonomes, travaillant itérativement avec les feedbacks de l'utilisateur.","2---conways-law-and-why-it-matters#2 - Conway's Law and Why it Matters":"La loi de Conway a été depuis confirmée par de nombreuses études, y compris dans des industries différentes (automobile, aviation etc.).\nLa version moderne de cette loi se résume à une citation de Ruth Malan : “If the architecture of the system and the architecture of the organization are at odds, the architecture of the organization wins”.\nUne organisation qui est organisée en silos techniques (équipes QA, DBA, sécurité etc.) ne pourra pas produire de système architecturé pour l'optimisation du flow.\nAutre exemple : une organisation qui s'organise principalement autour de la vente dans des régions géographiques a peu de chances de mettre en place une architecture logicielle globale qui fournit des services à toutes les régions.\nLe Reverse Conway Maneuver est une pratique qui fonctionne.\nLes recherches menées par les auteurs d'Accelerate montrent que c'est efficace.\nLe but c'est de transformer l'organisation pour créer un contexte où le travail peut être fait de bout en bout, sans avoir besoin d'une communication inter-équipe importante.\nPar exemple, si on a 4 équipes de devs back et front, qui transfèrent le travail à une unique équipe DBA, on va obtenir 4 applications back/front, et une seule DB centralisée.\nDans le cas où on voudrait une DB par app, on peut dissoudre l'équipe DBA, et intégrer ses membres aux 4 équipes.\nLa tendance naturelle va être l'émergence de l'architecture qu'on voulait.\nIl faut concevoir l'architecture des systèmes de manière à obtenir des modules de la taille d'une équipe.\nLa structuration de l'organisation nécessite des compétences techniques.\nIl faut au minimum impliquer des personnes techniques (qui comprennent les notions d'abstraction, encapsulation etc.) dans le choix du type d'équipes, de leur scope etc.\nSelon Ruth Malan : “If we have managers deciding which service will be built, by which teams, we implicitly have managers deciding on the system architecture”.\nSelon Allan Kelly, une personne se disant architecte doit avoir des compétences techniques, mais aussi de management.\nIl faut limiter la communication non nécessaire entre équipes.\nIl faut définir les patterns de communication entre équipes, en fonction de l'architecture du système qu'on veut mettre en place.\nSi les équipes communiquent en dehors de ces canaux, alors c'est qu'il y a sans doute quelque chose qui ne va pas : mauvaise API ? Composants mal agencés ou manquants ? etc.\nOn peut monitorer le volume de communication en ligne pour vérifier que les patterns de communication sont les bons.\nSi deux équipes communiquent juste parce que leur code se trouve dans un dépôt commun ou une application commune, alors on peut les séparer en utilisant les fracture plane patterns (chapitre 6).\nLa gestion des outils peut aider à respecter les patterns de communication :\nIl faut des outils partagés entre équipes qui collaborent, et des outils séparés entre équipes indépendantes.\nCertaines entreprises font l'erreur d'avoir de nombreuses équipes chargées de petites parties du système (des complicated subsystem teams). Ça doit rester une exception, et la norme doit être les équipes alignées sur le flow.\nLes réorganisations régulières pour réduire les effectifs ou pour donner des postes à la hiérarchie de management sont incompatibles avec une organisation basée sur la loi de Conway, la notion de cognitive load etc.","3---team-first-thinking#3 - Team-First Thinking":"Citation d'Allan Kelly dans Project Mytopia : “Disbanding high-performing teams is worse than vandalism: it is corporate psychopathy”.\nLa recherche montre que, dans les domaines de résolution de problèmes où il y a une grande quantité d'information à traiter, les équipes cohésives ont une meilleure performance que les individus.\ncf. Driskell and Salas, Collective Behavior and Team Performance.\nUne étude de Google sur ses propres équipes a montré que la dynamique d'équipe compte plus pour la performance que les individus qui la composent.\nPour les auteurs, une équipe est un groupe de 5 à 9 personnes qui travaillent avec un but commun.\nL'équipe est la plus petite unité de l'entreprise. On n'assigne donc pas de tâches aux individus.\nLe nombre de personnes est basé sur le nombre de Dunbar (suite aux recherches de l'anthropologue Robin Dunbar)\nDunbar dit qu'une personne peut faire avoir confiance envers 15 personnes, et avoir une confiance profonde envers 5 personnes.\nCa fonctionne par cercles concentriques pour les échelles suivantes : 50, 150 et 500 personnes.\nSi on dépasse 9 personnes, la confiance ne tient plus et l'équipe n'est plus cohésive.\nDans certaines entreprises avec une forte culture de la confiance, on peut monter jusqu'à 15 personnes, mais elles sont plutôt l'exception.\nLes équipes doivent être stables dans le temps.\nElles mettent plusieurs semaines à plusieurs mois après la formation pour atteindre leur rythme de croisière.\nAvec une phase de “storming” où chacun s'habitue aux comportements des autres cf. modèle de Truckman.\nRéorganiser les équipes selon des projets de 6 mois va les mettre en situation de réadaptation permanente.\nEn fonction de la culture de confiance de l'entreprise, une personne peut changer d'équipe tous les 1 à 2 ans sans impact majeur sur la performance des équipes.\n**L'équipe doit avoir l'ownership **sur les composants sur lesquels elle travaille.\nSi la même équipe intervient sur le même logiciel tout au long, elle peut planifier le court, moyen et long terme.\nElle se sent responsable en cas de raccourcis pris dans le code.\nIl ne doit pas y avoir de parties du logiciel sous la responsabilité de plusieurs équipes.\nLes membres des équipes doivent avoir un team-first mindset.\nIl s'agit de mettre les besoins de l'équipe au-dessus des leurs :\nAider/débloquer les autres, mentorer les nouveaux.\nSe concentrer sur les objectifs d'équipe.\nÊtre ouvert à l'exploration de nouvelles options, plutôt que de chercher à imposer son avis.\nIl existe des personnes “team toxic”, qui n'aiment pas le travail en équipe, et n'arrivent pas à s'y adapter. Elles doivent être écartées des équipes avant d'y causer des dommages.\nCe point est largement validé par la recherche.\nLa recherche montre que les équipes diverses ont plus de chances de produire des solutions créatives, rapides et ont plus de facilité à entrer en empathie avec les autres équipes.\nIl vaut mieux donner des bonus d'équipe, plutôt qu'individuels.\nOn peut faire la même chose pour le budget formation, qu'on confie à l'équipe pour qu'elle le dépense comme bon lui semble.\nIl faut faire attention à surveiller et limiter la cognitive load de l'équipe.\nIl s'agit de la quantité d'effort mental qu'on doit utiliser pour notre mémoire de travail.\nLa notion a été théorisée par le psychologue John Sweller.\nIl identifie 3 types de cognitive load :\nIntrinsic cognitive load : tout ce qui fait partie des fondamentaux à connaître pour résoudre le problème.\nExemple : Fondamentaux de la programmation, spécificités d'un langage en particulier.\nIl faut la minimiser par des formations, du pair programming etc.\nExtraneous cognitive load : ce qui concerne l'utilisation de l'environnement de la tâche.\nExemple : Comment déployer un service (commandes à taper etc.), comment configurer un outil etc.\nIl faut l'éliminer par l'automatisation des tâches.\nGermane cognitive load : la partie complexe propre au problème.\nExemple : La manière dont un service devrait interagir avec cet autre, la logique business d'un service etc.\nOn doit limiter ou éliminer les autres formes pour se concentrer sur celle-là qui est celle qui apporte de la valeur.\nIl faut donc à la fois limiter le scope d'action de l'équipe (germane cognitive load), et les laisser se former et automatiser les tâches (intrinsic et extraneous cognitive load).\nPour diminuer la cognitive load on peut aussi mettre en place un management basé sur les outcomes, diminuer la paperasse et les meetings, et avoir une équipe platform bien découplée.\nPour mesurer la cognitive load d'une équipe, la manière la plus rapide et efficace est de demander aux membres de l'équipe s'ils se sentent surchargés.\nLa mesure par le nombre de lignes de code ou ce genre de choses ne marche pas très bien.\nUne fois qu'on a identifié les subdomains, on peut appliquer des heuristiques pour l'attribution aux équipes.\nOn peut les classer par complexité relative :\nSimple : on sait comment faire l'essentiel du travail.\nCompliqué : il faudra faire de l'analyse et itérer.\nComplexe : il faudra faire beaucoup d'expérimentation.\nLes heuristiques sont :\n1 - Assigner chaque subdomain à une équipe.\nSi un subdomain est trop gros, on le découpe d'abord.\n2 - Assigner 2 ou 3 subdomains simples à une équipe.\n3 - Assigner un seul subdomain complexe à une équipe.\nUne des raisons c'est qu'elle ne pourra pas avoir tendance à traiter d'abord les tâches simples.\n4 - Ne pas mettre deux subdomains complexes dans une même équipe.\nIl faut que chaque équipe définisse et documente sa relation aux autres équipes (sa “Team API”).\nÇa inclut le code et la documentation de l'API disponible, et comment éventuellement y contribuer, la manière dont l'équipe travaille, communique, ce sur quoi ils travaillent actuellement etc.\nLe but c'est qu'une autre équipe puisse facilement trouver ce qu'il lui faut par elle-même, et trouver comment collaborer avec eux en cas de besoin.\nPour permettre une certaine communication entre équipes autonomes sans compromettre l'autonomie, on peut laisser du temps pour des activités de “guildes”, où des membres de diverses équipes s'organisent autour de sujets qui les intéressent, pour du partage de connaissances et de l'apprentissage.\nL'environnement de travail doit être explicitement pensé, qu'il soit physique ou virtuel.\nIl faut que le lieu de travail permette :\nDes moments de travail sans être dérangé.\nUne collaboration fréquente entre membres de l'équipe.\nUne collaboration occasionnelle avec les membres des autres équipes.\nSpotify a organisé, dès 2012, ses équipes en squads ayant un espace commun, et tribes (squads liées entre-elles) situées au même endroit.\nCDL (une entreprise d'assurance UK) et la banque ING Netherlands ont fait le même genre de chose.\nChez Auto Trader, ils ont mis ensemble les équipes techniques et non techniques (sales, product, marketing etc.) qui travaillaient pour la même business area et les mêmes clients.\nLe but est de favoriser la collaboration et augmenter l'empathie entre membres du même flow stream-aligned.\nIls ont aussi permis aux personnes de facilement déplacer leurs affaires pour s'installer là où c'est le plus utile sur le moment, et ont mis en place des espaces communs à ces équipes.\nDans le cas du remote first, il faut aussi organiser l'environnement :\nJason Fried et DHH adressent ce problème dans leur livre Remote: Office Not Required.\nL'environnement comprend les outils (chat, wiki, reporting etc.), mais aussi les règles qu'on se fixe (horaires, temps de réponse, manière de communiquer, conf calls etc.).\nCôté outil de chat, n peut utiliser des canaux préfixés : #team-vesuvius, #support-logging, #practices-testing\nAttention à ne pas oublier les pratiques techniques fondamentales (continuous delivery, TDD etc.), sans elles le team-first n'ira pas bien loin.","4---static-team-topologies#4 - Static Team Topologies":"Les auteurs ont identifié deux team anti-patterns :\n1 - Les équipes ad hoc qu'on crée sans trop réfléchir, par exemple :\nDes équipes qui avaient trop grandi et qui sont découpées au hasard.\nDes équipes qui s'occupent de tout le middleware.\nDes équipes DBA créés après un crash de la prod à cause des problèmes de DB.\n2 - Le fait de remélanger les équipes régulièrement, au gré des projets qui commencent et se terminent.\nLe critère que les organisations doivent chercher à optimiser c'est le flow de changement rapide.\nC'est important pour pouvoir s'adapter rapidement aux clients et au marché.\nSpotify, avec son papier de 2012 (qui a donné lieu au célèbre “Spotify model”) est un exemple connu de tournant vers ce modèle favorisant le flow rapide.\nIls avaient des équipes appelées squads, pluridisciplinaires, stables et composées de 5 à 9 personnes.\nLes équipes étaient regroupées en tribes contenant plusieurs squads qui collaboraient entre-elles plus qu'avec les autres squads.\nPar exemple, les testeurs de chaque squad d'une même tribe formaient un chapter, et se rencontraient régulièrement pour se synchroniser.\nUn dernier concept utilisé était celui de guilds, où il s'agissait de regrouper des communautés de pratique informelles autour de thématiques techniques.\nPour avoir un flow rapide et répondre aux besoins des clients, les équipes qui conçoivent et développent le logiciel doivent avoir un feedback régulier depuis la production.\nLes auteurs de ce livre ont par le passé créé un ensemble de patterns pour structurer les équipes et leurs interactions, appelés DevOps Topologies.\nIl y a deux idées importantes avec ces patterns :\n1 - Le fait que telle ou telle technique soit ou non appropriée dépend du contexte de l'organisation :\nMaturité technique et produit.\nLes entreprises qui ont une faible maturité (tests automatisés, continuous delivery, discovery) vont mettre du temps à l'acquérir.\nPendant ce temps, c'est OK de garder des équipes spécialisées (développement, opérations, sécurité etc.).\nTaille de l'organisation, et taille du logiciel.\nLes grandes entreprises ont besoin de plateformes self-service, et éventuellement peuvent adopter le SRE, alors que les plus petites non.\n2 - Il y a une liste de topologies connues comme étant des anti-patterns allant à l'encontre de DevOps.\nVoici quelques uns de ces patterns :\nLes feature teams ont besoin d'une grande maturité technique.\nLe fait d'avoir une feature team (une équipe pluridisciplinaire qui travaille sur une fonctionnalité de bout en bout) peut être problématique dans certains cas :\nDans le cas d'un shared ownership sur le code, si l'équipe passe de composants en composants pour son développement, mais n'a pas une maturité technique suffisante, elle perdra la confiance des autres équipes.\nPar exemple si elle ne s'applique pas à faire de tests automatisés et n'applique pas la règle du boy scout.\nLes product teams ont besoin d'un système de support.\nLes product teams (des feature teams qui ont l'ownership sur leur produit) ont besoin que certains aspects techniques (infrastructure, environnement de test, pipelines de CI/CD etc.) soient faits pour eux.\nL'important est que ça ne soit pas bloquant dans leur flow de développement.\nEt pour ça il faut que les services en question soient fournis en self-service, sans avoir à aller interagir avec d'autres équipes.\nLes cloud teams ne doivent pas créer l'infrastructure applicative.\nPour être DevOps, il faut une bonne séparation de responsabilités entre cloud teams et product teams :\nLes cloud teams peuvent être responsables de l'infrastructure de la plateforme cloud.\nMais c'est les product teams qui doivent être responsables de provisionner et mettre à jour les ressources dont elles ont besoin.\nLe SRE a du sens à grande échelle.\nLes équipes SRE (Site Reliability Engineering) sont utilisées chez Google uniquement pour les projets de grande envergure.\nIl s'agit d'une relation dynamique :\n1 - Au départ l'équipe produit gère son infra.\n2 - Si son produit grossit l'équipe SRE vient l'aider pour que ça marche à grande échelle.\n3 - Ensuite s'établit une relation où l'équipe produit établir des SLO (Service Level Objectives), et doit respecter un error budget, en collaboration avec l'équipe SRE.\nSi elle dépasse l'error budget, l'équipe SRE peut temporairement refuser l'ajout de nouveaux changements.\n4 - Si l'usage de l'application diminue, l'équipe produit reprend la main sur son infra, seule.\nLa mise en place d'une équipe SRE est difficile, dans la mesure où elle implique de garder un équilibre instable entre l'implication de l'équipe produit dans les opérations, et le fait pour l'équipe SRE de fournir leur expertise.\nEn ce sens, l'équipe SRE peut être vue comme une forme particulière d'équipe stream-aligned.\nSéparer les responsabilités pour casser les silos.\nPar exemple on peut casser une équipe “database”, et séparer les DBA (database administration) des DB Dev (database development) :\nLes DBA vont rejoindre une équipe Platform (ou être remplacés par l'offre Database-as-a-Service d'un cloud provider).\nLes DB Dev vont rejoindre une équipe stream-aligned.\nPour bien comprendre la relation entre équipes, il est important de maintenir une liste des dépendances entre équipes, qui mènent à des interactions.\nOn peut les classer en 3 catégories : les dépendances de connaissances, de tâches, et de ressources.\nChez Spotify, les nouvelles dépendances qui sont entre squads de différentes tribes amènent à vérifier qu'il n'y a pas un problème.\nLes DevOps Topologies doivent être utilisées en les adaptant régulièrement à l'évolution des équipes.\nPar exemple dans le cas d'un silotage Dev et Ops, on peut introduire une équipe DevOps qui sera là pour évangéliser les pratiques, puis petit à petit disparaîtra à mesure que le Dev et l'Ops ne forment qu'un.\nAttention à ce que ça ne devienne pas un anti-pattern où l'équipe DevOps s'installe dans la durée, et forme un silo de plus.","5---the-four-fundamental-team-topologies#5 - The four Fundamental Team Topologies":"Il existe 4 types fondamentales d'équipes.\nToutes les équipes d'une entreprise devaient être ramenées à celles-là.\nCela permet d'éviter les anti-patterns, et d'augmenter la clarté autour du rôle de chaque équipe.\nLes équipes Ops et Support ne sont pas dans la liste.\nCes équipes doivent être alignées sur le stream, et laisser les équipes avec lesquelles elles s'alignent rester DevOps.\n1 - Stream-Aligned Team.\nUn stream est un flow continu de travail, centré autour d'un domaine ou d'une “organizational capability”.\nÇa peut être un produit, un service, un parcours utilisateur, un client spécifique etc.\nUne équipe stream-aligned délivre de manière continue et de bout en bout (sans passage de main à une autre équipe) des incréments autour d'un stream.\nElle est donc au contact régulier du client qui utilise le produit sur lequel elle travaille.\nElle a une approche expérimentale et s'ajuste en permanence par le feedback qu'elle reçoit de la production.\nL'équipe doit avoir en interne toutes les compétences nécessaires, par exemple des généralistes qui cumulent certains rôles, et des spécialistes sur d'autres.\nUn exemple peut être les équipes principales (service teams) qu'Amazon utilise depuis presque 20 ans : pluridisciplinaires, autonomes, centrées autour d'un service.\n2 - Enabling Team.\nElle est composée de personnes spécialistes de leur domaine, et qui prennent le temps de se tenir à jour des nouvelles technos et techniques, de tester des choses etc.\nCes personnes vont ensuite agir comme des consultants, pour guider les équipes stream-aligned, et leur donner les connaissances nécessaires pour leur contexte.\nLeur but est de rendre les équipes stream-aligned autonomes, et de n'avoir à agir sur une équipe que pendant quelques semaines ou mois.\nExemple chez BCG Digital Ventures :\nIls ont constitué l'équipe suite à des problèmes d'efficacité (lead time trop long etc.), et de connaissance silotée.\nIls se sont concentrés sur le fait d'apprendre les bonnes pratiques aux autres équipes, à faire des pair, des mobs, y compris en filmant et diffusant les séances.\nLeurs métriques portaient sur le nombre de déploiements, le cycle time, le temps pour fixer les incidents de prod.\nNDLR : métriques DORA.\n3 - Complicated-Subsystem Team.\nIl s'agit d'une équipe qui travaille sur une partie spécifique d'un système, avec un besoin de connaissance spécialisée, au point que la plupart des membres de l'équipe doivent être spécialistes de ce sujet.\nLa différence avec les component teams (équipes s'occupant de subsystems communs à plusieurs systèmes) qu'on trouve dans les organisations traditionnelles c'est le critère de forte spécialisation.\nEn quelque sorte, on accepte une équipe chargée d'un composant qui ne porte pas sur un slice vertical entier, en espérant qu'il s'agit d'une exception du fait du critère de la spécialisation.\nLes complicated subsystem teams sont optionnels au sein d‘une organisation.\nElle permet par ailleurs de soulager la cognitive load des équipes stream-aligned.\nLa relation avec les équipes stream-aligned est collaborative au début, puis plus indépendante quand l'API du subsystem se stabilise.\nUn exemple de spécialités d'une telle équipe peut être : les connaissances sur les codecs vidéo, sur les modèles mathématiques, sur des algorithmes de reconnaissance faciale etc.\n4 - Platform Team.\nElle développe et met à disposition des outils self-service, pour soulager les équipes stream-aligned d'une partie de la complexité du système.\nElle traite ses outils comme des produits à destination des clients que sont les autres équipes.\nElle doit donc utiliser les techniques de product management habituelles pour un produit, y compris la discovery, en itérant à partir du feedback des équipes consommatrices.\nElle doit porter une grande attention à l'UX (plus spécifiquement DevEx dans ce cas).\nElle doit traiter le produit comme un système en production : définir des horaires de disponibilité des services, une gestion des incidents etc.\nA platform is not just a collection of features that Dev teams happened to ask for at specific points in the past, but a holistic, well-crafted, consistent thing that takes into account the direction of technology change in the industry as a whole and the changing needs of the organization.\nElle peut être composée d'équipes internes à la plateforme, et pouvant être des 4 types d'équipes (on obtient une sorte de topologie fractale).\nDes exemples de ce qui peut être dans la plateforme :\nProvisionner des serveurs.\nFournir des outils pour la gestion des accès ou la sécurité.\nFournir un outil de logging.\nFournir un service de monitoring.\nFournir des outils de CI/CD.\nLes auteurs parlent de Thinnest Viable Platform (TVP) pour insister sur le fait que la plateforme ne doit inclure que ce qui est vraiment nécessaire et utile aux autres équipes.\nSi les composants sous-traités fonctionnent bien, la plateforme peut se résumer à une page wiki qui les décrit.\nLa plateforme est construite sur d'autres plateformes par couches successives.\nUn cloud provider peut être une couche, on peut aussi avoir la JVM, Kubernetes, et même un OS peut être vu comme l'une des couches de plateforme.\nIl peut être utile de faire une représentation graphique des couches de plateforme du système.\nLe flow de changements doit être fait par une même équipe stream-aligned, sans passage de relais.\nPour ça il faut éviter les silos techniques (équipes QA, DBA, UX, architecture, data processing ETL etc.).\nAvoir une plateforme n'implique pas de passage de relais.\nL'équipe stream-aligned n'a pas besoin de demander une action de la part de l'équipe plateforme pendant les incréments qu'elle fait, puisque les outils sont self-service.\nUn des avantages à avoir des équipes pluridisciplinaires c'est que ça pousse à favoriser des solutions simples qui parleront à l'ensemble de l'équipe, plutôt que les solutions qui nécessitent une expertise technique poussée dans un domaine.\nComment transformer ses équipes pour adopter le modèle de Team Topologies :\nLa plupart des équipes doivent devenir stream-aligned.\nLe ratio entre stream-aligned et les trois autres réunies dans les organisations qui fonctionnent bien est entre 6:1 et 9:1.\nLes infrastructure teams doivent devenir des plateform teams.\nC'est pas forcément évident de demander d'utiliser des techniques de product management à des équipes composées d'admins système.\nLes component teams doivent être dissoutes pour être intégrées aux stream-aligned teams, ou devenir une des 3 autres catégories de teams.\nPar exemple :\nLes équipes de DBA peuvent être converties en enabling team pour aider à monter en compétence sur la performance DB.\nLes équipes middleware peuvent devenir des platform teams.\nLes tooling teams doivent devenir des enabling teams (en général temporaires), ou faire partie de la plateforme.\nLes support teams doivent s'aligner sur le stream de changement.\nTraditionnellement les organisations ont une équipe support pour l'ensemble des services.\nLe modèle qui marche le mieux est :\n1 - De créer une équipe support pour une équipe ou une famille d'équipes stream-aligned, seulement si le besoin se fait sentir.\nCa permet de garder chaque service ou groupe de services séparés, y compris du point de l'environnement de production (cf. loi de Conway).\nLes support teams peuvent plus facilement amener le feedback vers l'équipe de dev.\n2 - D'avoir une procédure cross-équipes pour la résolution d'incidents importants.\nL'équipe support peut être renommée “service experience team”, pour marquer le fait qu'elle n'est pas que technique.\nLes architecture teams doivent :\nsoit être dissoutes pour être remplacées par les stream-aligned teams prenant leurs propres décisions, et les chapters / guildes permettant de faire le lien entre équipes.\nsoit rejoindre des enabling teams à mi-temps (pour ne pas trop empiéter sur les stream-aligned teams).\nIl s'agit avant tout de penser la topologie d'équipes (type d'équipes, et leurs interactions) pour influer naturellement sur l'architecture du système.","6---choose-team-first-boundaries#6 - Choose Team-First Boundaries":"Il s'agit dans ce chapitre d'explorer ce que des techniques comme le Domain Driven Design et les Fracture Planes peuvent faire pour aider à avoir un logiciel divisé en composants de la taille des équipes, et dont le développement n'implique pas de passage de relais entre équipes.\nLa nature monolithique d'une application et plus généralement d'une organisation, peut parfois être difficile à détecter, et peut être de différentes natures.\nJoined-at-the-Database Monolith : l'application partage le même schéma de base de données, malgré l'éventuelle présence de plusieurs services tournant dans des process séparés.\nL'organisation considère en général la DB comme l'élément central.\nOn voit des équipes de DBA qui sont chargées de maintenir les schémas de DB à la place des équipes applicatives.\nMonolithic Builds (Rebuild Everything) : on teste et déploie les services en même temps.\nParfois c'est parce qu'on a une équipe QA qui teste le tout à la fin.\nMonolithic Model (Single View of the World) : l'organisation essaye de garder un seul modèle (représentation) à travers l'ensemble de ses contextes.\nSi c'est une petite organisation ça peut passer, mais dès qu'elle grossit, les équipes croulent sous le poids de la cognitive load.\nMonolithic Thinking (Standardization) : il s'agit d'une standardisation excessive des approches et technologies entre équipes.\nCette standardisation réduit la capacité des équipes à utiliser les bons outils, expérimenter, et réduit leur motivation de manière générale.\nC'est confirmé par la recherche faite par les auteurs d'Accelerate.\nMonolithic Workspace (Open-Plan Office) : le fait d'adopter un unique layout de bureau (que ce soit le bureau isolé ou l'open space) est moins efficace que de l'adapter à la nature des équipes, et pour supporter leurs interactions.\nUne mauvaise séparation de l'application peut mener à des inconsistances de diverses sortes. Pour éviter ça il faut couper le long des fracture planes.\nLes fracture planes sont des frontières naturelles au travers desquelles il faut découper l'application, si possible en les combinant.\n1 - Bounded Contexts : L'essentiel des fracture planes devraient correspondre à des bounded contexts, en suivant la méthode du Domain Driven Design.\nLes bounded contexts correspondent à des modèles, à la fois au niveau du langage, et au niveau de la manière dont les classes et fonctions sont agencées pour représenter une partie cohérence du domaine.\nLes bounded contexts sont difficiles à bien délimiter correctement, mais ça vaut le coup d'essayer.\nLe DDD a bien d'autres avantages, notamment le fait d'aligner les experts métier avec les développeurs.\n2 - Regulatory Compliance : certaines activités impliquent des process lourds pour respecter des normes. Par exemple le PCI DSS pour le stockage de cartes bancaires.\nOn peut donc séparer les parties qui nécessitent les procédures lourdes, du reste du système, pour éviter que le reste du système en subisse les conséquences.\n3 - Change Cadence : quand on a certaines parties du système qui nécessitent un rythme de changement plus lent, on peut les séparer pour éviter qu'ils ralentissent aussi l'évolution du reste du système.\n4 - Team Location : quand les personnes sont en présentiel sur des sites différents, ou même sur différents étages d'un même bâtiment, elles peuvent se parler beaucoup plus difficilement.\nÇa vaut la peine d'envisager une séparation d'équipe selon la localité.\nDans le cas de travail en remote, le fuseau horaire est un facteur déterminant sur la possibilité de se parler.\nLes auteurs conseillent pour une même équipe, de se trouver soit dans une situation de full présentiel, soit dans une situation remote first (avec tous les outils construits autour de ça).\n5 - Risk : certaines parties de l'application peuvent être traitées avec plus de prise de risque (passer moins de temps à s'assurer que tout marche comme prévu et risquer des incidents en production).\nOn peut séparer les produits selon la possibilité de prendre ces risques pour différencier les procédures.\nPar exemple :\nLes parties qui sont plus orientées marketing pour appâter de nouveaux clients, vs les parties qui servent à satisfaire les clients existants.\nLes parties visibles par des millions d'utilisateurs, vs les parties visibles seulement par un nombre réduit d'utilisateurs payants.\nLes parties à destination des clients vs les parties à destination des salariés en interne.\n6 - Performance Isolation : les parties qui nécessitent une performance ou une scalabilité particulière peuvent être isolées du reste pour recevoir un traitement particulier.\nExemple : la partie du système qui assure le paiement des taxes et qui doit tenir la charge pendant le dernier jour où on peut payer.\n7 - Technology : la séparation par technologie (frontend, backend, data etc.) est plutôt déconseillée parce qu'elle a tendance à créer des équipes qui ne sont pas autonomes.\nPour autant, dans certains cas elle peut être acceptable, par exemple pour de vieilles technologies impliquant plus de test manuel, peu de documentation, et un écosystème complètement différent.\n8 - User Personas : dans certaines situations on se retrouve avec des ensemble de fonctionnalités utilisées par des utilisateurs particuliers. On peut faire une séparation à cet endroit.\nPar exemple dans le cas où on a un pricing par tier, ou encore dans certains cas où il y a des utilisateurs admins et non admins.\nOn peut essayer de trouver d'autres critères qui marcheraient pour notre organisation. L'idée c'est que l'architecture qui en ressort permettent une plus grande autonomie des équipes, et une cognitive load réduite.\nExemple de Poppulo, une entreprise qui aide d'autres entreprises à mesurer leur impact en termes de communication.\nLa plupart des équipes sont alignées sur les business domains.\nUn des domaines est aligné selon les process de normes ISO à respecter.\nLa partie reporting de l'usage des features est répartie au sein de plusieurs équipes qui collaborent sur le sujet.\nUne équipe UX sert d'enabling team sur ce sujet.\nUne équipe SRE s'occupe des produits à forte charge.\nExemple d'entreprise avec un use case trop complexe pour ne pas faire de séparation par technologie.\nL'entreprise produit des objets physiques connectés, contrôlables depuis une application mobile et depuis le cloud. L'ensemble des données est aussi envoyé dans le cloud.\nComme on a des technologies différentes, des rythmes de changement différents, et de manière générale une cognitive load élevée sur l'ensemble de la stack, on peut exceptionnellement opter pour une séparation par technologie.\nLes trois gros blocs pourront donc avoir une relation stream-aligned vs platform, où le cloud ou les devices IoT peuvent jouer le rôle de platform.","7---team-interaction-modes#7 - Team Interaction Modes":"La topologie d'équipes, et notamment le type d'interaction, doit évoluer en fonction du contexte de l'entreprise.\nLes interactions entre équipes doivent être claires à chaque instant, et être limitées à ce qui est nécessaire.\nIl y a 3 modes d'interaction entre équipes :\nCollaboration : les deux équipes communiquent régulièrement autour d'un sujet particulier sur lequel elles sont en collaboration.\nCe mode est utile quand deux équipes sont sur de la découverte, de l'exploration de nouvelles techniques ou technologies.\nIl a lieu entre deux équipes avec des compétences complémentaires, nécessaires pour le problème en question.\nPar exemple : faire du cloud-based sensor management avec une équipe qui a les connaissances cloud, et l'autre les connaissances sur les sensors.\nIl peut y avoir deux manières d'entrer en collaboration :\n1 - Chaque équipe apporte son expertise, et collabore avec l'autre sur un sujet délimité.\n2 - Les deux équipes n'en forment temporairement qu'une, le temps de résoudre le problème. Elles ne doivent pas dépasser le nombre de Dunbar de 15 personnes au total.\nLe mode collaboration entraîne un brouillage des limites de responsabilité. Il faut donc que chaque équipe fasse un effort particulier sur cet aspect, pour que la relation ne parte pas sur de la défiance (accuser l'autre de ce qui ne marche pas etc.).\nLa collaboration coûte cher, chaque équipe sera moins efficace que seule, notamment à cause de la cognitive load plus élevée. Il faut donc que le résultat de l'exploration de la technique ou techno soit tangible.\nLe besoin de collaboration permanent ou trop fréquent peut indiquer un mauvais découpage des domaines, ou un mauvais mix de compétences dans les équipes.\nUne même équipe ne peut être en collaboration qu'avec au plus une seule autre équipe en même temps.\nUsage typique :\nStream-aligned team avec complicated subsystem team.\nStream-aligned team avec platform team.\nComplicated subsystem team avec platform team.\nX-as-a-Service : une équipe consomme des services mis en place par l'autre, sans avoir besoin de communiquer avec elle.\nLe service en question peut être par exemple une librairie, un composant, un outil de test, une API ou une plateforme.\nIl est utilisé “as a service” par l'équipe consommatrice, c'est-à-dire qu'il marche tel quel.\nCe mode est possible quand de l'exploration dans un mode plus innovant a déjà eu lieu, et qu'un service avec un contour clair a pu être défini.\nIl implique donc une faible interaction.\nIl permet de diminuer la cognitive load grâce à un ownership très clair de chaque partie.\nIl nécessite une forme de product management :\nL'équipe qui fournit le service s'intéresse à la manière dont le service est perçu et utilisé côté consommateurs.\nLe service doit avoir une bonne developer experience (DevEx).\nToutes les demandes ne sont pas implémentées tel quel, mais sont prises en compte dans la cohérence globale du produit.\nUne même équipe peut avoir une interaction X-as-a-service avec un grand nombre d'équipes en même temps.\nUsage typique :\nLes stream-aligned teams et les complicated subsystem teams consommant la plateforme d'une platform team (platform-as-a-service).\nLes stream-aligned teams et les complicated subsystem teams consommant une librairie ou un composant d'une autre complicated subsystem team.\nFacilitating : une équipe aide l'autre à surmonter des problèmes.\nC'est le mode d'interaction principal des enabling teams, mais il peut aussi être utilisé par d'autres types d'équipes.\nIl s'agit par exemple d'aider à être plus efficace sur des problèmes courants, mieux comprendre une technologie, apprendre plus vite.\nIl peut aussi s'agir de faciliter l'interaction entre plusieurs équipes qui ne se comprennent pas :\nEn aidant par exemple à clarifier des APIs difficiles à utiliser.\nEn aidant à mieux définir les interactions entre les équipes pour aller vers l'architecture souhaitée, c'est-à-dire réaliser une reverse Conway maneuver.\nQuand on le fait, les équipes concernées doivent entrer en mode collaboration pour trouver les problèmes de la nouvelle organisation rapidement.\nUne ou plusieurs équipes facilitatrices peuvent assister la transition.\nIl ne s'agit par contre pas de construire le logiciel à la place des équipes qui sont l'objet de l'aide.\nUne équipe peut avoir une interaction de facilitation avec un petit nombre d'autres équipes en même temps.\nUsage typique :\nUne enabling team aidant une des 3 autres types de teams.\nN'importe quelle équipe aidant une stream-aligned team.\nLe choix clé qu'on aura souvent à faire se trouve notamment entre la collaboration et le x-as-a-service pour deux équipes données.\nLes interactions principales pour chaque type d'équipe sont :\nStream-aligned team : collaboration ou x-as-a-service\nComplicated subsystem team : x-as-a-service\nEnabling team : facilitating\nPlatform team : x-as-a-service\nStratégies basées sur les interactions :\nOn peut utiliser le mode collaboration pour construire et affiner des interactions x-as-a-service.\nQuand un nouveau service est fourni en x-as-a-service, on passe par une phase de collaboration pour le construire d'une manière adaptée aux besoins des consommateurs.\nIl faut prévoir des petites phases de collaboration régulières entre équipes qui fournit et qui consomment, pour affiner le service : élargir, réduire son scope, ou le rendre plus ou moins flexible.\nOn peut changer l'interaction entre deux équipes de manière temporaire, pour aider une autre équipe, et augmenter l'empathie.\nDeux équipes peuvent par exemple, de manière délibérée, entrer en mode collaboration, et échanger des membres pour faire du pair pendant quelques jours ou semaines.\nIl faut bien sûr que ces changements soient pleinement consentis par les équipes.\nPour plus de détails sur ce genre de pratique, les auteurs recommandent Dynamic Reteaming de Heidi Helfand.\nOn peut utiliser les écarts entre les interactions prévues et les interactions réelles pour déceler les mauvaises limites architecturales du système.\nPar exemple : imaginons qu'on ait une équipe stream-aligned utilisant “as a service” un composant de calcul fait par une équipe complicated subsystem. Si l'utilisation implique de nombreuses interactions, on peut se poser des questions.\nPeut être que la limite du composant a été mal définie.\nPeut être que la complicated subsystem team a un manque de compétences UX/DevEx en interne.\nAutre exemple : imaginons qu'on ait une platform team s'attendant à une relation de collaboration avec une stream-aligned team, en vue du développement d'un nouveau service. Si la platform team ne reçoit pas beaucoup de retour, on peut se poser des questions.\nPeut être que la stream-aligned team n'a pas bien compris l'intérêt d'aider la platform team.\nPeut être que la stream-aligned team n'a pas les compétences nécessaires en interne, et qu'une autre team serait plus adaptée.","8---evolve-team-structures-with-organizational-sensing#8 - Evolve Team Structures with Organizational Sensing":"Vu que nous évoluons dans des contextes changeants, ce qu'il faut avant tout c'est non pas seulement designer la topologie d'équipes, mais surtout designer les règles selon lesquelles cette topologie va évoluer.\nLes types d'intéraction collaboration et x-as-a-service sont en fait adaptés à des situations différentes :\nLa collaboration permet d'explorer et d'innover rapidement en mettant en commun les compétences des deux équipes, du fait de la communication facilitée. Mais elle implique que la delivery devienne lente pour les deux du fait de la cognitive load plus importante.\nLe x-as-a-service permet de délivrer rapidement du fait d'une cognitive load plus faible, et d'une (quasi) absence d'interaction entre les équipes. Mais il ne permet pas d'innover rapidement sur le périmètre des deux équipes, du fait de l'interface pré-définie.\nLa conséquence c'est que la collaboration coûte cher, et doit donc être justifiée par la valeur qu'elle apporte en termes d'exploration ou de construction de quelque chose de commun.\nLe passage en mode collaboration peut en plus permettre d'apprendre des techniques que l'autre équipe maîtrise.\nExemple : une équipe low level qui fait de l'embarqué, et une équipe qui récupère ces données IoT pour les afficher en utilisant le cloud, entrent en collaboration.\nL'équipe embarqué peut apprendre de l'autre comment faire des tests dans des environnements éphémères, alors que l'équipe cloud en apprendra plus sur les problématiques de l'embarqué qui pourraient affecter les données.\nQue faire ensuite :\nUne fois la collaboration ayant porté ses fruits, on peut toujours la laisser durer pour que les équipes fassent plus d'exploration et d'apprentissage.\nEn l'état les deux équipes doivent se coordonner pour la production de features. Vu qu'elles travaillent sur des stacks très différentes et ont une cadence de cycle différente, on ne peut pas vraiment faire autrement.\nOn peut choisir l'une pour servir de platform team vis-à-vis de l'autre. Elle devra appliquer les méthodes de product management vis-à-vis de l'équipe consommatrice.\nA terme, si la cadence des cycles s'aligne, ils pourront passer sur deux équipes stream-aligned pairées.\nLes interactions entre équipes peuvent changer régulièrement, à condition que ce soit fait de manière délibérée.\nDans une grande entreprise, on s'attend à tout instant à ce qu'il y ait de nombreuses relations de collaboration permettant de construire les APIs “as a service”.\nPour chaque produit “as a service” qui fait l'objet d'une collaboration, on peut avoir une équipe stream-aligned qui collabore, et plusieurs autres qui ne collaborent pas, mais qui bénéficieront aussi du service quand il sera plus stable.\nLa collaboration coûtant cher, on essaye quand même de la limiter à ce qui est vraiment nécessaire.\nCe changement de topologie régulier et délibéré constitue une forme de design stratégique au sein de l'entreprise.\nCôté fréquence, le changement d'interaction peut être pertinent pour chaque équipe dans un ordre de temps qui est de plusieurs mois.\nIl existe des des triggers courants qu'on peut repérer, et qui indiquent qu'il faut probablement changer la topologie d'équipe :\nLe logiciel est devenu trop gros pour une équipe.\nSymptômes :\nUne startup dépasse 15 personnes.\nDes équipes attendent le travail d'autres équipes pour avancer.\nLes changements dans un même composant sont systématiquement assignés à la même personne dans l'équipe, y compris quand elle est occupée.\nLes membres de l'équipe se plaignent d'un manque de doc.\nContexte :\nLes produits ont tendance à grossir, et à force ils dépassent la cognitive load d'une équipe.\nPlus les mêmes personnes travaillent sur les mêmes choses au sein de l'équipe, plus on va optimiser en fonction de ce que ceux qui sont disponibles savent, plutôt que ce qui est le plus prioritaire.\nPour creuser ce point, on peut lire The Phoenix Project ou encore The DevOps Handbook.\nLa cadence de delivery devient de plus en plus lente.\nSymptômes :\nOn ressent, et éventuellement mesure le ralentissement comme étant une tendance de fond.\nLe nombre de choses en cours augmente, avec des choses en attente du travail d'autres équipes.\nContexte :\nUne équipe qui fonctionne bien devrait plutôt augmenter petit à petit sa cadence à mesure qu'elle adopte de meilleures techniques et enlève les bottlenecks.\nLe ralentissement peut venir d'un problème de type DevOps : l'équipe n'est plus responsable sur l'ensemble du cycle de vie de son produit, mais doit attendre.\nPar exemple, une équipe QA a été mise en place et vérifie chaque changement avec qu'il aille en production.\nOu alors il peut aussi venir d'une dette technique qui grandit et devient de plus en plus insoutenable.\nLes services principaux se basent sur une grande quantité de services sous-jacents.\nSymptômes :\nLes équipes stream-aligned ont du mal à avoir de la visibilité sur ce qui se passe de bout en bout sur leur produit.\nElles ont du mal à avancer sur leur flow à cause de la complexité d'intégration avec les subsystems.\nContext :\nCe problème se produit surtout dans les secteurs très régulés (banque, assurance, gouvernement etc.).\nCa peut être parce qu'une entreprise d'assurance doit s'interfacer avec des machines physiques d'usine pour faire des vérifications, ou une banque qui doit utiliser des mécanismes de vérification d'identité complexes.\nSi les stream-aligned teams connaissent les détails de tous les services bas niveau, elles ne s'en sortent plus.\nLa solution est :\nD'avoir une plateforme bas niveau dont l'action est cachée des équipes stream-aligned.\nUne plateforme haut niveau qui utilise la première, et qui contient des stream-aligned teams développant chaque aspect utile pour les teams stream-aligned finales.\nIls peuvent notamment fournir des services de tracking de requête avec des correlation IDs, du logging, des dashboards etc. pour que comprendre ce qui se passe dans les services sous-jacents soit simple pour les équipes steam-aligned finales.\nSi on a des équipes stables dans le temps, et dont la nature et les interactions sont bien définies à chaque instant, on peut développer un outil stratégique au sein de l'entreprise : l'organizational sensing.\nLes différentes équipes se comportent alors comme les composants d'un organisme vivant, et peuvent **repérer des signaux pour s'adapter **en temps réel.\nNDLR : même métaphore de l'organisation comme être vivant que dans Reinventing Organizations.\nIls font référence au concept d'environmental scanning de Naomi Stanford.\nLes équipes pourront se poser des questions comme :\nFaut-il changer les modes d'interaction ?\nEst-ce que la collaboration entre deux équipes est encore efficace ou est-ce qu'il faut passer en x-as-a-service ?\nFaut-il continuer à développer tel composant in house, ou adopter une version du commerce ?\nEst-ce que le flow de telle équipe est aussi fluide que possible et qu'est-ce qui la bloque ?\nEst-ce que telle plateforme fournit ce dont les équipes consommatrices ont besoin ? Est-ce qu'ils auraient besoin d'une intervention temporaire d'une équipe facilitatrice ?\nUn des éléments qui permet l'organizational sensing est le feedback venant du logiciel en production, et donc des utilisateurs.\nL'idéal est d'avoir les devs et les ops dans la même équipe (DevOps) pour que ce feedback soit le plus immédiat.\nIl ne faut surtout pas avoir “d'équipe maintenance” (ou équipe business as usual BAU), mais plutôt d'utiliser la mine d'infos que représente un système en production pour alimenter en feedback les systèmes qui sont en développement plus actif.\nOn pourrait dire que la séparation “nouveau service / ancien service” n'est pas un très bon fracture plane, il vaut mieux les laisser dans la même équipe (ou au moins deux équipes pairées) et trouver autre chose si besoin de scinder l'équipe.\nIl faut que les personnes qui s'occupent des opérations soient parmi les plus expérimentées (et non pas les débutants comme le font de nombreuses organisations), pour pouvoir reconnaître et trier les problèmes et renvoyer un feedback précieux vers le développement.","conclusion#Conclusion":"La raison pour laquelle de nombreuses organisations ont des équipes démotivées et n'arrivent pas à avancer, c'est qu'elles ont une obsession pour la delivery de features, et ignorent l'aspect humain et les dynamiques d'équipe.\nImplémenter le modèle proposé par Team Topologies n'est pas suffisant, il faut aussi d'autres éléments pour avoir une delivery qui marche bien :\nUne culture saine où chacun peut aborder les problèmes en toute sécurité, et où l'apprentissage continu est mis en avant.\nDes pratiques techniques à la hauteur :\nTest-first.\nContinuous delivery.\nPairing et mobbing pour la code review.\nNe pas chercher une seule root cause pour les incidents.\nEtc.\nDes pratiques financières saines :\nNe pas faire de séparation CapEx/OpEx (budget pour les nouvelles features / budget pour la maintenance des features actuelles).\nÉviter les project-driven deadlines.\nAllouer le budget training aux équipes plutôt qu'aux individus.\nUne vision business claire avec un horizon raisonnable à 3 / 6 / 12 mois, et un raisonnement clair autour du choix des priorités pour que chacun comprenne le pourquoi de ces choix.\nComment mettre en place Team Topologies dans notre organisation :\n1 - On commence par se demander ce dont chaque équipe aurait besoin pour :\nÊtre efficace en tant qu'équipe.\nAvoir l'ownership d'une partie du logiciel.\nRépondre aux besoins utilisateurs.\nRéduire la cognitive load non nécessaire.\n2 - On identifie les streams de changement pertinents.\nIl s'agit des flows de changements principaux que veut faire avancer l'organisation.\nQuelques exemples :\nTask-oriented streams : les différents services (passport, taxes etc.) disponibles pour les citoyens sur un site gouvernemental.\nRole-oriented streams : des produits bancaires comme la gestion de d'argent, l'automatisation des transactions etc.\nActivity streams : chercher des tickets, acheter des tickets, gérer les annulations etc. pour un système de gestion de tickets.\nGeographical streams : marché européen, marché nord-américain etc.\nUser-type streams : consommateur, petites et moyennes entreprises, grandes entreprises etc.\n3 - On identifie la Thinnest Viable Platform (TVP).\nIl s'agit de trouver les services qui sont nécessaires pour que les streams soient rapides et fiables.\nAttention à n'y mettre que ce qui est nécessaire : la plateforme peut très bien se résumer à une page wiki qui décrit les outils externes utilisés.\n4 - On identifie les écarts de compétences dont on a besoin au sein des équipes.\nEn plus de l'expertise technique, on a besoin de :\nCoaching d'équipe\nMentoring\nService management\nDocumentation\nPour le détail des transformations d'organisation, les auteurs renvoient au livre Fearless Change de Mary Lynn Manns et Linda Rising.\n5 - On explique les principes de la nouvelle manière de travailler centrée autour des équipes.\nOn peut présenter la loi de Conway, expliquer la cognitive load, le principe team first basé sur l'humain, le fait de limiter les interactions entre équipes et les différentes interactions possibles, avec les raisons de chacune etc."}},"/books/the-five-dysfunctions-of-a-team":{"title":"The Five Dysfunctions of a Team","data":{"i---the-fable#I - The Fable":"L’histoire raconte comment une personne corrige les dysfonctionnements d’une équipe de direction.","ii---the-model#II - The Model":"","1---an-overview-of-the-model#1 - An Overview of the Model":"Le modèle décrit dans le livre s’applique aux équipes de direction d’entreprises, aux équipes dirigeant un département, ou même de simples équipes quelconques qui veulent s’améliorer.\nLe travail d’équipe est absent de la plupart des organisations, et elles souffrent en général de 5 dysfonctions :\n1 - Le manque de confiance.\nElle résulte du fait que les membres de l’équipe ne se montrent pas vulnérables face aux autres.\n2 - La peur de la confrontation.\nComme il n’y a pas de confiance, l’équipe n’arrive pas à mener de débats d’idées passionnés, et reste sur des discussions superficielles.\n3 - L’absence d’engagement.\nComme il n’y a pas de débats passionnés, les membres ne s'engagent pas vraiment sur les décisions, même quand elles sont prises en réunion.\n4 - L’évitement de la responsabilisation.\nComme il n’y a pas d’engagement sur des éléments clairs, les membres n’osent pas tenir les autres pour responsables en leur faisant remarquer leurs actions ou comportements contreproductifs.\n5 - L’inattention aux résultats.\nComme les membres ne sont pas responsabilisés, ils ont plus tendance à mettre en avant leurs objectifs personnels (égo, carrière, reconnaissance), plutôt que les résultats collectifs.\nDit autrement, les membres d’une équipe qui fonctionne bien :\n1 - Se font confiance.\n2 - S’engagent dans des débats d’idées.\n3 - S’engagent à mettre en œuvre leurs décisions.\n4 - Se tiennent mutuellement responsables de la réalisation de leurs plans.\n5 - Se concentrent sur l’atteinte de résultats collectifs.","2---team-assessment#2 - Team Assessment":"Ce chapitre présente un questionnaire pour évaluer à quel point une équipe souffre de chacun des 5 dysfonctionnements.\nIl faut attribuer 1, 2 ou 3 points à chaque question, selon si on pense que c’est rarement, parfois ou souvent.\nLes questions :\n(4, 6, 12) concernent la dysfonction 1\n(1, 7, 10) concernent la dysfonction 2\n(3, 8, 13) concernent la dysfonction 3\n(2, 11, 14) concernent la dysfonction 4\n(5, 9, 15) concernent la dysfonction 5\nQuestions :\n1 - Les membres de l’équipe sont passionnés et ouverts quand ils discutent d’un problème.\n2 - Les membres de l’équipe font remarquer les uns aux autres leurs comportements improductifs et points faibles.\n3 - Les membres de l’équipe savent sur quoi les autres travaillent, et en quoi ça apporte de la valeur à l’équipe.\n4 - Les membres de l’équipe s’excusent rapidement et sincèrement les uns envers les autres, quand ils ont dit ou fait quelque chose d’inapproprié ou qui nuit à l’équipe.\n5 - Les membres de l’équipe font des sacrifices dans leur sphère d’influence ou d’expertise dans l’intérêt de l’équipe (dans le cas d’une équipe dirigeante ça peut être sur le budget du département par exemple).\n6 - Les membres de l’équipe admettent ouvertement leurs faiblesses et erreurs.\n7 - Les meetings d’équipe sont intéressants et non pas ennuyeux.\n8 - Les membres de l’équipe quittent les meetings en étant sûrs que les autres sont pleinement engagés sur les décisions prises, même en cas de désaccord.\n9 - Le moral des membres est significativement affecté par l’échec à atteindre les objectifs.\n10 - Pendant les meetings d’équipe, les problèmes les plus importants et difficiles sont mis sur la table pour être résolus.\n11 - Les membres de l’équipe sont sincèrement préoccupés par l’idée de décevoir les autres.\n12 - Les membres de l’équipe connaissent la vie personnelle les uns des autres, et sont à l’aise d’en discuter.\n13 - Les membres de l’équipe terminent leurs discussions avec des résolutions claires, et des actions spécifiques.\n14 - Les membres de l’équipe se challengent mutuellement sur leurs approches et plans.\n15 - Les membres de l’équipe ont plus tendance à pointer la réussite sur la contribution des autres, plutôt que de rechercher du crédit sur le leur.","3---understandingand-uncovering-the-five-dysfunctions#3 - Understandingand Uncovering the Five Dysfunctions":"","dysfunction-1-absence-of-trust#Dysfunction 1: Absence of Trust":"Dans le cadre d’une équipe, la confiance désigne le fait que les intentions des autres sont bonnes, et qu’on peut donc se montrer vulnérables sans avoir peur que ce soit exploité contre nous.\nLa vulnérabilité désigne les faiblesses, le manque de compétence, les erreurs, les demandes d’aide etc.\nEn général, ceux qui réussissent en entreprise sont dans la compétition avec les autres, et protègent leur réputation.\nIls ont donc du mal à se montrer vulnérables, même si c’est ce qui rend une équipe efficace.\nOutils pour améliorer les choses :\nExercice d’histoires personnelles (30 mn à 1h) : les membres de l’équipe répondent à des questions personnelles simples face aux autres pour mieux se connaître mutuellement.\nCa peut être par exemple : nombre de frères et soeurs, lieu de naissance, défis relevés pendant l’enfance, passe-temps favoris, premier job, pire job.\nCa contribue à augmenter l’empathie mutuelle, et diminue la tendance à attribuer de mauvaises intentions aux autres.\nExercice d’efficacité d’équipe (minimum 1h) : chaque membre de l’équipe identifie la contribution la plus importante de chaque collègue, ainsi qu’un domaine où il doit progresser.\nLe groupe se concentre sur une personne à la fois, en commençant en général par le leader.\nCet exercice est un peu plus délicat que le premier et nécessite au moins un peu de confiance, mais permet d’avoir aussi plus de résultat.\nStyles de personnalité et profiles comportementaux (minimum 4h) : chaque membre réalise un test et obtient une catégorisation. Le fait de se rendre compte des différents types de personnalité permet à chacun une meilleure empathie.\nUn des plus connus est MBTI (Meyers-Briggs Type Indicator).\nFeedback à 360 degrés : chaque membre de l’équipe identifie les forces et faiblesses de ses collègues.\nC’est un exercice encore plus délicat que les autres, mais offre encore plus de résultats.\nL’auteur déconseille fortement de le corréler à une quelconque évaluation de performance avec des conséquences financières.\nExercice d’équipe “expérientiels” du genre team building : l’équipe pratique une activité concrète et gagne en confiance par l’expérience et la pratique.\nCes activités sont devenues moins populaires parce que moins efficaces que d’autres.\nElles peuvent quand même avoir une certaine efficacité, à condition d’être bien pensées.\nLe leader :\nIl doit montrer l’exemple en se montrant vulnérable face aux autres, pour que les autres puissent prendre ce risque aussi. Sa démarche de vulnérabilité doit être sincère et non pas feinte.\nIl doit instaurer un climat où la vulnérabilité n’est pas punie, où on ne reproche pas à quelqu’un ses faiblesses et ses échecs.","dysfunction-2-fear-of-conflict#Dysfunction 2: Fear of Conflict":"Il faut faire la différence entre la confrontation d’idées, et les conflits interpersonnels :\nDans la confrontation d’idées on n’attaque pas l’autre sur sa personne, on reste sur le terrain du problème discuté.\nPar contre les deux sont tout aussi passionnés, plein d’émotions, de frustrations.\nEn n’ayant pas de discussions animées sur les idées, on a un grand risque de favoriser les tensions, les attaques personnelles sous forme de piques passive-agressive et autres mauvaises relations.\nConcernant l’argument de l’efficacité : c’est au contraire les équipes qui n’ont pas de confrontations d’idées animées qui reviennent en permanence sur la même discussion qu’ils reportent à chaque fois.\nOutils pour améliorer les choses :\nMining : il faut parfois qu’un des membres de l’équipe ait le rôle de “mineur de conflit”, qui va creuser les désaccords de sujets sensibles enfouis par les autres.\nOn peut parfois assigner ce rôle à une personne pendant un meeting donné.\nPermission en temps réel : pendant une confrontation, les autres membres doivent s’encourager les uns les autres à rester dans le débat productif.\nQuand on voit que l’un des membres montre de l’inconfort vis-à-vis du niveau de désaccord, on l’interrompt, et on lui rappelle que le débat qui a lieu est important pour l’équipe.\nOn peut aussi le rappeler à la fin, en faisant comprendre que ce n’est pas un comportement à en plus reproduire.\nAutres : il y a divers autres outils non détaillés dans le livre, l’un d’entre eux est le Conflict Mode Instrument (TKI) de Thomas-Killmann, qui permet de mieux comprendre le penchant naturel pour les conflits et faire les bons choix en la matière.\nLe leader :\nIl doit laisser se dérouler la confrontation, sinon il laissera les membres de l’équipe avec un sentiment de non résolution.\nIl doit lui aussi participer à la confrontation quand l’occasion se présente, et non pas chercher à l’éviter.","dysfunction-3-lack-of-commitment#Dysfunction 3: Lack of Commitment":"L’engagement dépend de la clarté des décisions et de l’acceptation des décisions par les membres.\nLe manque d’engagement peut venir de deux problèmes :\nLa recherche de consensus unanime est souvent impossible. Au lieu de ça, ce qu’il faut c’est plutôt que le point de vue de chacun soit entendu, et pour que chacun se rallie à la décision que le groupe prendra.\nSi le groupe se retrouve dans une impasse, le leader est autorisé à trancher.\nLa recherche de certitude paralyse la prise de décision. Il vaut mieux décider malgré le manque d’information et changer de décision par la suite que de ne rien faire.\nLes débats animés permettent justement de faire ressortir les informations précieuses de la pensée de chacun, pour prendre les meilleures décisions possibles sur le moment.\nOutils pour améliorer les choses :\nMessages en cascade : on prend au moins 10 minutes à la fin d’un meeting pour récapituler les décisions prises, au besoin les clarifier, et décider de ce qui doit être communiqué à l’extérieur.\nDeadlines : on définit un temps limite pour qu’une décision soit prise sur un sujet particulier.\nCa évite de remettre le sujet à plus tard, et force à décider pour s’engager.\nAnalyse du pire cas possible : on essaye d’envisager ce qui pourrait arriver de pire si on prend une décision. Ca permet d’être plus confiant en relativisant les conséquences, et de prendre la décision plus facilement.\nThérapie par l’exposition au faible risque : on prend des cas où les conséquences des décisions ne sont pas très importantes, et on essaye de les prendre plus rapidement.\nEn analysant la situation, les équipes qui ont un problème d’engagement vont se rendre compte qu’en prenant des décisions après un débat animé et peu de recherche, elle sera à peu près aussi pertinente qu’en faisant beaucoup de recherche (parce qu’ils ont tendance à trop décaler la prise de décision).\nLe leader :\nIl doit lui aussi pousser à prendre des décisions rapidement, et à ne pas chercher à être sûr ou que tout le monde soit pour une proposition avant d’avancer.","dysfunction-4-avoidance-of-accountability#Dysfunction 4: Avoidance of Accountability":"L’accountability veut dire que les membres de l’équipe tiennent leurs pairs pour responsables vis-à-vis de leur manque de performance ou de leur comportement qui nuirait au groupe.\nL’idée que ne rien dire permettrait de préserver les relations est une erreur : les membres de l’équipe vont se reprocher de ne rien dire et de laisser la situation se dégrader.\nLa pression exercée par les pairs permet de ne pas avoir besoin de bureaucratie, et d’être quand même dans l’excellence et la performance.\nOutils pour améliorer les choses :\nLa publication des objectifs et des standards : en clarifiant et rendant public les objectifs et les standards de comportement, on rend plus facile le fait que les membres se tiennent responsables vis-à-vis de ceux-ci.\nDes évaluations simples et régulières : en mettant en place un cadre léger pour que les membres se fassent des évaluations (orales ou écrites) régulières, on les pousse à se tenir responsables mutuellement.\nLes récompenses d’équipe : si les récompenses sont collectives, les autres vont avoir tendance à plus difficilement accepter qu’un des membres contribue peu, vu que leur récompense dépend de son action aussi.\nLe leader :\nLa difficulté pour le leader c’est de ne pas s’imposer comme la source d’autorité principale, au risque que le reste des membres considèrent qu’il n’est pas de leur responsabilité de tenir les autres responsables.\nPour autant, le leader doit être prêt à servir de “filet de sécurité” en termes d’autorité dans l’éventualité où l’équipe n’a pas fait son travail sur le fait de se tenir mutuellement responsables.","dysfunction-5-inattention-to-results#Dysfunction 5: Inattention to Results":"En parlant de résultats on parle ici des objectifs principaux qu’une équipe se fixe (par exemple OKR).\nLes membres d’une équipe qui a cette dysfonction peuvent être concentrés sur deux autres choses :\nLe statut de l’équipe : pour certains membres, le simple fait de faire partie de l’équipe est suffisant, et l’atteinte des résultats devient secondaire.\nC’est souvent le cas chez les organisations à but non lucratif, ou les entreprises prestigieuses.\nLe statut individuel : certains membres de l’équipe mettent en avant plus leur propre intérêt personnel (par exemple de carrière) plutôt que la réalisation des résultats collectifs.\nNDLR : par exemple le CV Driven Development.\nOutils pour améliorer les choses :\nLa déclaration publique des résultats : en s’engageant publiquement, les membres de l’équipe s'engagent en quelque sorte, et vont avoir plus tendance à essayer de réussir.\nRécompense basée sur les résultats : une récompense, par exemple financière, peut venir récompenser la réalisation des objectifs d’équipe.\nAttention par contre à ce que ce ne soit pas le seul moteur de motivation.\nLe leader :\nIl doit faire office de modèle en s’intéressant aux résultats. S’il ne le fait pas, les autres vont s’en désintéresser d’autant plus.\nIl doit réserver la reconnaissance et les récompenses pour l'atteinte des résultats."}},"/books/turn-the-ship-around":{"title":"Turn the Ship Around","data":{"introduction#Introduction":"La motivation des salariés est au plus bas aux Etats Unis, et la plupart des programmes d’empowerment ne donnent rien.\nLe modèle leader-follower traverse les siècles et est encore prédominant aujourd’hui : il s’agit d’avoir deux camps, ceux qui contrôlent et ceux qui sont contrôlés.\nCe modèle était efficace pendant très longtemps, mais devient inefficace dans les temps récents parce qu’on fait de plus en plus de travail intellectuel, qui nécessite une motivation intrinsèque de chacun à utiliser ses capacités cognitives.\nL’efficacité de ce modèle dépend en grande partie des aptitudes du leader : dès qu’il part, si le nouveau leader est moins bon, les performances baissent immédiatement.\nLa technique de l’empowerment pose problème parce qu’elle pose les employés comme assistés, et donc entre en contradiction avec le fait de leur donner du pouvoir.\nLe modèle leader-leader consiste à transformer les followers en leaders. Il est possible parce que la qualité de leader peut être apprise par n’importe qui.\nIl ne se base pas sur la qualité d’une seule personne, et donc permet à l’organisation d’être bien plus résiliente, même quand l’un des leaders s’en va.\nLes trois expériences qui ont marqué l’auteur :\nIl a d’abord été subordonné sur le Sunfish, il a été marqué par le capitaine qui lui a laissé essayer son idée d’utiliser le sonar actif pour sonder un navire marchand, en lui laissant complètement le champ libre.\nPar la suite, il a voulu à son tour empower ses subordonnés sur le Will Rogers, mais son expérience a échoué, et il a fini par les micro-manager.\nIl a ensuite été capitaine sur le Santa Fe, où il a retenté l’expérience, cette fois avec plus de succès, succès qui se mesure encore mieux 10 ans plus tard. C’est cette expérience qui est racontée dans ce livre.\nLe modèle leader-leader se décompose en 3 composantes :\n1 - Laisser le contrôle aux subordonnés, tout en gardant la responsabilité.\n2 - Augmenter la compétence des subordonnés.\n3 - Augmenter la compréhension du but de l’organisation chez les subordonnés.","i---starting-over#I - Starting Over":"","1---pain#1 - Pain":"L’auteur raconte son échec sur le Will Rogers en 1989. Le sous-marin était vieux et avait raté une certification récemment.\nL’officier que l’auteur remplaçait était très axé sur la direction minutieuse des opérations de maintenance, et était sollicité en permanence pour donner des directives.\nLe but de l’auteur était d’adopter un leadership plus proche de celui qu’il avait connu sur le Sunfish.\nPar exemple ne pas donner de consignes précises, poser des questions sur la manière dont l’équipage abordait les problèmes, mettre en relation directement les sous-chefs de département.\nLes subordonnés de l’auteur ont fini par faire des erreurs, et il s’est rendu compte juste avant une inspection qu’un refroidisseur était mal assemblé. Ça a causé le report de l’inspection et a donné une mauvaise image de son équipe et du bâteau.\nL’auteur est finalement revenu à un leadership plus directif jusqu’à la fin de sa mission, à contre-cœur.\nTrès peu de membres du Will Rogers ont été promus à cause de la mauvaise performance globale pendant le séjour de l’auteur.\nL’auteur a repensé à son expérience, et en a tiré des questionnements à propos du type de leadership dominant :\nL’idée d’empowerment est en soi porteur de contradiction : il s’agit d’un mécanisme infantilisant, venant du haut, alors que le but est que le pouvoir soit en bas.\nSelon l’auteur c’est à cause du fait que les gens ont été habitués à ne pas exercer de pouvoir.\nL’auteur ressent le côté démotivant du management top-down sur lui-même quand ça vient de ses supérieurs, et ne veut pas faire aux autres ce qu’il ne trouve pas épanouissant.\nIl note aussi l’absence de résilience de l’approche top-down : il suffit que la direction fasse un faux pas et le reste de l’équipage suit sans garde-fou, alors qu’ils auraient les capacités pour repérer les erreurs.\nQuestions à se poser :\nL’empowerment est-il nécessaire, et doit-il venir de quelqu’un d’autre ?\nEst-ce que toute l’organisation dépend des décisions de quelques personnes ?\nDans quelle mesure la culture dominante influence l’image du leadership qu’on va adopter ?","2---business-as-usual#2 - Business as Usual":"En 1998, l’auteur devenu capitaine était censé diriger l’Olympia, un sous-marin d’attaque ayant connu beaucoup de succès récemment.\nUn mois avant de devenir responsable du sous-marin, il veut monter à bord pour assister à une inspection, mais l’autre capitaine refuse : la raison est qu’il n’a aucun intérêt à faciliter la passation puisqu’il n’est jugé que sur le temps où il est en responsabilité.\nSi les choses tournent mal après son départ, le leader sera vu comme étant quelqu’un de très fort dont la présence manque, plutôt que comme n’ayant pas bien entraîné ses subordonnés.\nFinalement il parle à l’équipage, et se rend compte qu’ils ont un fonctionnement très vertical. Il se dit qu’il aura du mal à changer ce mode de leadership étant donné les succès actuels qu’ils connaissent.\nJuste avant qu’il prenne le commandement du sous-marin, on lui annonce qu’il va plutôt prendre le commandement d’un autre sous-marin : le Santa Fe.\nLe leadership vertical a tendance à mettre en place un fonctionnement fragile, mais qui finit par arranger les gens, en particulier sur le court terme :\nLe capitaine est content parce qu’il exerce un grand pouvoir.\nLes subordonnés s’enlisent dans une forme de paresse et de facilité à suivre les ordres, et deviennent de moins en moins compétents.\nIls vont aussi avoir plus tendance à répéter le modèle en exerçant le pouvoir qu’ils ont sur leurs subordonnés.\nLe manque de compétence et l’incapacité à prendre des initiatives de la part des subordonnés finit par être visible par une faible performance à long terme.\nQuestions à se poser :\nEst-ce que les leaders sont récompensés pour le succès à long terme ? Le succès de leurs subordonnés ?\nSi l’organisation subit un échec après le départ du leader, est-ce qu’on pense que c’était un bon leader ou un mauvais ?","3---change-of-course#3 - Change of Course":"Contrairement à l’Olympia, la Santa Fe va d’échec en échec et est très mal vu.\nNon seulement les performances sont mauvaises sur le Santa Fe, mais la rétention est aussi catastrophique : seuls 3 membres de l’équipage ont reconduit leur présence.\nL’auteur a 6 mois pour préparer le sous-marin et l’équipage pour une mission.\nCôté équipage, l’auteur a 5 officiers chefs de département et 12 chefs qui constituent le middle management, et le reste composé de juniors qui pour la plupart n’ont connu que le Santa Fe. La plupart sont plutôt démoralisés.\nLe supérieur de l’auteur lui a confié le commandement du Santa Fe parce qu’il pense qu’il sera capable de le changer du tout au tout. C’est une situation avec une absence de leadership.\nSon supérieur ne le micro-manage pas, il lui laisse le champ libre pour agir. C’est une occasion unique pour l’auteur d’implémenter un style de management non vertical.\nQuestions à se poser :\nEn tant que leader, est-ce qu’on est prêt à prendre des risques personnels pour faire réussir la mission et l’équipe en adoptant un type de management différent ? Est-ce qu’on est prêt à abandonner du pouvoir et du prestige pour le bien de l’organisation ?\nEn tant que leader, est-ce qu’on est prêt à confier des objectifs, en laissant les subordonnés choisir comment les réaliser ?","4---frustration#4 - Frustration":"L’auteur débarque sur le Santa Fe et trouve un équipage démoralisé, à qui on ne cesse de répéter qu’ils sont les plus mauvais.\nIl pose des questions à l’équipage pour mieux comprendre le contexte, connaître leur point de vue sur ce qui devrait changer, les problèmes principaux, leurs objectifs personnels etc.\nIl obtient de nombreuses réponses parlant de problèmes de gestion et de désorganisation sur le Santa Fe. Pas mal de personnes qui se plaignent que leur carrière est au point mort.\nL’auteur avait étudié en détail le fonctionnement de l’Olympia, mais ne connaît pas le Santa Fe. Il est donc obligé d’être sincèrement curieux en posant des questions à l’équipage pour obtenir des connaissances techniques.\nIl passe en revue les registres en étant toujours accompagné d’un membre de l’équipage.\nIl se concentre davantage sur l’équipage, et s’appuie sur leur expertise, ça lui permet de construire une relation avec eux qu’il n’aurait pas eu sinon.\nQuestions à se poser :\nEst-ce que le leader doit être le plus compétent techniquement ?\nEst-ce que la compétence technique est personnelle, ou est-ce qu’elle émerge du collectif ?\nComment faire en tant que leader pour savoir ce qui se passe sur le terrain ?","5---call-to-action#5 - Call to Action":"L’auteur observe l’équipage et remarque qu’une partie d’entre eux sont résignés.\nC’est le cas pour Dave, l’un des chefs de département qui semble particulièrement compétent, mais dont les idées ne sont jamais écoutées par la hiérarchie.\nL’un des juniors n’arrive pas à avoir sa permission pour Noël à cause du fait qu’elle doit être validée par 7 personnes plus haut dans la hiérarchie.\nRick le responsable de la radio se plaint que le capitaine se réserve la lecture des informations qui arrivent, pour pouvoir les filtrer si besoin, et pour pouvoir remettre la faute sur les subordonnés au cas où quelque chose qui est demandé ne serait pas fait.\nLe capitaine fait ce qu’il faut pour renforcer la posture de leader-follower qui cause cette résignation :\nA l’une des réunions où tout l’équipage est réuni, le capitaine arrive en dernier et avec du retard, et parle pour les plus hauts gradés, les autres étant considérés moins importants.\nA l’une des célébrations où des officiers sont décorés, le capitaine prononce quelques mots, mais on voit bien qu’il ne connaît pas ses hommes.\nLe but principal de l’équipage est d’éviter les problèmes de tous genres, plutôt que d’accomplir quelque chose.\nEt pourtant l’auteur sent une volonté de bien faire qui ne demande qu’à être libérée. Il se donne l’objectif de répondre à cet élan.\nQuestions à se poser :\nEst-ce que les gens veulent se dépasser ou sont dans une zone de confort ?\nEst-ce qu’ils se protègent en évitant les problèmes, ou est-ce qu’ils cherchent à arriver à un meilleur résultat ?\nEst-ce que les leaders prennent le contrôle ou laissent le contrôle ?","6---whatever-they-tell-me-to-do#6 - “Whatever They Tell Me to Do!”":"Une photo montrant l’équipage manifestant peu de sérieux dans un endroit important du sous-marin a circulé sur internet.\nQuand l’auteur demande à l’un de ces officiers ce qu’il fait sur le sous-marin, il répond avec cynisme qu’il fait “ce qu’on lui dit de faire”.\nL’officier en chef des départements demande à ce que chacun des chefs de département lui fasse un rapport, pour qu’il puisse vérifier qu’ils ont bien fait tout ce qu’il leur avait demandé, et donner d’éventuelles instructions.\nSelon l’auteur, faire le rapport est acceptable, mais il faudrait qu’il ne soit qu’informatif, pour que la responsabilité reste de leur côté et non pas du côté de leur chef. C’est à eux de prendre les décisions.\nL’auteur accepte en tant que futur capitaine de laisser les décisions aux chefs de départements, tout en étant responsable de l’ensemble de ce qu’ils décident.\nDe manière plus générale, l’auteur pense aussi au reste de l’équipage en dessous des chefs de département, dont l’intelligence devait être mise à contribution en leur donnant plus d’autonomie.\nIl se met en tête de renverser le type de leadership dès qu’il sera capitaine.\nQuestions à se poser :\nEst-ce que les gens sont vraiment contents d’obéir ? Pour ceux qui le seraient, pour quelle raison ils le sont ?\nQuel type de leadership est favorisé par les procédures mises en place ?\nSi le fonctionnement interne était révélé dans un journal, est-ce qu’on en serait contents ?","7---i-relieve-you#7 - “I Relieve You!”":"Les règles de l’armée américaine poussent à un leadership leader-follower en donnant un pouvoir absolu au capitaine, et en indiquant la délégation comme optionnelle.\nL’équipage du Santa Fe est centré sur le fait de ne pas commettre d’erreurs, parce que les sous-marins sont évalués sur ce critère.\nLe problème c’est que ça pousse à l’inaction et empêche l'initiative : moins on fait de choses, et moins ce sera une source d’erreurs.\nL’auteur compte remettre le focus sur la réalisation de l’excellence, en ramenant le focus sur les erreurs au second plan.\nUne partie de la réalisation de l’excellence sera d’obtenir une compréhension profonde des mécanismes sous-jacents qui mènent aux erreurs.\nLa mise en avant de l’excellence plutôt que le fait d’éviter les problèmes est un mécanisme de clarity (cf. Start with Why de Simon Sinek).\nL’auteur devient enfin capitaine du sous-marin.\nQuestions à se poser :\nEst-ce que nos équipes prennent des risques et des initiatives, ou est-ce qu’ils en sont découragés à cause des problèmes qui pourraient en résulter ?\nEst-ce que le middle management a comme motivation principale d’éviter les problèmes ou d'accomplir de grandes choses ?\nQuand on analyse les décisions, quel type de critère les drive ?","ii---control#II - Control":"","8---change-in-a-word#8 - Change, in a Word":"Plutôt que de changer le mindset pour espérer un changement de comportement, il commence par changer les actions, et le mindset suivra.\nL’auteur décide de commencer par les chefs qui sont juste au-dessus des officiers de base. Sa justification est d’éviter une approche trop top-down, mais en même temps en pouvant s’appuyer sur des personnes qui ont des notions de leadership.\nLe leadership des chefs est très faible\nD’une part parce que le haut de la hiérarchie décide de trop de choses directement.\nD’autre part parce qu’une partie d’entre ont été formés au leadership centré sur le respect des procédures, nécessaire pour gérer un réacteur nucléaire. Mais coller aux procédures n’est pas efficace pour manoeuvrer un sous-marin contre un ennemi.\nL’auteur leur demande s’ils veulent avoir un rôle important sur le sous-marin, il faut que la volonté vienne d’eux, sinon ce serait une transformation leader-follower.\nParmi les problèmes principaux des chefs, il y avait la mauvaise évaluation du sous-marin, le faible taux de promotion, et des problèmes liés au manque de contrôle sur l’emploi du temps des chefs et de leurs équipes.\nL’idée c’est de changer le “code génétique” de l’organisation juste ce qu’il faut pour qu’il y ait le résultat voulu.\nL’auteur évoque le livre Build to Last de Jerry Porras qui parle des institutions qui restent, alors que les personnalités qui vont et viennent.\nLa première mesure mise en place, et proposée par les chefs, c’est de rendre les chefs responsables de la validation des permissions de sortie de leurs équipes, alors que jusque-là la validation devait aller jusqu’en haut de la hiérarchie.\nLe changement en question va même plus loin, puisqu’ils seront aussi en charge des rotations et de tous les aspects administratifs.\nPour compenser le pouvoir enlevé aux officiers plus haut dans la hiérarchie, l’auteur s'ampute lui-même d’une partie de son pouvoir de validation pour le leur laisser à ses subordonnés directs.\nDans les itérations suivantes, un chef était nommé responsable de chaque évolution se passant sur le Santa Fe.\nL’auteur propose l’exercice suivant pour changer le code génétique de notre organisation, et laisser plus de contrôle au bas de la hiérarchie :\n1 - Identifier les documents ou procédures qui spécifient qui a l’autorité pour prendre les décisions.\n2 - Identifier les types de décisions qui pourraient être poussées plus bas dans la hiérarchie.\n3 - Réunir le groupe de décideurs, et leur demander de compléter : “Quand je pense à déléguer cette décision, je m’inquiète de …”.\n4 - Faire une longue pause et laisser le groupe réfléchir à la question.\n5 - Reprendre en attaquant les objections une par une.\nEn général elles seront en deux catégories : des inquiétudes sur la compétence, et des inquiétudes sur la clarté des objectifs de l’organisation pour les subordonnés.\nPrincipes à retenir :\nSi on ne s’attaque pas au “code génétique” en donnant vraiment du pouvoir aux subordonnés, mais qu’on fait plutôt des programmes d’empowerment avec des changements à la marge, ça ne fonctionnera pas.\nSi le changement est entièrement piloté par le haut, ça ne marchera pas : il faut qu’il soit voulu par les subordonnés.\nQuestions à se poser :\nComment pousser les mid level managers à passer d’une position privilégiée où ils ne font rien parce que tout le pouvoir est concentré en haut, à une position où ils prennent des responsabilités ?\nQuand on veut déléguer du pouvoir, de quoi est-ce qu’on a peur ?\nQuel changement peut-on faire qui donnerait un pouvoir de décision supplémentaire aux managers mid level, et qui ne serait constitué que d’un mot à changer dans un document clé ?","9---welcome-aboard-santa-fe#9 - “Welcome Aboard Santa Fe!”":"Une inspection se tiendrait dans une semaine, pour vérifier que l’équipage sait ce qu’il fait, et que la maintenance est correcte. Mais les compétences n’étaient pas là.\nUne partie de l’équipage était sceptique à l’idée de la nouvelle manière leader-leader de faire. Mais une masse critique supportait l’auteur.\nAu moment de parler à l’ensemble de l’équipage, l’auteur décide de faire se rapprocher les subordonnés, et mettre les chefs à l’arrière.\nL’auteur demande aux officiers ce qui serait nécessaire pour que l’équipage soit davantage fier de leur sous-marin, récupère les réponses, et décide qu’il faudra se comporter comme s'ils l’étaient : dès qu’un visiteur venait, il faudrait le saluer de son nom, donner son propre nom, et donner le nom du sous-marin.\nIl propose l’exercice suivant pour créer un changement culturel dans une organisation :\n1 - Donner des cartes à remplir aux employés, et leur demander de remplir la phrase : “Je saurai que nous avons réussi tel changement culturel, si je vois les employés faire …”.\n2 - On colle les cartes sur le mur, et on prend une pause.\n3 - Chacun regarde les cartes, et on refait éventuellement un deuxième tour pour laisser l’opportunité à ceux qui n’avaient pas rempli les cartes d’en remplir.\n4 - On classe les cartes, et on s’attèle à la manière d’écrire le comportement culturel dans les pratiques de l’entreprise.\n5 - On met à jour les procédures nécessaires pour l’officialiser.\nDans le choix entre changer le comportement pour changer le mindset, et changer le mindset pour changer le comportement, l’auteur propose de commencer par changer le comportement.\nQuestions à se poser :\nComment faire quand les employés ne veulent pas changer leur manière de fonctionner ?\nDans le cadre d’une transformation organisationnelle, est-ce qu’il faut agir d’abord et réfléchir ensuite, ou faire l’inverse ?","10---underway-on-nuclear-power#10 - Underway on Nuclear Power":"Malgré des progrès, l’équipage avait encore quelques difficultés à œuvrer à l’excellence, plutôt qu’à suivre les procédures et éviter les erreurs.\nL’équipage devait réaliser une carte précise des fonds sous-marins pour un des trajets du Santa Fe.\nUn grand nombre de personnes étaient impliquées dans la réalisation et la revue de la carte, pour qu’elle contienne le moins d’erreurs possibles.\nLa carte était finalement parfaitement réalisée, en respectant les bonnes procédures, mais était inutilisable parce qu’elle faisait passer par le mauvais chemin.\nEn plus du suivi des procédures, ce qui a contribué à l’échec de la carte était aussi la tendance à vouloir délivrer un produit parfaitement fini du premier coup.\nL’auteur a décidé de s’impliquer plus régulièrement dans le processus le temps que l’équipage soit plus dans l’idée de rechercher l’excellence.\nUn autre problème avec les cartes c’est que les couleurs n’étaient pas cohérentes entre plusieurs cartes, certaines choses étaient représentées en rouge dans l’une et en jaune dans l’autre, avec la légende correspondante.\nL’auteur a décidé de réunir l’ensemble des personnes impliquées dans la réalisation des cartes et d’en parler.\nL’équipage avait en fait mal compris ce qu’on essayait de faire exactement avec ces couleurs, et utilisait celles à disposition.\nAprès discussion, et suggestions de diverses personnes, le groupe est arrivé à un accord sur des couleurs en fonction de la nature des eaux : bleu pour l’eau sous le contrôle du Santa Fe, jaune pour les eaux dont il faut rester en dehors, et vert pour celles où il faut rester en profondeur.\nA propos de la représentation des informations et des couleurs, l’auteur conseille The Visual Display of Quantitative Information de Edward Tufte.\nLes conversations fréquentes et tôt dans le processus sont un mécanisme qui permet de laisser le contrôle aux mains des subordonnés : ils reçoivent du feedback et peuvent décider de la manière dont ils font avancer les choses.\nC’est aussi un mécanisme qui permet la clarté puisqu’on guide dans le sens dans lequel va l’organisation.\nLa confiance consiste à penser que la personne est de bonne foi et pense ce qu’elle dit, c’est de l’ordre du relationnel. Ca ne consiste pas à acquiescer avec la position qu’elle tient, qui est plus un élément technique.\nQuestions à se poser :\nEst-ce que nos équipes passent leur temps à réaliser des documents inutiles ?\nComment rendre les informations importantes accessibles aux employés ?\nComment convaincre les employés d'avoir des conversations fréquentes avec les supérieurs hiérarchiques ?","11---i-intend-to#11 - “I Intend To…”":"Dans les quelque jours avant une importante inspection, l’équipage se prépare en réalisant un exercice où le moteur est désactivé, et la panne doit être réparée rapidement.\nL’auteur, voulant mettre un peu plus de pression à l’équipage, demande à l’un des subordonnés d’augmenter le rythme d’un élément technique spécifique à la valeur ⅔.\nLe subordonné fait passer l’ordre, mais il n’est pas exécuté. En fait le cran ⅔ n’existait pas sur ce modèle de sous-marin.\nL’auteur demande si le subordonné le savait, celui-ci répond oui, et dit qu’il a fait passer l’ordre parce qu’il a obéi, il pensait que l’auteur avait des informations auxquelles seuls les dirigeants ont accès.\nL’auteur décide alors de ne plus donner d’ordres, et demande aux subordonnés de dire “J’ai l’intention de …” à chaque fois qu’ils veulent faire quelque chose.\nLe comportement est adopté par les chefs, mais aussi par l’ensemble de l’équipage. C’est l’une des mécaniques qui aura le plus d’impact sur l’adoption d’un leadership leader-leader.\nL’auteur cite The 7 Habits of Highly Effective People de Stephen Covey, dont de nombreuses idées ont été implémentées sur le Santa Fe.\nChanger le langage pour que les subordonnés indiquent ce qu’ils vont faire plutôt que de demander la permission est un mécanisme qui permet de leur laisser plus de contrôle.\nQuestions à se poser :\nQu’est-ce qui fait qu’en tant que leader, on prend du contrôle au lieu d’en donner ?\nQu'est- ce qu’il faudrait surmonter pour que l’ensemble des personnes utilisent “J’ai l’intention de …” dans notre organisation ?","12---up-scope#12 - Up Scope!":"En pleine simulation d’attaque, l’auteur, fatigué, donne une directive de déplacement du sous-marin et va se coucher.\nEn se réveillant, il se rend compte que la directive a été suivie, mais que les choses ne se sont pas passées comme prévu, parce que la situation avait changé depuis.\nN’ayant donné que la directive et pas le raisonnement derrière, l’équipage ne pouvait que le suivre aveuglément et non pas faire preuve d’intelligence.\nPlus tard, une autre question se pose au sujet de déployer l’antenne radio pour recevoir les messages, au risque de se faire repérer par le sous-marin ennemi à proximité.\nL’auteur veut donner une directive mais se retient, et laisse du temps pour que les autres puissent intervenir.\nL’un des membres fait alors remarquer qu’ils pourront toujours récupérer les messages juste après avoir coulé le sous-marin ennemi, ce qui est une bonne idée. Ils le mettent en place et tout se passe comme prévu.\nRésister à l’urgence de donner une solution est un mécanisme qui permet de redonner du contrôle aux subordonnés.\nSi la décision est vraiment urgente, on peut la prendre, mais il faudra revenir dessus avec l’équipe.\nSi la décision peut attendre un peu, on prend l’avis de l’équipe d’abord, même brièvement.\nL’idée n’est pas d’arriver à une unanimité, mais de faire ressortir les divergences pour prendre la meilleure décision possible.\nQuestions à se poser :\nQuand un problème survient, en tant que leader est-ce qu’on pense immédiatement qu’on doit tout gérer avec plus d’attention, ou est-ce qu’on se dit qu’on doit lâcher du contrôle ?\nComment faire pour qu’à notre prochaine réunion d’équipe, on laisse de la place pour l’ensemble du groupe, pour qu’une décision soit prise par l’équipe toute entière ?","13---whos-responsible#13 - Who’s Responsible?":"L’inspection a aussi remarqué que le Santa Fe n’avait pas répondu à un certain nombre de messages importants adressés par le haut commandement.\nLes messages étaient en fait catégorisés et ceux en attente listés. Il y avait ensuite un meeting régulier pour faire le point ce qui a été fait ou est en retard.\nPour l’auteur, ce meeting représente un overhead, et surtout envoie le message que la responsabilité est top-down : le management surveille les subordonnés.\nL’auteur prend alors la décision d’éliminer le monitoring top-down centralisé, et de déléguer entièrement la responsabilité de répondre aux messages et d’en faire le suivi aux chefs de département.\nÉliminer le monitoring top-down est un mécanisme qui permet de redonner du contrôle, en mettant la responsabilité du travail fait entièrement aux mains des subordonnés.\nOn parle ici d’éliminer le monitoring qui consiste à surveiller pour ensuite aller dire que ça ne va pas, pas de la simple collecte de données qui permet d’avoir de la visibilité.\nQuand on veut que les employés soient empowered, se comportent comme ayant de l’ownership, il ne faut pas juste le décréter, mais faire les changements de process qui vont le permettre, comme par exemple l’élimination du monitoring top-down.\nLes leaders qui se plaignent du manque de motivation mais gardent des process top-down sont décrédibilisés.\nL’auteur conseille Out of the Crisis d’Edwards Deming, à propos du TQL (Total Quality Leadership), qui parle spécifiquement de changer les process pour donner de l’ownership aux employés.\nQuestions à se poser :\nCombien de systèmes de monitoring top-down est-ce qu’il y a dans notre organisation ? Comment les éliminer ?\nEst-ce qu’on verrait un équivalent au meeting des messages qu’on pourrait éliminer pour donner plus de responsabilité au middle management ?","14---a-new-ship#14 - “A New Ship”":"Pendant une manoeuvre, alors qu'un des subordonnés contrôlait la direction du sous-marin, il a mis du temps à dire de tourner, et l’auteur lui a mis un coup de pression en lui demandant s’il comptait le faire : il comptait bien le faire, mais le fait que l’auteur lui donne l’instruction a fait qu’il n’était plus vraiment aux commandes.\nDe manière plus générale, l’auteur se pose des questions sur la raison pour laquelle il doit encore aussi souvent intervenir pour résoudre les problèmes, plutôt que les subordonnés soient autonomes. Il en parle avec les chefs de département :\nLes objectifs organisationnels ne sont pas suffisamment au centre de l’attention (problème de clarity).\nLa communication est trop formelle : ils utilisent un langage militaire où chaque mot a une signification précise, mais en contrepartie ils ont tendance à éviter la nuance. Des choses comme “Je pense que”, “Il me semble que” etc.\nL’auteur demande à l’équipage de faire un travail sur la communication, et d’augmenter les communications informelles en disant à haute voix ce qu’on pense.\nIl pense que si la personne qui comptait tourner dans quelques instants s’exprimait régulièrement, il n’aurait pas eu à lui mettre un coup de pression, et elle aurait moralement gardé le contrôle du sous-marin.\nPenser à haute voix est un mécanisme qui permet de donner du contrôle aux subordonnés : les supérieurs hiérarchiques n’ont pas besoin d’intervenir parce qu’ils sont rassurés, et peuvent plus facilement laisser les subordonnés exécuter leurs plans en autonomie.\nLe fait que les supérieurs hiérarchiques eux aussi pensent à haute voix permet de donner des informations de contexte aux subordonnés.\nL’inspection se passe bien, et le commandant félicite l’auteur, qui à son tour peut féliciter l’équipage.\nQuestions à se poser :\nA quel point les employés sont à l’aise avec l’idée de parler de leurs intuitions ?\nComment créer un environnement où les employés expriment leurs incertitudes, idées et espoirs ?\nA quel point la confiance est liée à ces éléments ?","15---we-have-a-problem#15 - “We Have a Problem”":"L’un des membres de l’équipage a oublié d’enlever un balisage indiquant que l’électricité n’était pas allumée sur des câbles, alors qu’il l’a allumé. Il n’y a pas eu de blessés mais ça aurait pu.\nÇa donne une image particulièrement mauvaise pour le Santa Fe étant donné qu’il y a déjà eu des problèmes du même type récemment.\nLe membre en question a préféré reporter l’incident aux inspecteurs pour respecter les règles et rester dans une forme d’intégrité.\nMalgré les potentiels problèmes, l’auteur pense que ce membre avait raison de reporter l’incident : les inspecteurs indépendants forment un corps indispensable pour la bonne santé de l’organisation.\nCoopérer avec les inspecteurs est un mécanisme permettant de donner du contrôle à l’ensemble de l’équipe vis-à-vis de leur propre destin.\nLes inspecteurs étant indépendants, ils offrent une garantie de pousser l’équipe à apprendre et s’améliorer, et tendre vers l’excellence technique.\nSur les aspects où l’équipe faisait quelque chose d’innovant, les inspecteurs pouvaient répandre ces pratiques ailleurs. Et sur les aspects où l’équipe faisait mal les choses, c’était une occasion de s’améliorer.\nQuestions à se poser :\nComment utiliser des groupes extérieurs d’audit, des commentaires de réseaux sociaux etc. pour améliorer notre organisation ?","iii---competence#III - Competence":"","16---mistakes-just-happen#16 - “Mistakes Just Happen!”":"Le management, la personne qui avait oublié d’enlever le balisage, et les inspecteurs se réunissent pour parler de la faute et de ce qu’il faut faire.\nLe membre qui avait commis la faute s’en explique avec honnêteté, et l’auteur décide de ne pas le punir.\nLe meeting se concentre sur le plus important : comment faire en sorte que ça ne se reproduise pas.\nLa solution trouvée est d’éviter les erreurs habituelles issues d’une inattention pendant des moments délicats : il s’agit de faire des actions délibérées, c’est-à-dire faire une pause, faire des gestes et utiliser la voix pour signifier ce qu’on va faire, et enfin le faire.\nL’équipage faisait deux erreurs à propos des actions délibérées :\nPenser qu’il s’agit de le faire pour que les supérieurs hiérarchiques le voient plutôt que simplement pour faire moins d’erreurs.\nPenser que c’est pour l’entraînement seulement, et qu’en situation réelle il faut agir plus vite.\nFaire des actions délibérées est un mécanisme qui permet la compétence, en permettant de faire significativement moins d’erreurs pendant les moments les plus importants.\nLes actions délibérées permettaient aussi à une personne à proximité de corriger la personne qui faisait l'action délibérée après qu’elle ait vocalisé qu’elle allait faire une action qui représentait une erreur.\nIl s’agit donc aussi d’un mécanisme permettant le travail d’équipe.\nQuestions à se poser :\nQue ferait-on si un employé admettait une erreur faite en mode “pilote automatique” ?\nEst-ce qu’en mettant en place le mécanisme des actions délibérées dans notre organisation, on baisserait significativement le nombre d’erreurs ?","17---we-learn#17 - “We Learn”":"Un nouvel incident est survenu avec une torpille mal fixée. Les officiers qui avaient fait la manœuvre manquaient en fait de compétence technique.\nMalgré l’incident, le commandant a assuré son soutien à l’auteur, ce qui lui a donné du courage pour continuer.\nL’auteur s’est fait la réflexion qu’avec l’habitude d’obéir en mode leader-follower, les personnes du bas de l’échelle avaient vu leurs compétences s’atrophier. Il fallait les revigorer.\nL’auteur a réuni l’équipage, et ils ont décidé d’un manifeste qui caractérise leur action.\nL’élément central de leur manifeste a été “we learn”, en toutes circonstances et dans chaque situation.\nLeur apprentissage se fait quotidiennement, et représente une posture active que chacun adopte.\nL’apprentissage se fait en faisant le travail : il s’agit de changer de perspective sur le travail qui est fait, et d’en apprendre le plus possible d’un point de vue technique, ou même apprendre à le déléguer.\nL’objectif est d’obtenir des employés plus qualifiés, qui pourront être plus autonomes pour décider, et donc plus motivés et épanouis par leur travail.\nCommuniquer cet objectif permet de largement les motiver à s’engager dans le processus.\nExercice à faire en meeting avec le leadership :\nOn donne des papiers, et on balance la phrase : “Notre entreprise serait plus efficace si tel niveau de management avait la main sur telles décisions”.\nLes gens écrivent, puis vont en pause et réfléchissent.\nOn sélectionne ensuite certains sujets parmi ceux proposés, et on demande “Qu'est-ce que les personnes de ce niveau de management auraient besoin de savoir faire pour pouvoir prendre ces décisions ?”.\nOn laisse les gens répondre et on va en pause pour réfléchir.\nOn a alors des choses concrètes à mettre en place pour augmenter la compétence, pour pouvoir augmenter l’autonomie de décision des subordonnés.\nNous apprenons, partout, tout le temps est un mécanisme qui permet la compétence.\nQuestions à se poser :\nQuelles domaines de notre entreprise sont en échec à cause de la faible compétence des employés ?\nComment mettre en place une politique d’apprentissage permanent dans notre entreprise ? Est-ce qu’écrire un manifeste serait envisageable ?","18---under-way-for-san-diego#18 - Under Way for San Diego":"L’auteur a l’idée de donner un objectif de temps sur la réduction de la submersion du sous-marin, ce qui permet à l’équipage de le prendre comme un défi, et de voir le processus comme un tout et non pas une somme d’étapes pour gagner du temps.\nPendant un exercice, l’auteur fait en sorte que certains composants tombent en panne pour voir comment l’équipage gère la situation, alors qu’il a été briefé.\nL’équipage a beaucoup de mal, et met du temps à régler le problème.\nEn fait l’équipage assiste passivement aux briefings et n’écoute pas vraiment.\nIls décident alors de **ne plus faire de briefings, et de les remplacer par des certifications **:\nOn pose des questions auxquelles l’équipage doit répondre pour vérifier s’il est prêt.\nIl s’agit aussi d’un moment de décision : si l’équipage n’est pas prêt, on décide de reporter l’activité.\nÇa a l’effet bénéfique de pousser l’équipage à étudier ce qu’il est censé savoir à l’avance, pour passer la certification. Il est donc bien plus actif que pendant un briefing.\n“Ne pas briefer, mais plutôt certifier” est un mécanisme qui augmente la compétence des employés.\nLes employés étudient par eux-mêmes, et deviennent chacun compétent, plutôt qu’une personne compétente fasse des piqûres de rappel de temps en temps aux autres.\nLa quantité de temps que les employés passent à apprendre par eux-mêmes est une métrique qu’une organisation en bonne santé voudra augmenter.\nQuestions à se poser :\nA quel point est-ce que les gens se préparent avant un événement ?\nQu'est-ce qu’il faudrait pour certifier nos équipes à propos du fait qu’il connaissent les objectifs collectifs et comment y contribuer ?","19---all-present-and-accounted-for#19 - All Present and Accounted For":"Un officier prévient l’auteur qu’un des officiers junior en charge de faire le guet est porté absent sans permission.\nLe top management se réunit pour discuter de son cas. Une partie d’entre eux pensent qu’il faut une punition exemplaire, alors que l’autre partie pense qu’il fait du bon travail, et qu’il a été trop surmené récemment et a eu besoin de dormir.\nEn creusant un peu plus, le junior qui n’avait pas pu suffisamment dormir devait faire le guet trop souvent parce que le chef ne voulait pas participer aux tours.\nL’auteur décide de ne pas punir le junior, mais met en place une règle : aucun chef ne pourra avoir un travail quotidien plus favorable que le plus difficile donné à ses subordonnés.\nEn y réfléchissant, l’auteur se dit que ce qu’il faut c’est répéter le message, encore et encore. Il a beau l’avoir déjà dit des centaines de fois, les chefs ne l’ont manifestement pas encore intégré.\nRépéter le message en continu encore et encore est un mécanisme qui permet d’augmenter la compétence des employés, pour les faire basculer petit à petit dans le système leader-leader.\nIl faut se dire qu’ils n’ont pas forcément connu le système leader-leader, et donc n’arrivent pas à se faire une image mentale de ce que ça peut bien représenter. En répétant, ça finit par rentrer.\nQuestions à se poser :\nQuel message faut-il répéter au middle management pour qu’il intègre qu’il faut d’abord qu’ils prennent soin de leurs subordonnés, avant de penser à eux-mêmes ?\nQuand est-ce qu’un leader peut outrepasser un protocole pour aider un subordonné en difficulté ?","20---final-preparations#20 - Final Preparations":"Les chefs de département préparent un exercice de déclenchement de feu, sachant qu’il faut en théorie l’arrêter en 2mn pour qu’il soit contenu.\nL’équipage échoue lamentablement en mettant beaucoup trop de temps, parce que les plus proches de l’extincteur ne sont pas ceux habilités à le manipuler. La procédure joue contre l’efficacité.\nUn autre problème est que le feu d’entraînement ne s’éteint que très difficilement, ce qui ne pousse pas l’équipage à s’organiser pour l’éteindre au plus vite.\nLa solution mise en place est de demander à l’équipage de faire au plus efficace pour que la personne la plus proche de l’extincteur l’utilise. On indique l’objectif, et ensuite l’équipage se débrouille pour faire au plus efficace.\nLe sous-marin doit rester le plus silencieux possible. Il y a donc un capteur qui vérifie qu’il n’y ait pas de bruit trop puissant, et prévient. Le chef doit alors appeler tous les membres pour savoir qui est à l’origine du bruit.\nL’un des officiers suggère de changer cette pratique, pour que ce soit les membres de l’équipage qui préviennent d’eux-mêmes à chaque fois qu’ils font un bruit trop important.\nFinalement, l’efficacité est bien plus grande puisque de nombreux bruits non captés par le capteur sont aussi rapportés, et l’équipage fait bien plus attention.\nSpécifier des objectifs plutôt que des méthodes est un mécanisme qui permet la compétence des subordonnés, et leur apporte de la clarté.\nIls vont alors se démener pour trouver de bonnes solutions et donc monter en compétence, et iront dans le bon sens puisque l’objectif est là.\nQuestions à se poser :\nLes procédures dans notre entreprise vont-elles à l’encontre de l’efficacité et de l’excellence technique ?\nEst-ce que nos employés s’occupent de suivre les procédures au détriment des objectifs collectifs.","iv---clarity#IV - Clarity":"","21---under-way-for-deployment#21 - Under Way for Deployment":"Le moment arrive enfin où le Santa Fe est prêt pour partir en mission, après presque 6 mois de préparation.\nLe management se réunit, et décide de définir des objectifs globaux autour de thèmes : empowerment, efficiency, and tactical excellence.\nL’auteur décide de répandre cette information de manière la plus large possible.\nIls décident aussi d’encourager les subordonnés à se définir des objectifs d’amélioration personnels.\nLes promotions se sont mal passées pour l’équipage du Santa Fe : il y en a eu très peu.\nL’auteur remarque que les subordonnés ne connaissent pas bien le fonctionnement du système de promotions. En fait, c'est l’examen qui fait la différence.\nIls décident donc d’aider les subordonnés par des formations, avec des examens internes d’entraînement, plus difficiles que les examens de promotion.\nDes mois plus tard, les résultats se révèlent bien meilleurs. Et ça continue les années suivantes.\nL’officier de plus haut rang après l’auteur avait besoin d’aller voir son père malade, et l’ingénieur avait besoin d’aller voir sa femme qui allait accoucher. L’auteur tenait absolument à ce que ces permissions soient accordées.\nLes permissions ont pu être accordées, parce que les compétences de l’ensemble de l’équipage avaient été améliorées grâce au système leader-leader.\nCréer de la confiance et prendre soin des subordonnés est un mécanisme qui améliore la clarté.\nIl ne s’agit pas de les protéger de leur propre comportement, ce qui mènerait à l’irresponsabilité, mais plutôt à leur donner les meilleurs outils et avantages possibles, y compris en dehors de leur travail.\nQuestions à se poser :\nEst-ce qu’en tant que leader on met tous les outils à disposition des subordonnés pour qu’ils réussissent leurs objectifs professionnels et personnels ?\nEst-ce qu’on protège les subordonnés des conséquences de leurs actes ?","22---a-remembrance-of-war#22 - A Remembrance of War":"En passant près d’un endroit où un ancien sous-marin a été coulé pendant la 2ème guerre mondiale, l’équipage commémore le moment.\nEn plus de ce genre d’action, ils lisaient aussi des citations de bataille, de médaille d’honneur etc. à chaque fois qu’un membre était promu.\nIls ont aussi visité un musée consacré aux sous-marins à Pearl Harbor.\nUtiliser l’héritage de l’organisation comme inspiration est un mécanisme qui permet la clarté.\nIl s’agit de se rappeler des moments fondateurs, par exemple des crises qui ont été surmontées, et de les utiliser comme sources d’inspiration.\nOn peut en parler de temps en temps, les inclure dans notre manifeste, dans des rapports etc.\nQuestions à se poser :\nQuel est l’héritage de notre organisation, et qu’est-ce qu’il révèle sur sa raison d’être ?\nComment raviver cet héritage aujourd’hui ?","23---leadership-at-every-level#23 - Leadership at Every Level":"L’auteur demande à l’équipage de se réunir et de chercher les principes qui devraient guider les décisions, pour rédiger un cadre directeur de valeurs.\nLe critère pour savoir si un principe est bon ou pas c’est de se demander si ce principe peut aider à prendre une décision.\nPour que ces principes soient vraiment incarnés, ils étaient par exemple utilisés pour les félicitations et évaluations, en disant que telle personne a fait preuve de telle et telle qualité issue des principes directeurs.\nUtiliser des principes directeurs pour prendre les décisions est un mécanisme de clarté pour les subordonnés.\nAttention par contre à ce que les principes soient alignés avec ce que l’organisation fait vraiment, sinon les décisions ne seront pas alignées, ou les principes ne seront pas utilisés.\nUn moyen simple de savoir si les principes sont alignés avec ce que l’organisation fait vraiment est de demander aux employés quels sont les principes selon lesquels ils fonctionnent.\nQuestions à se poser :\nA quelles occasions est-ce qu’on met nos principes directeurs en avant ?\nComment faire pour que les employés les connaissent, et les utilisent vraiment pour prendre des décisions ?","24---a-dangerous-passage#24 - A Dangerous Passage":"Le sous-marin devait traverser une zone étroite et peu profonde, où traversent des centaines de bateaux. Ils suivaient un grand bateau de près, quand ils ont failli percuter un autre bateau.\nUn des officiers a donné l’alerte courageusement, et a permis de faire éviter la collision.\nL’auteur a alors félicité l’officier immédiatement devant les autres, et a laissé la récompense formelle pour un peu plus tard.\nLa reconnaissance immédiate pour renforcer le comportement désiré est un mécanisme de clarté envers les subordonnés.\nIl faut par contre que l’objet de la reconnaissance soit par rapport à des objectifs vis-à-vis de l’extérieur plutôt que par rapport à une compétition avec les autres.\nSi la reconnaissance se fait par rapport à une compétition, il y a le risque qu’il y ait moins d’esprit d’équipe, et que les employés passent plus de temps que nécessaire sur une tâche particulière.\nPar exemple : féliciter les employés qui éteignent le feu en moins de 2 minutes, plutôt que féliciter les 10% des employés les plus rapides à éteindre le feu.\nQuestions à se poser :\nEst-ce qu’on a dans notre organisation un système pour féliciter immédiatement un employé ?","25---looking-ahead#25 - Looking Ahead":"Le Santa Fe avait fini par être prêt à partir en mission 2 semaines avant la date prévue, parce que l’auteur et le top management avaient planifié la chose bien en amont pour laisser 2 semaines de repos à l’équipage avant la mission de 6 mois.\nFinalement ils ont dû partir plus tôt, et ont vu leurs repos écourtés, mais ils étaient bien prêts 2 semaines avant.\nLe secret de leur réussite a été de commencer en ayant le but final en tête (finir 2 semaines plus tôt), et planifier le reste en fonction de ça.\nPour continuer sur la lancée de voir loin, l’auteur a mis en place une heure par jour de mentoring avec à chaque fois un des membres du top management, pour l’aider à établir ses objectifs personnels.\nL’auteur apprenant autant que la personne qu’il mentorait, donc il considère que c’était des sessions leader-leader.\nIl insiste sur le fait que les objectifs doivent être sur le temps long : une ou plusieurs années.\nIl a mis un accent particulier à rendre les objectifs mesurables :\nEn posant la question : comment sauras-tu que tu as atteint cet objectif ?\nEn mettant les outils en place pour collecter les données qui permettent de mesurer les progrès.\nCommencer en ayant le but final en tête est un mécanisme de clarté pour les subordonnés, parce qu’il permet de ne pas perdre l’objectif de vue.\nPour le faire, on peut :\nRelire ce chapitre, et le chapitre 2 de The 7 Habits of Highly Effective People.\nCréer des objectifs organisationnels pour les 3 à 5 ans avec les autres leaders.\nDemander à chaque fois “Comment est-ce qu’on le saura ?”, et ajouter les systèmes de mesure.\nDemander aux employés de se trouver des objectifs mesurables, en accord avec les objectifs de l’organisation.\nQuestions à se poser :\nA quel point est-ce qu’on pense loin dans le temps ?\nEst-ce qu’on saura si on a atteint nos objectifs personnels et celle de l’organisation ?\nComment trouver du temps pour que les subordonnés et le chef puissent se mentorer mutuellement ?","26---combat-effectiveness#26 - Combat Effectiveness":"L’équipage participait à un exercice, après la mission de 6 mois, où il s’agissait de permettre à un commando d’exécuter une mission à l’extérieur, puis de les récupérer dans le sous-marin.\nL’ensemble de la préparation s’est faite en mode leader-leader, l’auteur n’a pas eu besoin de donner de directives pour que chacun s’organise pour préparer de quoi prendre en charge d’éventuels membres du commando blessés.\nL’auteur pense qu’il faut reculer pour se positionner correctement, et en donne l’ordre d’un air un peu paniqué. Mais l’un des jeunes officiers lui dit qu’il a tort.\nS’ils avaient suivi l’ordre de l’auteur, ils auraient potentiellement raté le commando.\nL’auteur soupçonne qu’un tragique accident d’un autre sous-marin ait eu lieu parce que l’équipage n’avait pas osé aller contre les ordres du capitaine.\nAvoir une attitude de questionnement plutôt que d’obéissance aveugle est un mécanisme de clarté pour les subordonnés.\nQuestions à se poser :\nEst-ce que nos employés nous suivraient dans nos erreurs en tant que leader ?\nComment faire pour que ces erreurs ne soient pas propagées mais plutôt arrêtées, d’où qu’elles viennent ?"}},"/books/unit-testing":{"title":"Unit Testing: Principles, Practices, and Patterns","data":{"i---the-bigger-picture#I - The bigger picture":"","1---the-goal-of-unit-testing#1 - The goal of unit testing":"De nos jours, la plupart des entreprises créent des tests pour leurs logiciels.\nEn moyenne le ratio code de test / code de prod est entre 1/1 et 3/1 (en faveur du code de test), et parfois plus.\nLe but principal des unit tests c'est de permettre une croissance durable du projet. Sans eux, le temps de développement explose au bout d'un moment.\nOn appelle cette explosion la software entropy : la désorganisation progressive du code.\nLe fait qu'un code soit difficilement testable est un signe de mauvaise conception à cause d'un couplage inapproprié. C'est un bon indicateur négatif. Par contre, si le code est testable, ça ne veut pas dire qu'il est bon, on ne peut pas en faire un indicateur positif.\nLes tests sont un code comme un autre, ils ont un coût de maintenance, et peuvent avoir une valeur nulle ou même négative. Il vaut mieux ne garder que les bons tests.\nLe code coverage est un bon indicateur négatif : si le code coverage est faible c'est que le code est peu testé. Par contre, si le coverage est élevé, on ne peut rien conclure : c'est un mauvais indicateur positif.\nLe problème du coverage c'est :\nQu'on ne peut pas s'assurer qu'on vérifie tout ce qui est fait. Par exemple, si un code renvoie un résultat et assigne ce résultat dans une variable globale, et que le test vérifie seulement l'une de ces choses, on ne pourra pas savoir que l'autre n'est pas testée malgré le coverage de 100%.\nQu'on ne teste pas les chemins permis par nos dépendances. On délègue souvent des responsabilités à des dépendances qui permettent beaucoup de flexibilité, mais sans tester chaque possibilité offerte. Et on ne peut pas vérifier qu'on le fait.\nLe simple fait de faire un parseInt(variable) fera que variable marchera dans des cas précis supportés par la fonction standard parseInt(). Pour autant, on ne peut pas s'assurer de tester chacun de ces chemins et leurs conséquences avec notre code.\nSe fixer le coverage comme target crée un incentive pervers qui va à l'encontre de l'objectif du unit testing. Le coverage doit rester un indicateur (négatif).\nLe branch coverage est une autre forme de coverage qui compte le nombre d'embranchements (if, switch etc.) testés sur le nombre total d'embranchements. C'est un peu mieux que le code coverage, mais ça reste pour autant seulement un indicateur négatif.\nUn bon test c'est un test qui :\nest intégré au cycle de développement, exécuté le plus souvent possible.\nteste les parties les plus importantes de la codebase. En général c'est la logique business.\noffre une grande valeur comparé aux coûts de sa maintenance.","2---what-is-a-unit-test#2 - What is a unit test?":"Il existe deux écoles de unit testing : la classical school (qu'on pratiquait à l'origine), et la London school, qui est née à Londres.\nUn livre canonique pour le style classique est Test-Driven Development: By Example de Kent Beck, et un livre pour le style London est Growing Object -Oriented Software, Guided by Tests de Steve Freeman et Nat Pryce.\nUn unit test est un test qui vérifie une unité de code, de manière rapide, et isolée.\nLe code testé peut avoir des dépendances.\nLes shared dependencies sont celles qui affectent les tests entre eux parce qu'ils sont changés par le code et ne sont pas réinitialisés entre les tests. Par exemple, une base de données est shared. Elle pourrait ne pas l'être si elle était instanciée à chaque test.\nLes private dependencies sont celles qui ne sont pas partagées.\nLes out-of-process dependencies sont celles qui sont exécutées dans un autre processus. Elles impliquent un temps d'exécution plus important que de rester dans le même processus que le code exécuté. La base de données est out-of-process, même si on l'instancie à chaque fois.\nLes volatile dependencies sont soit non installées sur un environnement par défaut (c'est le cas d'une base de données, mais pas d'un filesystem par exemple), soit on un comportement non déterministe (par exemple new Date()).\nA propos de la gestion de dépendances, l'auteur conseille Dependency Injection: Principles, Practices, Patterns de Steven Deursen et Mark Seemann.\nOn appellera par la suite\ncollaborators les dépendances qui sont soit shared, soit mutables (un objet utilisé par l'objet qu'on teste, la base de données etc.)\nvalues les dépendances qui sont immutables (par exemple un Value Object, le nombre 5 etc.).\nLa controverse entre les deux écoles porte sur l'isolation :\nPour la London school l'isolation porte sur le code testé.\nTout collaborator qui n'est pas directement testé doit être remplacé dans les tests par un test double (c'est le terme générique, le terme mock est une forme particulière de test double). On tolère seulement les dépendances immutables (les values).\nLe “unit” c'est l'unité de code (la classe), donc on a un fichier de test par classe.\nAvantages :\nÇa permet de tester du code même très couplé, en remplaçant simplement les dépendances dans les tests par des doubles.\nÇa permet d'être sûr que seul un test ne marchera plus si une fonctionnalité ne marche plus.\nInconvénients :\nÇa ne force pas à faire du code découplé.\nLes tests cassent facilement au moindre refactoring.\nPour la classical school l'isolation porte sur les tests entre eux : il faut pouvoir les jouer en parallèle sans qu'ils s'affectent mutuellement.\nOn n'utilise les doubles que très peu, seulement pour éliminer les shared dependencies.\nLe “unit” c'est le comportement (la fonctionnalité), et celle-ci peut contenir plusieurs classes qui seront toutes instanciées indirectement dans les tests.\nAvantages :\nCa force à faire du code découplé.\nCela permet d'avoir des tests qui collent mieux au cas d'usage business, et qui sont moins fragiles aux refactorings.\nInconvénients :\nSi une fonctionnalité ne marche pas, plusieurs tests peuvent casser.\nMais si on rejoue tous les tests à chaque changement de code, on peut savoir que c'est lui qui vient de faire passer les tests au rouge.\nEt en plus si un changement casse beaucoup de tests, ça permet de savoir que cette partie du code est très importante.\nLes deux écoles ont aussi une différence dans leur rapport au TDD :\nLa London school va avoir tendance à faire du outside-in TDD, en construisant d'abord les classes de plus haut niveau utilisant des collaborators sous forme de test doubles. Puis les implémenter petit à petit en allant vers le détail.\nLa classical school va plutôt mener à du inside-out TDD, en partant des classes les plus bas niveau dans le modèle, pour construire par-dessus jusqu'aux couches supérieures.\nUn test d'intégration est un test qui ne répond pas à une des 3 caractéristiques du test unitaire (tester une unité, de manière rapide et isolée).\nPour la London school, les caractéristiques sont :\nVérifier le comportement d'une seule classe.\nLe faire vite.\nLe faire en isolation vis-à-vis des dépendances de cette classe (grâce aux doubles).\nPour la classical school :\nVérifier le comportement d'une unité de comportement.\nLe faire vite.\nLe faire avec des tests isolés les uns par rapport aux autres.\nDu coup pour savoir ce qui est test d'intégration :\nLa plupart des tests unitaires selon la classical school sont des tests d'intégration pour la London school puisqu'ils font intervenir plusieurs classes.\nUn test qui teste plusieurs unités de comportement sera un test d'intégration pour la classical school.\nDans le cas où on a une out-of-process dependency (comme une DB) impliquée, les tests sont lents donc on sera sur des tests d'intégration pour les deux écoles.\nSi on a une shared dependency (comme une DB) impliquée, là encore on aura un test d'intégration pour les deux écoles.\nUn test end-to-end est un test d'intégration qui teste toutes les dépendances out-of-process (ou la plupart d'entre elles), là où les autres tests d'intégration n'en testent qu'une ou deux (genre juste la DB, mais pas RabbitMQ ou le provider d'emails).\nDans la suite du livre l'auteur va plutôt adopter l'approche classique, parce que c'est celle qu'il préfère et celle qui est la plus courante.","3---the-anatomy-of-a-unit-test#3 - The anatomy of a unit test":"Les tests unitaires doivent être structurés avec les 3 blocs Arrange, Act, Assert (qu'on appelle aussi Given, When , Then) :\nArrange : Il peut être aussi gros que les deux autres sections réunies. S'il est plus gros, il est conseillé de l'extraire dans une fonction pour augmenter la lisibilité.\nAvoir une méthode unique (constructeur de la classe de tests, ou beforeAll / BeforeEach global) est une moins bonne idée puisqu'on couple les tests ensemble, et que ce que possède chaque test est moins clair.\nL'idéal c'est avoir des fonctions de type factory configurables, qu'on peut réutiliser dans les tests en sachant depuis le test à peu près ce qu'on crée.\nAct : Il ne devrait faire qu'une ligne vu qu'on est censé vérifier une unité de comportement. S'il fait plus, c'est qu'on a la possibilité de faire une partie de la chose et pas l'autre, ce qui peut vouloir dire qu'on est en état de casser un invariant, et donc qu'on a une mauvaise encapsulation de notre code.\nExemple : si notre act c'est deux lignes qui font :\ncustomer.purchase(item);\nstore.removeFromInventory(item);\nC'est que l'un peut être fait sans l'autre au niveau de l'interface publique (celle-là même qui est testée). C'est un danger qu'on s'impose pour rien. Une seule méthode publique devrait faire les deux.\nAssert : Vu qu'on vérifie une unité de comportement, il peut y avoir plusieurs outcomes, donc plusieurs asserts.\nAttention quand même, si cette section grossit trop, c'est peut être le signe d'une mauvaise abstraction du code. Par exemple, si on doit comparer toutes les propriétés d'un objet un par un, et qu'on aurait pu implémenter l'opérateur d'égalité sur l'objet en question, et ne faire qu'un assert.\nOn écrit en général d'abord la partie arrange si on a déjà écrit le code, et d'abord la partie assert si on fait du TDD.\nQuand on teste plusieurs unités de comportement, on se retrouve par définition avec un test d'intégration. Il vaut mieux revenir sur de l'unitaire si possible.\nIl faut éviter les if dans les tests. Ça complique la compréhension et la maintenance.\nPour repérer facilement l'objet qu'on teste, et ne pas le confondre avec des dépendances, l'auteur conseille d'appeler l'objet testé sut (pour System Under Test) :\n// Arrange\nconst sut = new Calculator();\n// Act\nconst result = sut.sum(3, 2);\n// Assert\nexpect(result).toBe(5);\nPour bien séparer les 3 sections AAA l'une de l'autre, l'auteur conseille :\nSoit de laisser une ligne entre chaque section.\nSoit, si certaines sections doivent déjà sauter des lignes parce qu'elles sont longues, de laisser en commentaire //Arrange, //Act et //Assert\nA propos de la manière de nommer les tests :\nVu que les tests testent un comportement, le nom des tests doit être une phrase, qui a du sens pour les experts métier.\nSauf dans le cas où on teste des fonctions utilitaires, qui n'ont donc pas de sens pour les experts métier.\nIl faut éviter de mettre le nom du SUT (la fonction testée) dans le nom du test. Ça oblige à changer le nom du test si le nom de la fonction change, et ça n'apporte pas grand chose puisque c'est le comportement qui nous intéresse.\nIl vaut mieux être spécifique dans le nom du test. Par exemple, si on teste qu'une date est invalide si elle est au passé, préciser ça plutôt que de rester vague en parlant simplement de vérifier si la date est valide.\nOn peut utiliser les tests paramétrisés pour grouper des tests dont seule une valeur d'entrée et la valeur attendue change. Par exemple tester avec la date d'aujourd'hui, avec la date de demain, etc.\nAttention quand même, faire ce genre de regroupement a un coût en lisibilité. Donc à faire que si les tests sont simples.\nIl faut éviter de mettre dans le même test les cas positifs et les cas négatifs.\nEn général, le framework de test fournit la possibilité de paramétriser les tests, en acceptant une liste de paramètres à faire varier en entrée du test.","ii---making-your-tests-work-for-you#II - Making your tests work for you":"","4---the-four-pillars-of-a-good-unit-test#4 - The four pillars of a good unit test":"Dans ce chapitre on pose des critères pour évaluer la qualité d'un test.\nUn bon test a 4 caractéristiques fondamentales :\n1- Protéger des régressions : éviter les bugs.\nPour évaluer ce point on peut prendre en compte :\nLa quantité de code exécutée : plus il y en a plus c'est fiable.\nLa complexité du code exécuté.\nL'importance du code : tester du code du domaine est plus utile que de tester du code utilitaire.\n2- Résister aux refactorings : à quel point on peut refactorer sans casser le test.\nPour évaluer ce critère, on peut regarder si le test produit souvent des faux positifs pendant les refactorings.\nL'intérêt de ce critère c'est que si on a trop de faux positifs :\nOn porte de moins en moins attention au résultat des tests puisqu'ils disent souvent n'importe quoi : et on laisse passer de vrais bugs.\nOn n'ose plus refactorer le code, puisqu'on n'a pas confiance dans les tests. Et le code pourrit.\nCe qui fait casser les tests pendant les refactorings c'est souvent le couplage aux détails d'implémentation, au lieu de porter sur un comportement attendu du point de vue métier.\nPour avoir une idée de ce que veut dire “tester les détails d'implémentation”, l'exemple le plus extrême de ce genre serait un test qui vérifierait simplement que le code source de la fonction testée est bien le code source attendu dans le test. Ce test casserait littéralement à chaque changement.\nSans aller jusqu'à cet extrême, on retrouve souvent des tests qui vérifient la structure interne d'un objet, ou qu'une fonction appelle telle ou telle autre fonction etc. sans que ça n'ait aucun intérêt d'un point de vue métier.\n3- Donner un feedback rapide : à quel point on peut exécuter le test vite.\nPlus le test est lent, moins souvent on l'exécutera.\n4- Être maintenable : il y a deux composantes :\nA quel point c'est difficile de comprendre le test. Ça dépend de la taille du test, de la lisibilité de son code.\nA quel point c'est difficile de lancer le test. Par exemple à cause de la database qui doit être en train de tourner etc.\nLes deux premiers piliers caractérisent la précision (accuracy) du test.\nLa protection contre les régressions dépend de la capacité à ne pas avoir de faux négatifs (bugs présents mais ratés par les tests). C'est le fait d'avoir le bon signal.\nLa résistance aux refactorings dépend de la capacité à ne pas avoir de faux positifs (fausses alarmes). C'est l'absence de bruit.\nAu début du projet, les faux positifs (les bugs pas couverts) ont la plus grande importance. Mais à mesure que le projet avance, les faux négatifs deviennent de plus en plus gênants et empêchent de garder le code sain en le refactorant.\nDonc si on est sur un projet moyen ou gros, il faut porter une attention égale aux faux positifs et aux faux négatifs.\nOn peut noter un test sur chacun des 4 critères, et lui donner une note finale qui nous aidera à décider si on le garde ou non (pour rappel : garder un test n'est pas gratuit, ça implique de la maintenance).\nOn peut évaluer (subjectivement) la valeur du test à chacun des 4 critères, entre 0 et 1, puis multiplier ces quatre valeurs pour avoir le résultat final.\nCa implique donc qu'un test qui vaut zéro à l'un des critères aura une valeur finale de zéro. On ne peut pas négliger un des critères.\nOn ne peut malheureusement pas obtenir la note maximale partout, parce que les 3 premiers critères ont un caractère exclusif entre eux : on ne peut en avoir que deux parfaits.\nLes tests end to end, par exemple, maximisent la protection vis-à-vis des régressions parce qu'ils exécutent beaucoup de code, et sont résistants aux refactorings vu qu'ils testent depuis ce que voit l'utilisateur final. Par contre ils sont très lents.\nEt si on a des tests très rapides, en général on n'obtiendra pas à la fois un découplage et donc une résistance aux refactorings, et en même temps une capacité à arrêter tous les bugs.\nLa règle à retenir c'est que la résistance aux refactorings est non-négociable, pour la raison que ce critère est assez binaire : soit on est bien découplé, soit non. Et si on ne l'est pas, la valeur du test passe à zéro.\nLe choix qui reste c'est donc la possibilité de faire varier le curseur entre la rapidité du test, et sa capacité à empêcher les régressions.\nSi on examine notre pyramide de tests (unit, integration, e2e), on maximisera d'abord le critère non-négociable de résistance aux refactorings pour tous, puis :\nLes tests unitaires sont les plus rapides et protègent le moins, puis on a les tests d'intégration qui sont au milieu, et les tests e2e sont très lents et protègent le plus.\nEn général on a peu de tests e2e parce que leur extrême lenteur diminue beaucoup leur valeur. Et ils sont aussi difficiles à maintenir.\nPour les projets classiques on aura une pyramide, et pour les projets très simples (CRUD etc.), on pourra se retrouver avec un rectangle.\nLe black-box testing consiste à tester sans prendre en compte la structure interne, seulement avec les considérations business. Le white-box testing consiste à faire l'inverse.\nLe white-box testing menant à du code couplé aux détails d'implémentations, il n'est pas résistant aux refactorings, donc il ne faut pas l'utiliser (sauf pour analyser).","5---mocks-and-test-fragility#5 - Mocks and test fragility":"Il y a principalement deux types de test doubles :\n1- Les mocks qui aident à émuler et examiner les interactions sortantes, c'est-à-dire le cas où le SUT interagit pour changer l'état d'une de ses dépendances.\nOn pourrait voir le mock comme la commande du pattern CQS.\nIl existe une petite distinction avec les spies qui sont des mocks écrits à la main, alors que les mocks sont en général générés par une librairie de mock.\n2- Les stubs qui aident à émuler les interactions entrantes, c'est-à-dire le cas où une des dépendances fournit une valeur utilisée par le SUT.\nOn pourrait voir le stub comme la query du pattern CQS.\nIl existe des sous ensemble de stubs :\nle dummy qui est très simple\nle stub qui est plus sophistiqué, et retourne la bonne valeur en fonction du cas\net le fake qui est un stub utilisé pour remplacer un composant qui n'existe pas encore (typique de l'école de Londres).\nLe mot mock peut vouloir dire plusieurs choses, ici on l'utilise pour sa définition principale de sous ensemble de test double, mais parfois il est utilisé pour désigner tous les tests doubles, et parfois il désigne l'outil (la librairie qui permet de créer des mocks et des stubs).\nVérifier les interactions sur des stubs est un antipattern : les stubs émulent des données entrantes, et donc vérifier que le stub a bien été appelé relève du couplage à des détails d'implémentation.\nLes interactions ne doivent être vérifiées que sur des mocks, c'est-à-dire des interactions sortantes, dans le cas où l'appel qu'on vérifie a du sens d'un point de vue business.\nLa distinction entre comportement observable et détail d'implémentation :\nIl faut d'abord choisir le client qu'on considère, puis vérifier si notre code lui permet :\nSoit d'exécuter une opération pour faire un calcul ou un side effect pour atteindre ses objectifs.\nSoit d'utiliser un état pour atteindre ses objectifs.\nSi oui, alors on a un comportement observable, si non alors notre code est un détail d'implémentation.\nLe choix du client considéré est important, on reviendra sur cet aspect dans la suite.\nSi l'API publique coïncide avec le comportement observable, alors on dira que notre système est bien conçu.\nSinon, on dira qu'il fait fuiter des détails d'implémentation. Parce que des détails d'implémentation pourront alors être accédés de manière publique sans protection (sans encapsulation).\nExemple : Le cas où le renommage de l'utilisateur se faisait en deux temps : renommer, puis appeler la fonction de normalisation qui coupe le nom à 50 caractères max. Ici la fonction de normalisation ne permet d'atteindre aucun objectif du client qui l'appelle (il voulait juste renommer), pourtant elle est publique. On a donc un problème de fuite.\nUn bon moyen de savoir si on fait fuiter des détails d'implémentation, c'est de voir les cas où on a besoin de plus d'une opération pour atteindre un objectif du client (le “act” du test).\nL'architecture hexagonale consiste en plusieurs hexagones communiquant entre eux.\nChaque hexagone est constitué de deux couches :\nLe domain layer qui n'a accès qu'à lui-même et qui contient les règles et invariants business de l'application.\nIl est une collection de domain knowledge (how-to).\nL'application service layer qui orchestre la communication entre le domain layer et le monde externe. Elle instancie des classes importées du domain layer, leur donne les données qu'elle va chercher en base, les sauve à nouveau en base, répond au client etc.\nElle est est une collection de use-cases business (what-to).\nLe terme hexagone est une image, chaque face représente une connexion à un autre hexagone, mais le nombre n'a pas besoin d'être 6.\nAu sein de chaque couche, le client est la couche d'au-dessus, et donc ce sont ses objectifs qui sont pris en compte pour savoir si on lui expose des détails d'implémentation ou non.\nLes objectifs du client final sont transcrits en objectifs secondaires dans la couche du dessous, et donc on a une relation fractale qui permet à tous les tests d'avoir toujours un rapport avec un requirement business (Les objectifs de l'application service layer sont des sous-objectifs du client final).\nNDLR : un peu comme les OKR.\nExemple :\n// domain layer\nclass User {\n setName(newName: string) {\n // On normalise et on set la valeur.\n }\n}\n// application service layer\nclass UserController {\n renameUser(userId: number, newName: string) {\n const user: User = getUserFromDatabase(userId);\n user.setName(newName);\n saveUserToDatabase(user);\n }\n}\nPour savoir quand utiliser les mocks sans abimer la résistance au refactoring, il faut se demander si l'interaction sortante qu'on veut vérifier est interne à notre application (notre hexagone par exemple), ou porte vers des systèmes externes.\nSi l'interaction est interne, alors il ne faut pas mocker, même s'il s'agit d'une dépendance out-of-process comme une base de données. Tant qu'elle n'est visible que depuis notre application, elle est un détail d'implémentation pour nos clients.\nSi l'interaction est externe, et donc visible par nos clients externes, alors il faut vérifier qu'elle se fait correctement par un mock. Par exemple, l'envoi d'un email répond à un besoin client, donc il faut vérifier que l'appel vers le système externe se fait correctement.\nPour parler un peu des écoles : l'école de Londres préconise de mocker toutes les dépendances mutables, ça fait beaucoup trop de mocks. Mais l'école classique préconise de mocker aussi des choses en trop : typiquement la base de données qui est une shared dependency. On peut au lieu de mocker nos interactions avec elle, la remplacer intelligemment par autre choses dans nos tests (cf. les deux prochains chapitres).","6---styles-of-unit-testing#6 - Styles of unit testing":"Il y a 3 “styles” de tests :\nOutput-based : c'est quand il n'y a pas de side effect, et qu'on teste une fonction qui prend des paramètres, et renvoie quelque chose. Il s'agit de fonction pure, donc de programmation fonctionnelle.\nState-based : on fait une opération, et on vérifie l'état d'un objet.\nCommunication-based : on utilise des mocks pour vérifier qu'un appel à une fonction a été fait avec les bons paramètres.\nA propos des écoles de test :\nL'école classique préfère le state-based plutôt que la communication based.\nL'école de Londres fait le choix inverse.\nEt toutes les deux utilisent l'output-based testing quand c'est possible.\nOn peut comparer les 3 styles de test vis-à-vis des 4 critères d'un bon test :\nPour la protection contre les régressions et la rapidité de feedback les 3 styles se valent à peu près.\nConcernant la résistance au refactoring :\nL'output-based testing offre la meilleure résistance parce que la fonction se suffit à elle-même.\nLe state-based testing est un peu moins résistant parce que l'API publique exposée est plus importante, et donc les chances de faire fuiter des détails d'implémentation dans la partie publique sont plus grandes.\nLe communication-based testing est le plus fragile, et nécessite une grande rigueur pour ne pas coupler à des détails d'implémentation.\nConcernant la maintenabilité : c'est à nouveau l'output-based qui est le plus maintenable parce que prenant le moins de place, suivi du state-based, et enfin du communication-based qui prend beaucoup de place avec ses mocks et stubs.\nGlobalement l'output-based testing est le meilleur, mais il nécessite d'avoir du code écrit de manière fonctionnelle.\nA propos de la programmation fonctionnelle, l'auteur conseille les livres de Scott Wlaschin.\nPour pouvoir faire de l'output-based testing, il faut écrire du code avec des fonctions pures, c'est-à-dire qui renvoient le même résultat à chaque fois qu'on donne les mêmes paramètres, sans qu'il n'y ait d'inputs ou d'outputs cachés.\nParmi ces choses cachées, on a :\nLes side-effects : des outputs cachés, par exemple la modification d'un état d'une classe, l'écriture dans un fichier etc.\nLes exceptions : elles créent un chemin alternatif à celui de la fonction, et peuvent être traitées n'importe où dans la stack d'appel.\nLa référence à un état interne ou externe : un input caché qui va permettre de récupérer une valeur qui n'est pas indiquée dans la signature de la fonction.\nPour savoir si on a une fonction pure, on peut essayer de remplacer son appel par la valeur qu'elle devrait renvoyer, et vérifier que le programme ne change pas de comportement. Si oui on a une referential transparency.\nL'architecture fonctionnelle consiste à maximiser la quantité de code écrite de manière fonctionnelle (fonctions pures, avec valeurs immutables), et confiner le code qui fait les side-effects à un endroit bien précis.\n1- Il y a le code qui prend les décisions, qui est sous forme de fonctions pures. C'est le functional core.\n2- Et le code qui agit suite aux décisions, qui prend les inputs et crée les side-effects (UI, DB, message bus etc.). C'est le mutable shell.\nOn va couvrir le functional core par de nombreux tests unitaires output-based, et couvrir le mutable shell qui est la couche d'au-dessus par des tests d'intégration moins nombreux.\nL'architecture fonctionnelle est en fait un cas particulier de l'architecture hexagonale :\nLes deux ont bien deux couches organisées par inversion de dépendance.\nLa différence principale c'est que l'architecture fonctionnelle exclut tout side-effect du functional core vers le mutable shell, alors que l'architecture hexagonale permet les side-effects dans la couche domaine tant que ça n'agit pas au-delà de cette couche (DB par exemple).\nExemple d'application peu testable, refactorée vers la functional architecture :\nDescription :\nOn a un système d'audit qui enregistre tous les visiteurs d'une organisation.\nLe nom de chaque visiteur et la date sont ajoutés à un fichier de log.\nQuand le nombre de lignes max du fichier est atteint, on écrit dans un autre fichier.\nInitialement la classe AuditManager a une méthode addRecord() qui va lire les fichiers existants, les classer pour trouver le dernier. Puis vérifier s'il est plein pour soit écrire dedans, soit écrire dans dans un nouveau.\nLa logique et la lecture/écriture sont dans la même fonction. Donc les tests vont être à la fois lents, et difficiles à paralléliser à cause de la dépendance out-of-process partagée qu'est le filesystem.\npublic class AuditManager {\nconstructor(\n public maxEntriesPerFile: number,\n public directoryName: string\n ) {}\naddRecord(visitorName: string, timeOfVisit: Date) {\n // Get all files in the given directory\n const fs = require('fs');\n const files = fs.readdirSync(directoryName);\n // Build the record content\n // If no file, create one with our record\n fs.writeFile(...\n // Sort by file name to get the last one\n // If file's lines do no exceed max, write inside\n // Otherwise create a new file and write inside\n}\n}\nUne 1ère étape est d'utiliser des mocks pour découpler le filesystem de la logique :\nUne des manières de faire ça c'est d'injecter un objet qui respecte une interface IFileSystem, qui sera soit le vrai filesystem, soit un mock dans les tests.\nLe mock va à la fois servir de stub pour renvoyer le contenu des fichiers, et aussi de mock pour vérifier qu'on appelle bien la bonne fonction avec les bons paramètres pour écrire dans le filesystem. L'usage du mock ici est légitime parce que ces fichiers sont user-facing.\npublic class AuditManager {\nconstructor(\n public maxEntriesPerFile: number,\n public directoryName: string,\n public fileSystem: IFileSystem,\n ) {}\naddRecord(visitorName: string, timeOfVisit: Date) {\n const files = fileSystem.readdirSync(directoryName);\n // Build the record content\n // If no file, create one with our record\n fileSystem.writeFile(...\n // Sort by file name to get the last one\n // If file's lines do no exceed max, write inside\n // Otherwise create a new file and write inside\n}\n}\nit(\"creates a new file when the current file overflows\", () => {\n const fileSystemMock: IFileSystem = {\n readdirSync: () => [\"audits/audit_1.txt\", \"audits/audit_2.txt\"],\n writeFile: jest.fn(),\n // ...\n };\n const sut = AuditManager(3, \"audits\", fileSystemMock);\n sut.addRecord(\"Alice\", new Date(\"2019-04-06\"));\n expect(fileSystemMock.writeFile).toHaveBeenCalledTimes(1);\n expect(fileSystemMock.writeFile).toHaveBeenCalledWith(\n \"audits/audit_3.txt\",\n \"Alice; 2019-04-06\"\n );\n});\nOn n'a rien changé à la protection contre les régressions et à la résistance aux refactorings. Par contre on a rendu les tests plus rapides, et on a un peu amélioré la maintenabilité parce qu'on n'a plus à se préoccuper du filesystem. Mais le setup des mocks est verbeux, on peut faire mieux sur la maintenabilité.\nLa 2ème étape est de refactorer vers la functional architecture :\nAuditManager ne connaît plus du tout l'existence du filesystem : il reçoit des valeurs en entrée (une liste de FileContent à partir duquel il lira le contenu des fichiers), et renvoie des valeurs en sortie : une liste de FileUpdate qui contiendront les contenus à changer).\nclass AuditManager {\n constructor(public maxEntriesPerFile: number) {}\n addRecord(\n files: FileContent[],\n visitorName: string,\n timeOfVisit: Date\n ) {\n // Build the record content\n // If no file, create one with our record\n if (files.length === 0) {\n return new FileUpdate(\"audit_1.txt\", newRecord);\n }\n // Sort by file name to get the last one\n // If file's lines do no exceed max, write inside\n // Otherwise create a new file and write inside\n }\n}\nclass FileContent {\n constructor(public fileName: string, public lines: string[]) {}\n}\nclass FileUpdate {\n constructor(public fileName: string, public newContent: string) {}\n}\nOn a une classe Persister qui va permettre de lire tous les fichiers, et de renvoyer leurs informations sous forme de FileContent, et une autre méthode pour prendre une liste de FileUpdate, et les appliquer sur le filesystem. Il doit être le plus simple possible pour que le max de logique soit dans AuditManager.\nconst fs = require(\"fs\");\nclass Persister {\n readDirectory(directoryName: string): FileContent[] {\n return fs.readdirSync(directoryName).map((file) => {\n return new FileContent(file.name, file.lines);\n });\n }\n applyUpdate(filePath: string, update: FileUpdate) {\n fs.writeFile(filePath, update);\n }\n}\nPour faire fonctionner ensemble le functional core (AuditManager), et le mutable shell (Persister), on a besoin d'une autre classe de type “application service” (pour utiliser la terminologie de l'hexagonal architecture).\nIl va manipuler manipuler Persister pour obtenir les données des fichiers, les donner à une instance d'AuditManager, puis appeler la méthode de calcul sur AuditManager, récupérer les commandes d'écriture en sortie, et les donner à Persister pour mettre à jour le filesystem.\nclass ApplicationService {\n constructor(public directoryName: string, maxEntriesPerFile: number) {\n this.auditManager = new AuditManager(maxEntriesPerFIle);\n this.persister = new Persister();\n }\n addRecord(visitorName: string, timeOfVisit: Date) {\n const files: FileContent[] = this.persister.readDirectory(\n this.directoryName\n );\n const update: FileUpdate = this.auditManager.addRecord(\n files,\n visitorName,\n timeOfVisit\n );\n this.persister.applyUpdate(this.directoryName, update);\n }\n}\nOn a gardé les précédents avantages, et on a amélioré la maintenabilité en éliminant le setup de mocks verbeux, remplacés par la simple instanciation de valeurs mis dans les objets FileContent et FileUpdate.\nit(\"creates a new file when the current file overflows\", () => {\n const sut = new AuditManager(3);\n const files = [\n new FileContent(\"audits/audit_1.txt\", []),\n new FileContent(\"audits/audit_1.txt\", [\n \"Peter; 2019-04-06\",\n \"Jane; 2019-04-06\",\n \"Jack; 2019-04-06\",\n ]),\n ];\n const update = sut.addRecord(files, \"Alice\", new Date(\"2019-04-06\"));\n expect(update.fileName).toBe(\"audit_3.txt\");\n expect(update.newContent).toBe(\"Alice; 2019-04-06\");\n});\nPour rester sur du fonctionnel, on peut renvoyer les erreurs par valeur de retour, et décider de quoi en faire dans l'application service.\nLa functional architecture n'est pas toujours applicable.\nElle permet d'avoir des avantages en termes de maintenabilité du code et des tests, mais elle a des désavantages :\nLe code pourra être un peu plus gros pour permettre la séparation entre logique et side effects.\nLe code pourra souffrir de problèmes de performance.\nDans notre cas, ça a marché parce qu'on lisait tous les fichiers avant d'appeler la logique en donnant tous ces contenus et la laissant décider. Si on avait voulu n'en lire que certains en fonction de paramètres décidés par la logique, on n'aurait pas pu la garder comme fonction pure.\nUne autre solution aurait pu être de concéder un peu de centralisation de la logique dans le core en faveur de la performance, en laissant la décision de charger les données ou non à l'application service.\nIl faut donc appliquer la functional architecture stratégiquement.\nNe pas sacrifier la performance si elle est importante dans le projet.\nL'appliquer si le projet est censé durer dans le long terme, et que l'investissement initial de séparer en vaut la peine.\nEn général (surtout si on fait de l'OOP), on aura une combinaison de tests state-based et output-based, et quelques tests communication-based.\nLe conseil ici c'est de privilégier les tests output-based quand c'est raisonnablement possible.","7---refactoring-toward-valuable-unit-tests#7 - Refactoring toward valuable unit tests":"Les tests et le code sont profondément liés, il est impossible d'obtenir de bons tests avec du mauvais code.\nOn va catégoriser le code en 4 catégories, en fonction de 2 axes :\nL'axe de complexité ou d'importance vis-à-vis du domaine.\nLa complexité cyclomatique est définie par le nombre de branches possibles dans le code : 1 + le nombre de branches.\nLe calcul tient compte du nombre de prédicats dans les conditions : si notre if vérifie 2 prédicats, ça ajoute 2 points.\nL'importance vis-à-vis du domaine c'est la connexion du code avec le besoin de l'utilisateur final. Du code utilitaire ne rentrera pas là-dedans.\nC'est la complexité ou l'importance domaine. Un code signifiant du point de vue du domaine mais simple rentre dans la description.\nL'axe du nombre de collaborators impliqués.\nPour rappel un collaborator est une dépendance, qui est soit mutable (autre chose que des valeurs primitives et des value objects), soit out-of-process.\nLes 4 catégories de code sont :\nDomain models and algorithms : grande valeur sur l'axe de complexité, faible valeur sur l'axe des collaborators.\nC'est eux qu'il faut le plus tester, à la fois parce qu'ils sont faciles à tester et parce qu'on obtiendra une grande résistance aux régressions. C'est d'eux qu'on obtient le meilleur “retour sur investissement” de nos tests.\nTrivial code : faible valeur sur les deux axes.\nC'est du code simple, qui ne mérite pas de tests.\nControllers : faible valeur sur l'axe de complexité, grande valeur sur l'axe des collaborators.\nIl s'agit de code pas complexe mais qui coordonne le code complexe ou important.\nOn peut les tester avec des tests d'intégration qui seront beaucoup moins nombreux que les unit tests des domain models and algorithms.\nOvercomplicated code : grande valeur sur les deux axes.\nLà on est embêté : c'est à la fois du code qu'on ne peut pas se permettre de ne pas tester, et du code difficile à tester. Par exemple des fat controllers qui font tout eux-mêmes.\nOn va chercher à se débarrasser de ce code en le découpant, pour obtenir du code qui score beaucoup sur l'un des axes mais jamais les deux.\nLe Humble Object Pattern va nous permettre de découpler la logique de la partie difficile à tester (par difficile on entend code asynchrone/multi-thread, UI, dépendances out-of-process etc.).\nLe test va tester la partie logique complexe/métier directement.\nLe humble object est une fine couche avec très peu de logique, qui va lier la logique et la dépendance qui pose problème dans les tests.\nIl s'agit de dire qu'un code doit soit avoir une grande complexité (domain layer and algorithms), soit travailler avec beaucoup de dépendances (controllers), mais jamais les deux.\nExemples :\nLa functional et l'hexagonal architecture utilisent le humble object pattern.\nOn peut aussi mettre dans cette catégorie les patterns MVC et MVP qui séparent la logique (le modèle) de la UI (view), avec le humble object (le presenter ou le controller).\nL'aggregate du DDD est aussi un exemple : on groupe les classes dans des clusters (les aggregates) où elles auront une forte connectivité, et les clusters auront une faible connectivité entre eux. Ca permet de faciliter la testabilité en ayant besoin d'instancier essentiellement les collaborators du cluster concerné.\nNDLR : que l'aggregate permette d'améliorer la testabilité ou la maintenabilité OK, mais j'arrive pas à voir le rapport avec le humble object pattern ici. On n'a pas de hard-to-test dependency.\nExemple d'application avec du code overcomplicated, refactorée vers du humble object pattern :\nDescription :\nOn a un CRM qui gère les utilisateurs, et les stocke en DB.\nLa seule fonctionnalité dispo c'est le changement d'email : si le nom de domaine du nouvel email appartient à l'entreprise le user est un employé, sinon il devient un customer.\nEn fonction des emails des users, et donc de leur statut, le nombre d'employés est calculé et mis en base.\nQuand le changement d'email est fait, on doit envoyer un message dans un message bus.\nLa 1ère implémentation contient une classe User, avec une méthode changeEmail() qui calcule le nouveau statut du user, et sauve son email en base, mais aussi recalcule et sauve le nouveau nombre d'employés dans la table de l'entreprise. Elle envoie aussi le message dans le message bus.\nclass User {\n constructor(\n private userId: number,\n private email: Email,\n private type: UserType\n ) {}\n changeEmail(userId: number, newEmail: Email) {\n // Get user data from database\n // If new email is same as before, return\n // Get company data from database\n // Check whether the email is corporate\n // Set the user type accordingly\n // If the type is different, update company number\n // of employees.\n // Save user info in database\n // Save company info in database\n // Send message to message bus\n }\n}\nNotre méthode changeEmail() fait des choses importantes du point de vue domaine, mais en même temps elle a deux collaborators out-of-process (la DB et le message bus), ce qui est un no-go pour du code compliqué ou avec importance domaine.\nOn est en présence du pattern Active Record : la classe domaine se query et se persiste en DB directement. C'est OK pour du code simple, mais pas pour du code qui va croître sur le long terme.\nPossibilité 1 : rendre explicites les dépendances implicites, en donnant l'objet de DB et message bus en paramètre (ce qui permettra de les mocker dans les tests).\nQue les dépendances soient directes ou via une interface, ça ne change rien au statut du code : il reste overcomplicated.\nOn va devoir mettre en place une mécanique de mocks complexe pour les tests. On peut trouver plus clean que ça.\nPossibilité 2, étape 1 : introduire un application service (humble object) qu'on appelle UserController pour prendre la responsabilité de la communication avec les dépendances out-of-process.\nLa nouvelle classe va chercher les informations du user et de l'entreprise en DB, crée un objet User avec ces infos. Puis elle appelle user.changeEmail(), et enfin sauve les données du user et de l'entreprise en DB, et envoie l'event d'email changé dans le message bus.\nclass UserController {\n constructor(\n private database: Database,\n private messageBus: MessageBus\n ) {}\n changeEmail(userId: number, newEmail: Email) {\n // Get user data from database\n // Get company data from database\n const user = new User(userId, email, type);\n const numberOfEmployees = user.changeEmail(\n newEmail,\n companyDomainName,\n numberOfEmployees\n );\n // Save user info in database\n // Save company info in database\n // Send message to message bus\n }\n}\npublic class User {\n// ...\nchangeEmail(\n newEmail: Email,\n companyDomainName: string,\n numberOfEmployees: number\n) {\n // If new email is same as before, return\n // Check whether the email is corporate\n // Set the user type accordingly\n const newType = ...\n // If the type is different, update company number\n // of employees.\n numberOfEmployees = ...\n this.email = newEmail;\n this.type = newType;\n return numberOfEmployees;\n}\n}\nProblèmes :\nOn a une logique complexe dans le fait de reconstruire les données à partir de la base de données (le mapping), c'est le travail d'un ORM.\nL'event de changement d'email est envoyé systématiquement, même si l'email n'a pas été changé.\nOn a un petit code smell : la méthode user.changeEmail() prend le nombre d'employés en paramètre, et renvoie le nouveau nombre d'employés. Ça n'a rien à voir avec un user donné.\nMais au moins la classe User a perdu ses collaborators, elle est donc en l'état purement fonctionnelle. On va pouvoir la tester à fond facilement.\nÉtape 2 : enlever de la complexité de l'application service.\nPour faire le mapping entre les données de la DB et un objet en mémoire, on va soit utiliser un ORM, soit créer nous-mêmes un objet de type factory qui va renvoyer notre User.\nCette logique a l'air simple avec peu de branches apparentes, mais il faut prendre en compte les branches cachés liés aux dépendances : on fait des conversions de type, on va chercher des objets inconnus dans un tableau un à un etc. beaucoup de choses peuvent mal aller dans ce processus.\npublic class UserFactory {\ncreate(data: Record<any, any>) {\n Precondition.requires(data.length >= 3);\n const id = data[0];\n const email = data[1];\n const type = data[2];\n return new User(id, email, type);\n}\n}\nOn a ici du code utilitaire complexe.\nA la fin de l'étape on a bien User qui est dans la case “domain models and algorithms”, et UserController qui est dans la case “Controllers”. Il n'y a plus d'overcomplicated code.\nÉtape 3 : on introduit une nouvelle classe domaine Company.\nNotre nouvelle classe domaine Company peut récupérer la logique de calcul du nombre d'employés qu'on sort de User.\nOn a donc UserController qui crée les deux objets de domaine à partir des données de la DB, et qui appelle user.changeEmail() en donnant l'instance company en paramètre.\nOn a un principe important d'encapsulation OO ici : tell, don't ask. Le user va dire (tell) à l'instance de company de mettre à jour elle-même son nombre d'employés, plutôt que lui demander (ask) ses données brutes et faisant l'opération à sa place.\nuser.changeEmail() n'est plus une fonction pure puisqu'elle a un collaborator (company), mais vu qu'il n'y en a qu'un et qu'il n'est pas out-of-process, c'est raisonnable.\nOn va donc devoir faire du state-based testing, l'output-based étant possible qu'avec des fonctions pures.\nclass Company {\n constructor(\n private domainName: string,\n private numberOfEmployees: number\n ) {}\n changeNumberOfEmployees(delta: number) {\n Precondition.requires(this.nomberOfEmployees + delta >= 0);\n this.numberOfEmployees += delta;\n }\n isEmailCorporate(email: Email) {\n // Get the domain part from the email\n // and return whether it is equal to this.domainName\n }\n}\nclass User {\n // ...\n changeEmail(newEmail: Email, company: Company) {\n if (newEmail === this.email) return;\n const newType = company.isEmailCorporate(newEmail)\n ? Usertype.Employee\n : Usertype.Customer;\n // If the type is different, update company number\n // of employees.\n company.changeNumberOfEmployees(delta);\n this.email = newEmail;\n this.type = newType;\n }\n}\nclass UserController {\n constructor(\n private database: Database,\n private messageBus: MessageBus\n ) {}\n changeEmail(userId: number, newEmail: Email) {\n const userData = this.database.getUserById(userId);\n const user = UserFactory.create(userData);\n const companyData = this.database.getCompany();\n const company = CompanyFactory.create(companyData);\n user.changeEmail(newEmail, company);\n this.database.saveCompany(company);\n this.database.saveUser(user);\n this.messageBus.sendEmailChangedMessage(userId, newEmail);\n }\n}\nA la fin, user et company sont sauvés en DB, et l'event est envoyé dans le message bus par UserController.\nComment tester notre exemple refactoré ?\nLe code des classes domaine (User et Company), et le code utilitaire complexe (factory si on n'a pas utilisé d'ORM) peuvent être unit testés à fond.\nExemple : \"changement d'email de corporate à non corporate\", \"changement d'email de non corporate à corporate\", \"changement d'email au même email\" etc.\nit(\"changes email from non corporate to corporate\", () => {\n const company = new Company(\"mycorp.com\", 1);\n const sut = new User(\"user@gmail.com\", UserType.Customer);\n sut.changeEmail(\"new@mycorp.com\", company);\n expect(company.numberOfEmployees).toBe(2);\n expect(sut.email).toBe(\"new@mycorp.com\");\n expect(sut.userType).toEqual(UserType.Employee);\n});\nLes méthodes ultra simples comme le constructeur de User n'ont pas à être testées.\nLe controller doit être testé avec des tests d'intégration moins nombreux. Ce sera l'objet des prochains chapitres.\nLes pré-conditions sont des checks qui permettent de throw une exception tôt si une incohérence est détectée, pour éviter des problèmes plus importants.\nCes pré-conditions doivent être testées seulement si elles ont un lien avec le domaine, sinon c'est pas la peine.\nExemple de pré-condition qu'on teste : la méthode qui permet de mettre à jour le nombre d'employés sur Company throw si le nombre souhaité est inférieur à 0.\nExemple de pré-condition à ne pas tester : notre user factory vérifie que les données venant de la base ont bien 3 éléments avant de reconstruire le user. Cette vérification n'a pas de sens d'un point de vue domaine.\nNotre découpage domaine/controller marche bien parce qu'on récupère l'ensemble des données upfront, et les sauve à la fin en base inconditionnellement dans le controller. Mais que faire si on a besoin d'accès à des données seulement dans certains cas dictés par la logique ?\nIl y a des trade-offs à faire en fonction de :\nla testabilité du code du domaine\nla simplicité du code du controller\nla performance\nOn a 3 possibilités :\nGarder toute la logique dans le domaine, et toute l'interaction avec les deps out-of-process dans le controller.\nDans ce cas on va avoir une moins bonne performance, puisqu'on fera la lecture de la donnée dont on n'aura peut-être pas besoin à l'avance systématiquement. Le controller n'ayant pas la connaissance de si on a besoin ou non, il prend la donnée et la donne tout le temps.\nPar contre on a un code de domaine testable, et un controller simple.\nInjecter les dépendances out-of-process dans le domaine, et laisser le code business décider quand récupérer ou non les données.\nLe souci ici c'est la maintenabilité des tests du domaine, avec soit des tests lents à travers la DB, soit des mocks compliqués à maintenir.\nPar contre on a un controller simple, et de la performance.\nDécouper le processus de décision en plusieurs parties.\nLe controller va appeler la 1ère partie, récupérer les données, puis décider lui-même s'il faut faire la deuxième partie. Si oui il récupère les données additionnelles depuis la DB, et exécute la 2ème partie. Et à la fin comme d'habitude sauve le tout en DB.\nUne partie de la logique risque de fuiter du domaine vers le controller et rendre le controller plus compliqué.\nPar contre on a le code du domaine testable, et on garde la performance.\nLa plupart du temps, céder sur la performance n'est pas possible.\nIl nous reste donc les 2 dernières possibilités.\nL'auteur conseille de privilégier la séparation du processus de décision plutôt que l'injection des dépendances out-of-process dans le domaine. On peut gérer la fuite de la logique vers le controller et la complexification du code du domaine avec certaines techniques.\nUne de ces techniques est le pattern CanExecute / Execute.\nImaginons qu'on veuille mettre à jour l'email du user seulement si son compte n'est pas encore confirmé.\n1ère possibilité : on query les infos upfront, on donne tout à user, et le user décide de changer ou non l'email. Mais on a peut être récupéré les infos de la company pour rien, si le user était déjà confirmé => problème de performance.\n2ème possibilité : le controller vérifie lui-même si le compte du user est confirmé avant de faire éventuellement la query des infos de la company. Ici le controller a récupéré une partie de la logique chez lui.\n3ème possibilité (CanExecute / Execute) : le user expose une méthode canChangeEmail() qui encapsule la logique de prise de décision. Le controller n'a plus qu'à l'appeler pour décider si on passe à l'étape suivante ou non. La décision ne se fait plus vraiment au niveau du controller.\n// controller\nconst canChangeEmail = user.canChangeEmail();\nif (!canChangeEmail) {\n return;\n}\nuser.changeEmail(newEmail, company);\nPour s'assurer que le controller n'a d'autre choix que d'appeler cette méthode avant d'aller plus loin (et donc lui retirer de la responsabilité), on va mettre une pré-condition dans la méthode user.changeEmail(), où on appelle explicitement canChangeEmail() en vérifiant que la réponse est oui. Et cette pré-condition métier sera testée (contrairement à l'appel à canChangeEmail() dans le controller).\n// user\npublic canChangeEmail() {\nreturn this.isEmailConfirmed ? false: true;\n}\npublic changeEmail(newEmail: Email, company: Company) {\nPrecondition.requires(this.canChangeEmail());\n// [...]\n}\nVoici une autre de ces techniques concerne l'envoi de domain events :\nOn parle bien ici des domain events au sens DDD, ces events permettent d'informer les autres composants du système des étapes importantes qui ont lieu dans nos objets domaine.\nSi on revient à notre exemple de CRM, au moment du changement d'email du user, le controller envoie un message dans un message bus. Mais cet envoi est fait dans tous les cas, même si le changement n'a pas eu lieu. On veut l'envoyer seulement si le changement est fait.\nPour enlever la décision d'envoyer ou non l'event du controller, et la mettre dans le domaine, on va créer une liste d‘events qu'on met à l'intérieur de la classe domaine.\nOn a un event :\nclass EmailChanged {\n public userId: number;\n public newEmail: Email;\n}\nLe User crée l'event si l'envoi est confirmé :\npublic changeEmail(newEmail: Email, company: Company) {\n// [...]\nthis.emailChangedEvents.push(new EmailChanged(userId, newEmail);\n}\nEt le Controller va itérer sur les domain events de User pour envoyer les bons messages dans le message bus :\npublic changeEmail(userId: int, newEmail: Email) {\n// [...]\nuser.changeEmail(newEmail, company);\n// [...]\nuser.emailChangedEvents.forEach((event) =>\n this.messageBus.sendEmailChangedMessage(\n event.userId,\n event.newEmail\n );\n);\n}\nOn va donc pouvoir unit tester la création de chaque domain event dans chaque cas dans le user, et on fera beaucoup moins d'integration tests pour vérifier que le controller lit bien les events du user et envoie ce qu'il faut.\nDans des projets plus gros, on pourrait vouloir fusionner les events avant de les dispatcher, cf. Merging domain events before dispatching.\nPour ce qui est de l'envoi de l'email, c'est un comportement observable de l'extérieur donc il doit être fait que si l'email est changé. Par contre, l'écriture en DB peut être faite inconditionnellement parce qu'elle est privée et que le résultat ne changera pas.\nOn a un petit souci de performance à écrire en DB si l'email n'a pas changé, mais c'est un cas plutôt rare.\nOn peut aussi le mitiger par le fait que la plupart des ORM n'iront pas écrire en DB si l'objet n'a pas changé. Donc on peut faire l'appel sans crainte.\nLe conseil général de Vladimir est de ne jamais introduire de dépendances out-of-process (même mockées dans les tests) dans le code du domaine. Il conseille plutôt de fragmenter les appels au domaine, et au pire mettre ce code dans le controller et le tester par des tests d'intégration.\nLes cas dans lesquels on va devoir mettre la logique dans le controller peuvent être par exemple :\nVérifier qu'un email est unique (il faut faire un appel out-of-process pour ça).\nGérer les cas d'erreur liés aux appels out-of-process.\nA propos de qui est le client de qui et de la notion de détail d‘implémentation :\nAu niveau du controller, le client c'est l'utilisateur final, donc il faut tester ou mocker ce qui lui est visible ou sert directement son but. Les appels qui sont faits vers le domaine sont un détail d'implémentation.\nAu niveau du domaine, le client c'est le contrôler, donc il faut unit tester ce qui sert directement son but. Les appels éventuels vers d'autres classes du domaine sont des détails d'implémentation qu'on n'a pas à mocker.","iii---integration-testing#III - Integration testing":"","8---why-integration-testing#8 - Why integration testing?":"Pour rappel, un test d'intégration est un test qui ne répond pas à au moins un des 3 critères des tests unitaires : vérifier une unité de comportement, le faire vite, le faire en isolation par rapport aux autres tests.\nEn pratique les tests d'intégration vont être ceux qui gèrent la relation avec les dépendances out-of-process.\nOn est donc dans la partie “controllers” en termes de type de code.\nLes règles de la pyramide des tests sont de :\nCouvrir le maximum de cas par des tests unitaires.\nTester un happy path, ainsi que les edge cases qui ne peuvent pas être couverts par les tests unitaires avec des tests d'intégration.\nQuand la logique est simple, on a moins de tests unitaires, mais les tests d'intégration gardent leur valeur.\nQuand un edge case amène à un crash immédiat, il n'y a pas besoin de le tester avec un test d'intégration.\nExemple du pattern CanExecute/Execute.\nOn appelle ce principe le Fail Fast principle.\nOn reste dans l'esprit coût/bénéfice pour la maintenance d'un test, dans ce cas le bénéfice n'est pas suffisant parce que ce genre de cas ne mène pas à de la corruption de données, et est rapide à remarquer et à fixer.\nIl y a 2 manières de tester les dépendances out-of-process : les tester directement ou les remplacer par des mocks.\nOn peut classer ces dépendances en deux catégories :\nLes managed dependencies sont celles que seuls nous utilisons, et que le monde externe ne connaît pas. Exemple typique : la base de données.\nCes dépendances sont considérées comme des détails d'implémentation.\nOn n'a donc pas à se préoccuper de nos interactions avec elles, ce qui compte c'est leur état final, et l'impact que ça aura sur le résultat observable. Donc pas besoin de mock.\nLes unmanaged dependencies sont celles qui sont observables de l'extérieur. Exemple typique : un serveur SMTP dont les mails seront visibles par les clients finaux, ou encore un message bus dont les messages vont affecter des composants externes à notre système.\nCes dépendances sont considérées comme faisant partie du comportement observable.\nPuisque les unmanaged dependencies sont observables, ils font partie de l'API publique, et donc il faut nous assurer que nos interactions avec elles restent les mêmes : les mocks sont parfaits pour ça.\nIl peut arriver qu'une dépendance soit à la fois managed et unmanaged : par exemple une base de données dont on choisit de partager certaines tables publiquement avec un composant externe.\nPartager une DB est en général une mauvaise idée parce que ça va nous coupler fortement, il vaut mieux passer par une API synchrone ou un message bus.\nCeci dit, si ça arrive, il faudra différencier les tables partagées des tables privées, et traiter chacune comme ce qu'elle est (managed/unmanaged) : des mocks pour assurer l'ensemble de nos interactions avec les tables partagées, et la vérification de l'état final seulement pour les tables privées.\nDans le cas où on n'aurait pas la possibilité de tester en intégration une DB privée (base legacy trop grosse, trop coûteuse, raisons de sécurité etc.), l'auteur conseille de ne pas écrire de tests d'intégration du tout pour celles-ci, et de se concentrer sur les unit tests.\nLa raison est que ça compromet la résistance aux refactorings en traitant une dépendance privée comme publique, et ça n'ajoute que très peu de protection contre des régressions en plus des unit tests. Le rapport coût/bénéfice n'est pas suffisant.\nSi on reprend l'exemple du CRM, pour écrire des tests d'intégration pour le UserController :\nOn va d'abord écrire un test pour couvrir le happy path le plus long. Ici ce serait le cas où on change l'email d'un user, qui passe de non corporate à corporate. On va mettre à jour en DB le user, les infos de company, et aussi envoyer le message dans le message bus pour l'email.\nIl n'y a qu'un edge case non couvert par des unit tests : le cas où l'email ne peut pas être changé. Mais dans ce cas on est sur du fail fast : une exception sera lancée et le programme s'arrêtera. Donc pas besoin de test d'intégration pour ça.\nA propos des tests end to end, on peut en faire quelques-uns pour notre projet, et leur faire traverser les scénarios les plus longs pour s'assurer que tout est bien branché. On vérifiera le résultat pour le client final au lieu de regarder dans la DB, et on vérifiera le message envoyé dans le message bus pour la dépendance externe à laquelle on n'a pas accès. Ici pour cette feature on choisit de ne pas en faire.\nConcernant notre test d'intégration de happy path donc, il faut d'abord décider de la manière dont on traite nos dépendances out-of-process : la DB est managed donc doit être testée au niveau de son état pour le user et la company, alors que le message bus est unmanaged donc doit être mocké pour tester les interactions avec lui.\nNotre test va contenir 3 sections :\nD'abord mettre le user et la company en DB et initialiser le mock pour le message bus. (Arrange)\nEnsuite appeler la méthode de notre controller. (Act)\nEt enfin tester le résultat en DB et l'interaction avec notre mock (Assert).\nit(\"changes email from non corporate to corporate\", () => {\n // Arrange\n db.createCompany(\"mycorp.com\", 0);\n db.createUser(1, \"user@gmail.com\", \"customer\");\n const busMock = { send: jest.fn() };\n const sut = new UserController(new MessageBus(busMock));\n // Act\n sut.changeEmail(1, \"user@mycorp.com\");\n // Assert\n const user = db.getUserById(1);\n expect(user.email).toBe(\"user@mycorp.com\");\n expect(user.type).toBe(\"employee\");\n const company = db.getCompany();\n expect(company.numberOfEmployees).toBe(1);\n expect(busMock.send).toHaveBeenCalledTimes(1);\n expect(busMock.send).toHaveBeenCalledWith(expect.toInclude(\"1\"));\n expect(busMock.send).toHaveBeenCalledWith(\n expect.toInclude(\"user@mycorp.com\")\n );\n});\nIl ne faut pas utiliser les mêmes objets entre les sections, de manière à être sûr à chaque fois de lire et écrire depuis la DB.\nL'introduction d'interfaces prématurées est une mauvaise idée. Il faut en introduire une quand elle existe déjà mais est implicite, c'est-à-dire quand il y a au moins deux implémentations de celle-ci.\nLe principe fondamental ici c'est YAGNI (you ain't gonna need it) qui dit que le code supposément utile pour plus tard ne le sera sans doute pas, ou pas sous cette forme.\nPour plus d'info sur le trade off YAGNI vs OCP, l'auteur a fait un article.\nPar conséquent, étant donné qu'un mock est une implémentation de plus, il nous faudra la plupart du temps faire une interface seulement pour les unmanaged dependencies.\nQuelques bonnes pratiques pour les tests d'intégration :\nCréer une séparation explicite entre domain model et controllers permet de savoir quoi tester unitairement, et quoi tester en intégration.\nLimiter le nombre de couches à seulement 3 : infrastructure layer, domain layer et application service layer.\nDavid J. Wheeler a dit à ce propos : “All problems in computer science can be solved by another layer of abstraction, except for the problem of too many layers of abstraction.”\nOn se retrouve souvent avec 4, 5, 6 layers, ce qui rend l'ajout d'une feature, et même la compréhension d'une feature complexe parce qu'on doit toucher à de nombreux fichiers.\nOn a souvent tendance à tester le layer du dessous depuis le layer du dessus. Et avec de nombreux layers on aboutit à de nombreux tests avec mocks qui apportent chacun peu de valeur.\nÉliminer les dépendances circulaires : quand deux classes dépendent l'une de l'autre pour fonctionner.\nLes dépendances circulaires créent aussi une difficulté à appréhender le code parce qu'on ne sait pas par où commencer.\nPar exemple, quand une classe en instancie une autre et lui passe une instance d'elle-même. On se retrouve à introduire des interfaces et utiliser des mocks pour les tests.\nNe pas mettre plusieurs Act dans le même test : parfois on est tenté de mettre en place plusieurs Arrange/Act/Assert à la suite dans le même test. C'est une mauvaise idée parce que le test devient difficile à lire et à modifier, et a tendance à grossir encore.\nA propos de la question des logs :\nSelon l'auteur, les logs doivent être testé uniquement s'ils sont destinés à être observés par des personnes autres que les développeurs eux-mêmes.\nPar exemple des personnes du business qui en ont besoin pour des insights.\nSteve Freeman et Nat Pryce distinguent deux types de logs dans Growing Object-Oriented Software, Guided by Tests : le support logging qui est destiné au personnel de support et sysadmins, et le diagnostic logging qui est destiné aux développeurs eux-mêmes pour du débug.\nIl faut bien distinguer le diagnostic logging et le support logging, en n'y appliquant pas la même technique de code.\nLe support logging étant plus important, on pourra utiliser une classe à part inspirée du structured logging : une manière de logger qui sépare les paramètres et le texte principal, de manière à pouvoir reformater ces logs comme on veut.\nExemple de code :\ndomainLogger.userTypeHasChanged(45, \"customer\", \"corporate\");\nclass DomainLogger {\n public userTypeHasChanged(\n userId: number,\n oldType: UserType,\n newType: UserType\n ) {\n this.logger.info(\n `User ${userId} changed type ``from ${oldType} to ${newType}`\n );\n }\n}\nPour le tester il va falloir le traiter comme une dépendance out-of-process unmanaged (puisqu'elle ne nous est pas privée). Et donc on peut faire comme avec le message envoyé dans le message bus :\nSi c'est le controller qui doit faire le log, il peut le faire directement et ce sera testé dans un test d'intégration sous forme de mock.\nSi c'est le code de domaine qui le fait, il faut séparer la logique d'envoi du log de l'envoi du log lui-même : créer un domain event pour l'envoi de ce log dans le domaine, et itérer sur les events de log dans le controller pour logger les logs dans la dépendance out-of-process. Le test pourra être fait sous forme unitaire pour vérifier la création du domain event.\nConcernant la quantité de logs :\nPour le support logging la question ne se pose pas : il en faut autant qu'il y a de requirement business.\nPour le diagnostic logging il faut faire attention à ne pas en abuser :\nTrop de logs noient l'information importante.\nMême si on log avec des niveaux différents, on pollue quand même le code avec des lignes de log un peu partout, ce qui rend plus difficile la lecture.\nL'auteur conseille de ne pas utiliser de logs dans le domaine, et dans ne le controller les utiliser que temporairement pour trouver un bug, puis les enlever.\nIdéalement il faudrait que les logs ne servent que pour les exceptions non gérées.\nConcernant la manière de passer le logger à nos objets, l'auteur conseille de le passer explicitement dans le constructeur ou dans l'appel à une méthode.","9---mocking-best-practices#9 - Mocking best practices":"Il faut mocker les unmanaged dependencies à l'edge (au bord) de notre système.\nLa raison est d'augmenter la protection contre les régressions en mettant en jeu le plus possible de code. On va donc mocker au plus près de l'appel à la dépendance externe.\nOn améliore aussi la résistance aux refactorings parce que ce qui est mocké est une API publique, et donc peu susceptible de changer contrairement à notre code interne.\nExemple : Si on a une classe MessageBus qui encapsule et ajoute des fonctionnalités à une classe Bus qui elle-même est un simple wrapper autour de la dépendance externe, il faut mocker Bus et non pas MessageBus.\nclass MessageBus {\n private _bus: Bus;\n // [...]\n}\nDans les tests on va peut être instancier un peu plus de choses pour que le mock soit en bout de chaîne, mais c'est pas grave :\n// Arrange\nconst busMock = new Mock<IBus>();\nconst messageBus = new MessageBus(busMock);\nconst sut = new UserController(messageBus)\n// [...]\n// Assert\nexpect(busMock).toHaveBeenCalledWith(/* ... */);\nPour le mock de notre DomainLogger, on n'est pas obligés d'aller jusqu'à l'edge parce que contrairement à MessageBus où la structure exacte des message est cruciale pour maintenir la compatibilité avec la lib, la structure exacte des messages de log nous importe peu.\nQuand on veut mocker du code réutilisé dans de nombreux endroits (ce qui est en général le cas du code qui est à l'edge du système), il peut être plus lisible d'implémenter son propre objet de mock, qui est par définition un spy.\nExemple de spy :\nclass busSpy {\n public send(message: string) {\n this.sentMessages.push(message);\n }\n public shouldSendNumberOfMessages(num: number) {\n expect(this.sentMessages.length).toBe(num);\n return this;\n }\n public withEmailChangedMessage(userId: number, newEmail: Email) {\n const message = `Type: user email changed id: ${userId}``email: ${newEMail}`;\n expect(this.sentMessages).toContain(message);\n return this;\n }\n}\nUtilisation dans le code :\nbusSpy\n .shouldSendNumberOfMessages(1)\n .withEmailChangedMessage(user.userId, \"new@gmail.com\");\nBonnes pratiques pour les mocks :\nVu que les mocks doivent êtres réservés aux dépendances out-of-process unmanaged, ils doivent être seulement dans les tests d'intégration.\nOn peut utiliser autant de mocks que nécessaire pour gérer toutes les dépendances out-of-process unmanaged qui sont utilisées dans notre controller.\nPour bien s'assurer de la stabilité de l'utilisation de l'API publique constituée par notre dépendance unmanaged, il faut aussi vérifier le nombre d'appels vers la dépendance.\nIl ne faut mocker que les classes qu'on possède. Ca veut dire qu'il faut wrapper toute dépendance unmanaged out-of-process par un adapter qui représente notre utilisation de cette dépendance. C'est ce wrapper qu'on va mocker.\nUn des avantages c'est que si la dépendance change de manière importante dans son interface, elle ne pourra pas impacter le reste de notre code sans qu'on change notre wrapper. Il s‘agit d'une protection.\nA l'inverse, selon l'auteur, créer des wrappers autour de dépendances qui ne sont pas unmanaged ne vaut pas le coup en terme de maintenance. Un exemple en est l'ORM.","10---testing-the-database#10 - Testing the database":"Il est préférable d'avoir tout ce qui concerne la structure de la base de données dans l'outil de versionning, tout comme le code.\nEn plus de la structure, certaines données sont en fait des reference data, et doivent être aussi versionnées avec le code.\nIl s'agit de données qu'il faut générer pour que l'application puisse fonctionner.\nOn peut différencier les reference data du reste en se demandant si l'application peut modifier ces données : si non alors ce sont des reference data.\nExemple : imaginons qu'on reprenne notre exemple CRM, et qu'on veuille mettre le type d'utilisateur en base. Si on veut garantir par la DB elle-même que le type ne sera pas autre chose que les types autorisés, on peut créer une table UserTypes, y mettre les types autorisés, et faire une foreign key depuis la table User vers cette table.\nLes données dans cette table sont là juste pour des raisons techniques, pour faire ce qui est fait ou pourrait l'être dans le code mais avec plus de sécurité. Elles ne sont pas accessibles aux utilisateurs de l'application. Ce sont des reference data.\nIl est préférable de permettre à tous les développeurs d'avoir leur base de données (idéalement sur leur machine locale).\nUne DB partagée peut devenir inutilisable, au moins momentanément, et ne permet pas de garantir l'exécution des tests vu que des modifications peuvent être faites par les autres développeurs.\nIl y a deux types d'approche pour le développement vis-à-vis de la base de données : la delivery state-based et migration-based.\nLa state-based consiste à avoir l'état actuel de la structure de la DB versionnée. On va alors créer une DB modèle à partir de cette structure, puis utiliser un outil de comparaison qui va la comparer avec la DB de production, pour ensuite appliquer les modifications sur la production.\nLa migration-based consiste à écrire des scripts de migration qui vont être versionnées. On ne connaît pas l'état actuel de la DB depuis ces scripts, mais les jouer tous dans l'ordre permet d'en obtenir un exemplaire.\nL'outil de comparaison de DB ne sera pas utilisé ici, sauf pour éventuellement permettre de détecter des anomalies dans la DB de prod.\nLa state-based est plus utile pour gérer les conflits de merge en ayant l'état explicite, alors que la migration-based est plus utile pour gérer la data motion (le fait de changer la structure de la DB avec des données dedans).\nLa raison est que gérer la transformation de données existantes est difficile à faire automatiquement, il faut y appliquer des règles métier.\nDans la plupart des cas, gérer la data motion est plus important que la gestion de conflits de merge. Donc il vaut mieux préférer l'approche migration-based.\nIl ne faut jamais faire de changements directement dans la DB sans passer par l'app, autrement que par des scripts de migration versionnés.\nSi une migration est incorrecte, il vaut mieux faire une migration pour la corriger (sauf si elle n'a pas encore été jouée et que la jouer amènera à de la perte de données).\nA propos de la gestion des transactions dans nos DB :\nLes transactions sont importantes à la fois dans le code pour garantir la consistance des données, et aussi dans les tests pour s'assurer qu'ils sont fiables.\nDans le code, on a deux notions liées à la DB :\nLes transactions qui décident si les modifications faites doivent être gardées ou non. Elles durent le temps de l'opération entière.\nLes repositories qui prennent une transaction, et agissent sur les données (en lecture ou écriture) dans le cadre de cette transaction.\nExemple dans notre controller du CRM :\npublic UserController {\npublic UserController(\n private transaction: Transaction\n) {\n this.userRepository =\n new UserRepository(transaction);\n}\n// [...]\nconst user = this.userRepository.getById(userId);\n// [...]\nthis.userRepository.save(user);\nthis.transaction.commit();\n// [...]\n}\nIl existe aussi un pattern appelé unit of work, qui consiste à retenir les modifications sur les objets qui doivent avoir lieu au cours d'une transaction, et à les soumettre en une seule fois à la DB au moment où la transaction est validée.\nCa permet notamment d'économiser le nombre de connexions à la DB. La plupart des ORM l'implémentent.\nDans le cas où on travaille avec les document databases comme MongoDB, les transactions sont souvent garanties au sein d'un même document seulement.\nDans ce cas, il faut se débrouiller pour que nos opérations n'affectent qu'un document. Si on utilise le domain model pattern du DDD, on pourra affecter un aggregate par document et suivre la guideline de ne mettre à jour qu'un document à la fois.\nConcernant les tests d'intégration, il faut que chacun des 3 blocs (Arrange, Act, Assert) ait sa transaction à lui.\nLa raison est de chercher à reproduire au mieux l'environnement de production. Dans le cas contraire on peut par exemple se retrouver à avoir certaines de nos libs (ORM notamment) qui vont mettre en cache certaines données au lieu d'aller lire/écrire en DB explicitement.\nÇa compromet donc l'idée de wrapper chaque test dans une transaction qu'on annule à la fin du test (l'idée est évoquée et balayée pour cette raison).\nSelon l'auteur, la parallélisation des tests d'intégration n'en vaut pas le coup, parce que ça nécessite trop d'efforts. Il vaut mieux les jouer séquentiellement, et cleaner les données entre les tests.\nIl suggère de cleaner au début de chaque test. Le faire à la fin peut poser problème à cause de potentiel crash avant la fin.\nConcernant la manière d'effacer les données, il suggère une simple commande SQL de type DELETE FROM dbo.User;\nIl vaut mieux éviter d'utiliser une DB “in memory” à la place de la vraie DB dans les tests d'intégration. Ça permet de transformer les tests d'intégration en unit tests, mais ça leur enlève aussi de la fiabilité vis-à-vis de l'intégration à cause des différences entre les deux bases de données.\nSelon l'auteur on va finir de toute façon par faire des tests d'intégration à la main si on va sur une BD différente.\nOn peut utiliser certaines techniques de refactoring pour rendre le code des tests d'intégration plus lisible :\nPour la section Arrange : on peut par exemple utiliser des méthodes de type factory pour que la création des objets en base avec transaction prenne moins de place.\nLe pattern Object mother consiste à avoir une méthode qui crée l'objet, et le renvoie.\nIl conseille de commencer par mettre ces méthodes dans la classe de test, et de ne les déplacer que si besoin de réutilisation.\nOn peut mettre des valeurs par défaut aux arguments, pour n'avoir à spécifier que ceux qui sont nécessaires.\nPour la section Act : on peut aussi utiliser une fonction helper pour réduire ça à un appel qui créera la transaction et la passera à la méthode testée, comme en production.\nEx :\nconst result = execute(() => user.changeEmail(userId, \"new@gmail.com\"));\nPour la section Assert : là aussi on peut utiliser des fonctions helper :\nOn peut mettre des fonctions qui abstraient le fait d'aller chercher des données en base.\nconst user = queryUser(user.id);\nOn peut créer une classe exposant une fluent interface par dessus des instructions assert.\nclass UserExtensions {\n public shouldExist(user: User) {\n expect(user).toBeTruthy();\n return user;\n }\n public withEmail(user: User, email: Email) {\n expect(email).toEqual(user.email);\n return user;\n }\n}\n// In test\nuser.shouldExist().withEmail(\"new@gmail.com\");\nTODO : ce code ne fonctionne pas en l'état, il faudrait trouver le moyen de faire de l'extension de méthode en Typescript.\nAvec les helpers qui créent des objets dans la section Arrange, ou qui lisent des objets dans la section Assert, on crée plus que 3 transactions en tout. Pour autant, ça reste un bon trade off selon l'auteur : on sacrifie un peu de performance du test, contre une amélioration substantielle de maintenabilité du test.\nFaut-il tester les opérations de lecture ? (comme renvoyer une information au client)\nLe plus important est de tester les opérations d'écriture qui peuvent corrompre les données. Pour celles de lecture il n'y a pas ce genre d'enjeu, donc la barre pour ajouter des tests est plus haute : il ne faut tester que les opérations les plus complexes.\nEn fait, l'intérêt principal du domain model c'est de protéger la consistance des données à travers l'encapsulation. Dans les opérations de lecture il n'y en a pas besoin.\nL'auteur conseille donc de ne tester les opérations qu'avec des tests d'intégration, et seulement pour celles qu'on veut tester.\nIl conseille aussi d'écrire les requêtes pour la lecture directement en SQL, l'ORM n'étant pas utile dans ce cas, et ajoutant des couches d'abstractions inutiles et peu performantes.\nFaut-il tester les repositories ?\nNon. Malgré l'intérêt apparent, le rapport bénéfice/coût est défavorable :\nD'un côté les repositories manipulent la DB qui est une dépendance out-of-process, donc si on les testait, ce serait avec des tests d'intégration (et ceux-ci coûtent cher).\nDe l'autre, ils ne fournissent pas tant de protection contre les régressions que ça, et surtout ils sont pour l'essentiel déjà testés par les tests d'intégration des controllers.\nSi on arrive à isoler les factories à part, ça pourrait valoir le coup de les tester à part unitairement, mais quand on utilise un ORM, on ne peut en général pas tester le mapping à part de la DB.\nIl en est de même pour les event dispatchers par exemple, dont le rapport bénéfice/coût des tests sera défavorable.","iv---unit-testing-anti-patterns#IV - Unit testing anti-patterns":"","11---unit-testing-anti-patterns#11 - Unit testing anti-patterns":"Il ne faut pas rendre publique une méthode privée, juste pour la tester.\nLa 1ère règle est de tester la fonctionnalité privée par l'effet qu'elle a sur l'API publique.\nSi la fonctionnalité privée est trop compliquée pour être testée à travers ce qui est public, c'est le signe d'une abstraction manquante. Il faut alors la matérialiser.\nExemple de code dont on a envie de tester la méthode privée getPrice() sans passer par la méthode publique :\nclass Order {\n public generateDescription() {\n return `Name: ${this.name}, ``total price: ${this.getPrice()}`;\n }\n private getPrice() {\n // de la logique compliquée ici\n }\n}\nOn matérialise l'abstraction manquante et on la teste avec de l'output-based testing :\nclass Order {\n public generateDescription() {\n const calculator = new PriceCalculator();\n return `Name: ${this.name}, ``total price: ``${calculator.calculate(\n this.products\n )}`;\n }\n}\nclass PriceCalculator {\n public calculate(products: Products[]) {\n // de la logique compliquée ici\n }\n}\nIl en est de même avec un attribut privé : le rendre public juste pour le tester est un antipattern.\nIl ne faut pas faire fuiter du domain knowledge du code vers les tests : réutiliser le même algorithme dans le test ne permettra pas de remarquer qu'on s'est trompé.\n_ Un exemple simple peut être un code qui fait une addition :\nreturn a + b, et un test qui teste avec l'addition aussi : expect(result).toBe(3 + 2);\nIl vaut mieux vérifier des valeurs pré-calculées sans réimplémenter l'algo : expect(result).toBe(5);\n_ Si on copie l'algo dans le test, alors on aura tendance à mettre à jour en même temps le code et le test en cas de changement, sans pouvoir se rendre compte que l'algo est faux. * Idéalement il faut pré-calculer le résultat à expect dans le test avec l'aide d'un expert métier (quand on n'est pas expert nous-mêmes comme pour l'addition), et en tout cas il ne faut pas obtenir le calcul à partir du code qui est censé être testé.\nLa code pollution consiste à introduire des choses dans le code, qui ne sont utiles que pour le test. C'est un antipattern.\nPar exemple avoir un if(testEnvironment) ... else ... introduit de la pollution qui posera des problèmes de maintenance plus tard.\nOn peut en général régler le problème avec des interfaces : par exemple s'il s'agit d'éviter certaines opérations de log dans les tests en ne loggant pas si on est en env de test, on peut injecter le logger dans le code avec une interface. Dans le test on donnera une version fake du logger qui ne log pas.\nL'interface est une petite pollution aussi, mais elle crée beaucoup moins de danger que des bouts de code dans des if.\nOn est parfois tenté de vouloir stubber/mocker une seule méthode d'une classe qui fait quelque chose de complexe, pour tester ce qui est complexe et éviter qu'elle ne communique avec une dépendance out-of-process. Ceci est un antipattern.\nLa bonne façon de faire est de séparer la logique complexe de la partie qui communique la chose à la dépendance out-of-process (typiquement avec un humble object pattern qui fait le lien entre les deux), et unit tester la logique.\nConcernant la notion de temps utilisée dans le code (new Date()), l'introduire en tant qu'élément statique est, comme dans le cas du logger, un antipattern qui introduit une dépendance partagée dans les tests, et pollue le code.\nLa bonne manière est d'introduire la dépendance temporelle explicitement dans le constructeur ou la méthode appelée.\nOn peut le faire soit sous forme de service appelable pour obtenir la date, soit en passant la valeur pré-générée. Passer la valeur directement est ce qui présente le moins d'inconvénients, à la fois pour la clarté du code, et pour la testabilité."}},"/":{"title":"Reading notes","data":{"":"These are my detailed notes taken while reading software engineering related books. Most of the notes are in french.","complete-notes#Complete notes":"","programming#Programming":"[fr] Effective TypeScript - Dan Vanderkam - 2019\n[fr] Refactoring - Martin Fowler - 2018\n[fr] The Design of Web APIs - Arnaud Lauret - 2019\n[fr] Unit Testing: Principles, Practices and Patterns - Vladimir Khorikov - 2020","architecture#Architecture":"[fr] Get Your Hands Dirty on Clean Architecture - Tom Hombergs - 2019\n[fr] Monolith to Microservices - Sam Newman - 2019\n[fr] Learning Domain-Driven Design - Vlad Khononov - 2021","infrastructure--data#Infrastructure / Data":"[fr] Designing Cloud Data Platforms - Danil Zburivsky and Lynda Partner - 2021\n[fr] Designing Data-Intensive Applications - Martin Kleppmann - 2016\n[fr] Effective Kafka - Emil Koutanov - 2020","product#Product":"[fr] Continuous Discovery Habits - Teresa Torres - 2021\n[fr] Outcomes Over Output - Joshua Seiden - 2019","organization#Organization":"[fr] Learning to Scale - Régis Medina - 2020\n[fr] Reinventing Organizations - Frédéric Laloux - 2015\n[fr] Team Topologies - Matthew Skelton and Manuel Pais - 2019\n[fr] The Five Dysfunctions of a Team - Patrick Lencioni - 2002","in-progress#In progress":"[fr] Turn the Ship Around - L. David Marquet - 2013"}},"/books/effective-kafka":{"title":"Effective Kafka","data":{"1---event-streaming-fundamentals#1 - Event Streaming Fundamentals":"Les systèmes distribués sont plus complexes que les systèmes non distribués, ils déplacent une partie de la complexité du local vers le global.\nLa raison pour laquelle on les utilise c'est qu'ils permettent de décomposer le système en plus petits problèmes qu'on va pouvoir diviser entre plusieurs équipes.\nLa complexité globale peut être réduite par certaines techniques, par exemple les messages asynchrones.\nOn y trouve des échecs partiels, intermittents, ou même byzantins (les nœuds envoient des informations fausses).\nLe problème le plus important est sans doute celui de la consistance.\nL'event-driven architecture (EDA) consiste à avoir des emitters envoyant des notifications d'event à des consumers.\nLes emitters n'ont aucune connaissance des consumers. Et de même les consumers n'ont pas connaissance des emitters.\nLes notifications d'event sont immutables, que ce soit côté emitter ou consumer.\nL'EDA est la manière la plus découplée de faire communiquer des composants entre eux.\nLe seul couplage sera dans le contenu des messages qui transitent.\nImaginons un système d'e-commerce, avec une plateforme BI et un CRM. Il leur suffira de consommer les events d'achat et d'y réagir en toute indépendance.\nParmi les autres possibilités qu'on aurait pour l'exemple e-commerce :\nOn peut les mettre dans un monolith (non-modulaire), mais la complexité risque d'augmenter à mesure que le modèle global est enrichi.\nOn peut utiliser des patterns d'intégration : des messages synchrones envoyés par le composant e-commerce ou par les deux autres. Dans ce cas on se rapproche du distributed monolith parce que les composants ne seront pas indépendants.\nOn peut utiliser la data decapsulation, où les composants BI et CRM viennent lire la DB du composant e-commerce. Dans ce cas on se retrouve dans un mode “get rich quick scheme” qui mène toujours à des pleurs. Le couplage est maximal.\nCet exemple montre que l'EDA scale de manière linéaire, alors qu'avec les approches plus couplées, la complexité explose quand on scale le nombre de composants.\nL'EDA est beaucoup plus résilient que les approches couplées : si un composant est en situation d'échec, il a peu de chances d'impacter d'autres composants.\nSi on reprend l'exemple d'e-commerce :\nDans le cas où le composant d'e-commerce est en situation d'échec, les autres composants vont continuer à pouvoir fonctionner, mais simplement ils ne recevront plus de nouveaux events.\nDans le cas où par exemple le CRM est en situation d'échec, les events continueront d'arriver, et il pourra toujours rattraper son retard dès qu'il est rétabli.\nOn peut aussi prévoir une mesure pour que si le message broker est en situation d'échec, l'émetteur puisse publier les events localement, pour les mettre dans le message broker plus tard.\nDans un système couplé, un composant qui est en échec peut entraîner des correlated failures chez les autres qui en dépendent.\nOn peut aussi avoir des congestive collapses dans le cas où certains composants sont temporairement surchargés, et que les requêtes synchrones mènent à avoir des timeouts, puis à envoyer plus de requêtes.\nL'EDA a aussi des avantages en termes de consistance.\nIl favorise l'ownership d'un élément stateful par un composant unique, les autres composants recevant les notifications d'event ne pouvant pas modifier cet état.\nEn dehors du composant owner, les events sont rejouables dans le bon ordre, garantissant une sequential consistency.\nL'EDA n'est cependant pas adaptée dans certains cas.\nElle n'est pas adaptée aux interactions synchrones.\nPar contre, dans les cas où on peut l'utiliser, elle permet des améliorations significatives des aspects non fonctionnels.\nL'event streaming est un moyen d'obtenir un stream durable et ordonné d'events immutables, délivrés aux consumers qui ont souscrit.\nL'event streaming n'est pas nécessaire pour implémenter l'EDA, qui peut d'ailleurs être implémenté dans un monolith (cf. outils comme React qui sont basés sur des events).\nEn revanche l'event streaming est pertinent comme choix face aux solutions concurrentes (comme les message queues) dans le cadre d'EDA distribuées, parce qu'il a été conçu pour ça.\nL'event streaming supporte nativement l'immutabilité des events.\nIl supporte la garantie d'ordre des events.\nIl supporte le fait d'avoir de multiples consumers.","2---introducing-apache-kafka#2 - Introducing Apache Kafka":"Kafka est une plateforme d'event streaming, mais elle comprend aussi un écosystème entier qui permet l'implémentation d'EDAs.\nL'event streaming est récent comparé aux formes traditionnelles de messaging (MQ-style).\nIl n'y a pas de standard, mais Kafka est le leader du domaine, et son fonctionnement sert de modèle pour les solutions concurrentes comme Azure Event Hubs et Apache Pulsar.\nHistoriquement, Kafka a été open-sourcé en 2011 par LinkedIn, et confié à la fondation Apache.\nIl avait été conçu notamment pour gérer les events d'activité des utilisateurs.\nEn 2019, LinkedIn opérait 100 clusters Kafka, pour un total de 100 000 topics et 7 millions de partitions.\nAujourd'hui Kafka est utilisé par des géants de la tech, pour des usages comme le real-time analytics, la data ingestion, le log aggregation et le messaging pour l'EDA.\nUber par exemple l'utilise pour gérer au total plus de 1000 milliards d'events par jour.\nParmi les usages qui permettent l'EDA, Kafka supporte :\nPublish-subscribe : un emitter publie des events, et plusieurs consumers les consomment sans que ces noeuds se connaissent.\nC'est notamment utilisé pour des microservices avec un faible couplage.\nLog aggregation : un ensemble de sources publient des events sous forme de log (soit applicatifs, soit d'infrastructure), qu'on va ensuite agréger au sein du même topic, pour le consommer dans une DB optimisée pour la lecture, comme Elasticsearch ou HBase.\nLog shipping : il s'agit de streamer des logs depuis une DB master vers un topic où plusieurs DB followers vont consommer et se mettre à jour.\nCe pattern permet notamment d'implémenter l'event sourcing.\nSEDA pipelines : le Stage Event-Driven Architecture est l'implémentation d'une pipeline d'events, où on fait une opération à chaque étape, avant d'émettre un event modifié pour l'étape suivante.\nC'est typiquement utilisé avec les data warehouses, data lakes, le reporting et autres outils de BI.\nOn peut voir le log aggregation comme une forme de SEDA.\nCEP : le Complex Event Processing consiste en un composant qui consomme des events de multiples sources, et en extrait l'information pertinente.\nIl a souvent besoin d'un stockage pour se rappeler les patterns déjà vus et y réagir.\nÇa peut être par exemple pour le trading algorithmique, l'analyse des menaces de sécurité, l'analyse de fraude en temps réel etc.\nEvent-sourced CQRS : Kafka se place entre la DB master et les DBs de projection, en permettant de les alimenter chacune au travers du concept de consumer groups.\nLa différence avec le log shipping c'est que le log shipping opère plutôt à l'intérieur d'un subdomain, alors que le CQRS peut aussi opérer à travers les subdomains.","3---architecture-and-core-concepts#3 - Architecture and Core Concepts":"Kafka est composé de plusieurs types de noeuds :\nBroker nodes : ce sont les composants principaux de Kafka, ils s'occupent des opérations I/O et de la persistance.\nCes nœuds sont des processus Java.\nChaque partition est sous la responsabilité d'un nœud master qui peut écrire dedans, les followers en ont une copie et peuvent être lus.\nUn même nœud peut être master pour certaines partitions, et follower pour d'autres.\nL'ownership peut passer à un autre nœud en cas de besoin (opération spéciale qui le nécessite ou échec du nœud qui était master de la partition).\nConcernant l'attribution de l'ownership, ça se fait d'abord en élisant un des nœuds comme cluster controller, puis celui-ci assigne l'ownership des partitions au gré des besoins.\nAugmenter le nombre de nœuds brokers constitue un moyen de scaler Kafka.\nOn peut améliorer la durability en ayant plusieurs copies de chaque partition (jusqu'à autant que le nombre de nœuds).\nOn peut améliorer l'availability pour les données en lecture.\nZookeeper nodes : Zookeeper est un projet open source distinct de Kafka.\nSes nœuds sont chargés d'élire le broker qui sera le cluster controller, de garantir qu'il n'y en ait qu'un, et d'en réélire un s'il n'est plus opérationnel.\nIls fournissent aussi diverses métadonnées à propos du cluster, par exemple l'état des différents nœuds, des informations de quotas, les access control list etc.\nProducers : les applications clientes qui écrivent dans les topics.\nUn producer communique avec Kafka via TCP, avec une connexion par broker node.\nConsumers : les applications clientes qui lisent des topics.\nLe fonctionnement de Kafka se base sur des notions d'ordering venant de la théorie des ensembles (set theory).\nLe total ordering consiste à avoir un ensemble d'éléments dont une seule configuration est possible.\nOn peut l'illustrer avec un set de nombres entiers { 2, 4, 6 }. Si on enlève l'élément 4, puis qu'on le remet, il ne pourra qu'être à la 2ème place, avant le 6 et après le 2.\nLe partial ordering consiste à avoir un ensemble d'éléments ordonnés selon un critère spécifique, mais dont plusieurs configurations sont possibles pour satisfaire le critère.\nPar exemple, si on a des entiers qu'on veut ordonner de manière à ce que le diviseur d'un nombre soit toujours après ce nombre, et qu'on a [ 2, 3, 4, 6, 9, 8 ], on peut tout autant les organiser en [ 3, 2, 6, 9, 4, 8 ].\nLa notion de causal order indique qu'on respecte le fait que certains éléments ont une relation happened-before entre eux qui est respectée, quel que soit leur ordre d'arrivée à destination.\nCette notion vient de l'étude des systèmes distribués (et non de la théorie des ensembles).\nElle est une forme de partial ordering.\nElle est la conséquence du fait qu'il n'y ait pas d'horloge commune à l'ensemble des nœuds d'un système distribué, et que les events peuvent arriver dans le mauvais ordre.\nLes records sont l'unité principale de Kafka. Ils correspondent aux events.\nIls sont composés :\nD'attributs assez classiques : la value qui peut être sous forme binaire, des headers pour donner des métadonnées, la partition associée au record, l'offset par rapport aux autres records de la partition, un timestamp.\nLa combinaison partition + offset permet d'identifier un record de manière unique.\nL'offset est une valeur entière qui ne peut qu'augmenter, même s'il peut y avoir des gaps entre deux offsets qui se suivent (cf. compaction chapitre 14).\nD'un champ binaire un peu plus inhabituel qui est la key, et qui est utilisée par Kafka pour associer les records avec une même partition.\nKafka est largement utilisé pour traiter des events à l'intérieur d'un bounded context, tout comme les events entre bounded contexts.\nIl est aussi de plus en plus utilisé en remplacement des brokers traditionnels (RabbitMQ, ActiveMQ, AWS SQS/SNS, Google Cloud Pub/Sub etc.). Dans ce cas, les records ne correspondent pas forcément à des events, et on n'est pas forcément dans de l'EDA.\nLes partitions sont l'unité de stream principale qui contient les records.\nLes records d'une même partition sont totally ordered.\nLes records publiés dans une partition par un même producer seront donc aussi causally ordered (la précédence respectée).\nEn revanche, si plusieurs producers publient dans la même partition sans eux-mêmes se synchroniser entre eux, les records de chaque producer seront causally ordered pour un même producer, mais ne le seront pas entre les producers (ça dépendra de qui l'a emporté pour publier plus vite).\nPublier dans plusieurs partitions ne règle pas ce problème : les records de chaque producer ne seront pas causally ordered. Si on veut un tel ordre, il faut un seul producer.\nLes topics sont des unités logiques qui regroupent des partitions.\nVu qu'il s'agit d'une union de partitions qui sont chacune totally ordered, les topics peuvent être considérés comme partially ordered.\nOn peut donc écrire dans les records de plusieurs partitions en parallèle, et n'assurer que l'ordre des records dans chaque partition.\nOn peut indiquer à la main la partition vers laquelle on veut publier un record, mais généralement on indique la key, qui sera hashée pour correspondre avec une partition donnée.\nDans le cas où on réduit le nombre de partitions, les messages peuvent être détruits.\nDans le cas où on augmente le nombre de partitions, on peut perdre l'ordre qu'on voulait conserver avec nos keys, puisque la fonction de hash redirigera vers une autre partition.\nMême si on a un nombre de partitions supérieur au nombre de keys, il est possible que deux keys mènent vers la même partition.\nLa seule chose qui est garantie, c'est qu'avec la même key, et si le nombre de partitions ne change pas, l'ordre sera respecté.\nUn consumer peut souscrire à un topic en tant que membre d'un consumer group, et bénéficier d'un mécanisme de load balancing avec d'autres consumers.\nLe 1er consumer qui souscrit se voit assigner toutes les partitions. Quand un 2ème consumer souscrit au topic, il se voit assigner environ la moitié des partitions qui étaient assignées au 1er. et ainsi de suite.\nLes consumers ne peuvent que lire les events sans impact sur eux.\nUne des conséquences c'est qu'on peut en ajouter beaucoup sans stresser le cluster. Et c'est une des différences par rapport aux brokers classiques.\nIls maintiennent les offsets de là où ils en sont pour chacune des partitions qu'ils sont en train de lire.\nLes consumers de différents consumer groups n'ont pas d'impact les uns sur les autres.\nKafka s'assure qu'il n'y a qu'un consumer d'un même consumer group qui peut lire dans une même partition.\nSi un consumer ne lit plus de messages jusqu'à dépasser un timeout, Kafka assignera ses partitions à un autre consumer, considéré comme sain, du même groupe.\nPour que Kafka puisse réassigner une partition à un autre consumer en gardant le bon offset, ou redonner le bon offset à un consumer qui se reconnecte après s'être déconnecté, il faut que les consumers communiquent leurs offsets à Kafka.\nOn appelle ce processus committing offsets.\nOn peut avoir un contrôle sur le moment où on va faire ce commit, et donc agir sur la garantie de delivery des messages, c'est-à-dire le fait qu'ils soient intégralement traités.\nOn peut passer d'une stratégie at-most-once à une stratégie at-least-once en faisant le commit après l'exécution de la callback au lieu du moment où le message est pris par le consumer.\nPar défaut, Kafka va faire un commit toutes les 5 secondes, sauf si un record est toujours en train d‘être exécuté, auquel cas il attendra la prochaine occasion 5 secondes plus tard.\nOn peut régler cette durée de 5 secondes à une autre valeur avec la configuration auto.commit.interval.ms.\nÇa implique que si le record est exécuté, et que dans les quelques secondes après le cluster bascule la partition sur un autre consumer, on risque de ne pas avoir commité et de réexécuter la callback du record dans le nouveau consumer.\nSi on veut avoir le contrôle sur le moment exact où on veut faire le commit, on peut désactiver le commit automatique (configuration enable.auto.commit à false), et le faire à la main dans le consumer.\nLe commit peut se faire via un canal in-memory asynchrone pour ne pas bloquer le consumer, avec la possibilité de fournir une callback qui sera exécutée par Kafka quand le commit aura été pris en compte\nOu alors le consumer peut aussi utiliser un appel synchrone pour le commit.\nUn cas classique est de traiter les records avec une stratégie at-least-once par batch, qu'on appelle poll-process loop :\nLe consumer garde un buffer de records qu'il prefetch en arrière-plan.\nIl traite les records un par un (ou parfois en parallèle avec un pool de threads si c'est OK d'un point de vue business).\nQuand on arrive au dernier record, il fait le commit de l'offset.\nPuis il prend le batch suivant et recommence.\nMême si c'est moins courant, il est possible de souscrire un consumer sans qu'il soit membre d'un consumer group.\nDans ce cas, il ne bénéficiera pas des divers mécanismes associés aux consumer groups : load balancing, rebalancing en cas d'échec, détection de l'échec par inactivité, persistance de l'offset.\nIl devra indiquer les couples topic/partition auxquels il souscrit, et devra persister ses propres offsets lui-même dans un store.\nIl peut y avoir deux cas d'usages :\nLe besoin d'avoir vraiment le contrôle sur la manière de consommer les messages, en stockant soi-même son offset etc.\nMais ce cas d'usage est très rare, et difficile à implémenter correctement.\nUn consumer éphémère qui est là juste pour monitorer ou débugger un topic, sans avoir besoin de persister d'offsets.\nC'est ce que fait par exemple l'outil Kafdrop qui permet de visualiser les messages présents dans les partitions via une interface web : à chaque fois il attache un consumer sans groupe.","4---installation#4 - Installation":"Il y a 4 méthodes pour installer Kafka (et Zookeeper) :\nEn utilisant les images Docker.\nSi on choisit une autre méthode que Docker, on aura juste besoin d'avoir d'avoir un JDK d'installé.\nLa méthode Kafka dans Docker est la plus immédiate pour avoir Kafka qui tourne, mais elle est aussi connue pour être difficile à configurer si on veut personnaliser.\nEn utilisant un package manager (yum, apt, homebrew etc.)\nEn clonant le dépôt git et en installant depuis les sources.\nEn téléchargeant des binaires sur le site de Kafka.\nIl suffit de télécharger un tar.gz et de le désarchiver, pour obtenir les exécutables de Kafka qu'on peut lancer avec notre JDK.\nLe livre part là-dessus.\nLa configuration de Kafka peut se faire en changeant les fichiers de conf dans le dossier config/.\nOn peut voir les configs prises en compte dans les logs, à chaque fois qu'on démarre Kafka.","5---getting-started#5 - Getting Started":"On a du tooling livré avec Kafka sous forme de scripts shell pour le gérer en CLI.\nOn peut par exemple créer un topic puis y ajouter des records.\nOn peut changer des offsets pour un consumer group.\netc.\nL'auteur déconseille de laisser Kafka créer automatiquement les topics (auto.create.topics.enable à true) pour plusieurs raisons :\nLes valeurs par défaut de Kafka remontent à sa création, et n'ont pas forcément été pensés pour l'usage qu'il a en général aujourd'hui.\nQuand on crée un topic, on devrait décider du nombre de partitions en fonction des critères de parallélisation. Donc un nombre par défaut ne va en général pas être satisfaisant.\nLa création de topic à la lecture est encore plus problématique, puisqu'on va avoir des lecteurs qui croient lire quelque chose et qui ne lisent rien.\nLe lag est la différence entre l'offset qui a été commité par un consumer sur une partition donnée et le high water mark de la partition (c'est-à-dire le dernier record dispo à la consommation).\nLa suppression d'un topic est asynchrone, c'est-à-dire qu'elle sera effectivement réalisée quelque part dans le futur par Kafka, après qu'on l'ait demandée.\nPour nos tests d'intégration, il va donc falloir trouver des solutions :\n1 - Supprimer le consumer group, les offsets enregistrés, ou mettre les offsets au high water mark (tous les trois ont le même effet).\n2 - Tronquer les partitions en avançant le low water mark (le record le plus ancien disponible à la consommation).\n3 - Utiliser des noms de topics uniques, et les supprimer au fil de l'eau (si on ne les réutilise pas, le fait qu'ils soient supprimés de manière asynchrone ne pose problème).\nCette dernière option est celle recommandée par l'auteur.\nSupprimer les offsets pour un consumer group et sur un topic donné, fait que la prochaine fois que ces consumers voudront consommer le topic, ils seront par défaut assignés au dernier record.\nOu au premier en fonction de l'option auto.offset.reset.\nSi on supprimer un consumer group, c'est comme si on supprimait ses offsets pour l'ensemble des topics où il avait consommé des records.\nL'essentiel des classes du client Java se résument à :\n1 - L'interface Producer, l'implémentation KafkaProducer, et la représentation du record ProducerRecord.\n2 - La même chose côté consumer : Consumer, KafkaConsumer, ConsumerRecord.\nEt c'est à peu près la même chose pour les autres clients qui s'en inspirent.\nL'option enable.idempotence à la création du producer permet de garder des séquences pour les couples producer/partition, pour s'assurer qu'un record n'est pas publié deux fois ou dans le mauvais ordre, dans le cas où il y aurait un timeout pendant une publication.\nL'auteur conseille de l'activer.\nIl faut bien penser à fermer la connexion, sinon on risque de monopoliser des ressources côté client et serveur.","6---design-considerations#6 - Design Considerations":"A propos de la séparation des responsabilités entre producers et consumers.\nDans le cas d'un event-oriented broadcast, c'est le producer qui a la responsabilité de la configuration du topic et du format des données publiées.\nC'est utile pour que les producers ne connaissent pas du tout les consumers, et qu'on reste sur du couplage faible.\nLe fait qu'on puisse avoir plusieurs consumers aux intérêts différents montre qu'il est plus pertinent que le producer ait la responsabilité des messages.\nPour autant, on peut se demander comment faire en sorte que les consumers soient tous satisfaits par le modèle proposé par le producer.\nOn peut mettre en place du topic conditioning, c'est-à-dire compartimenter les problèmes liés à chaque consumer avec une architecture SEDA, contenant pour chaque consumer (ou groupe de consumers aux intérêts communs), un module de conditionnement publiant à son tour dans un topic pour le consumer visé.\nCette solution permet de séparer les responsabilités, et laisser le producer avec son modèle, et chaque consumer avec le sien.\nPour du peer-to-peer messaging, c'est au contraire le consumer qui a la responsabilité de la configuration du topic et du format de données.\nLe consumer envoie des commandes au producer, pour que celui-ci lui prépare des données qu'il mettra dans Kafka.\nDans tous les cas, les flows doivent être designés avec soin, en prenant en compte les besoins des producers et des consumers.\nConcernant la question du parallélisme dans le cas où on veut laisser plusieurs consumers consommer depuis plusieurs partitions, il y a des facteurs à prendre en compte pour obtenir quelque chose de performant.\nL'organisation des partitions d'un topic est consumer-driven, du fait du design de Kafka. Le consumer se pose la question de la bonne clé de partitionnement.\nEn pratique, le consumer doit trouver une entité suffisamment stable pour que son identifiant puisse servir de clé de partitionnement.\nPar exemple, si on a des tournois de football, avec des events représentant ce qui se passe dans le jeu, on peut prendre le match comme entité stable, et avoir tous les events d'un même match ordonnés dans une même partition.\nSi on garde l'exemple mais qu'un consumer est intéressé par le déroulement du tournoi, alors il nous faudra garder l'ordre des matchs, et donc choisir comme entité stable le tournoi.\nMais on aura alors moins de possibilités de parallélisation puisqu'on ne pourra plus paralléliser les matchs.\nL'autre possibilité c'est de laisser le consumer qui a besoin de l'ordre des tournois le reconstituer lui-même, avec des infos qu'il a dans les events.\nSe pose ensuite la question du nombre de partitions du topic.\nPour rappel on ne peut pas enlever de partitions sans détruire de messages, et en rajouter fait que la fonction de hash n'envoie plus dans les mêmes partitions qu'avant le rajout (donc il vaut mieux éviter si on veut garder l'ordre des messages).\nUne solution peut être d'avoir dès le début un nombre suffisamment élevé de partitions par topic, pour ne jamais avoir à les augmenter.\nAttention cependant, trop de partitions peut causer des problèmes de performance.\nConfluent recommande un nombre de partitions maximal par broker de 100 x b x r partitions (avec b le nombre de brokers du cluster, et r le facteur de réplication).\nSi on atteint le nombre maximal de partitions qu'on avait prévu, une technique peut être de créer un nouveau topic avec plus de partitions, et de copier l'ensemble des messages de l'ancien topic vers le nouveau. Ça nécessite un peu d'effort.\nLe nombre de consumers dans un consumer group doit être au moins aussi grand que le nombre de partitions si on veut profiter du maximum de parallélisme.\nPar contre, allouer un tel nombre peut aussi mener à du gâchis de ressources, vu que le broker ne fonctionne pas forcément en flux tendu.\nOn peut alors plutôt allouer un nombre variable de consumers au groupe, basé sur l'activité du cluster.\nEnfin on peut envisager d'avoir du parallélisme à l'intérieur des consumers, en gérant plusieurs threads, pour traiter plusieurs records en même temps.\nA propos de la question de la delivery des messages.\nOn parle ici de “delivery” au sens où les messages sont traités jusqu'au bout par les consumers, pas juste du fait qu'ils soient disponibles à la lecture (ça, ils le restent de toute façon pour tous les consumers dès lors que la publication a marché).\nOn peut avoir une delivery at-most-once, en faisant le commit dès le début de la lecture.\nC'est utile dans les cas où la perte occasionnelle de donnée n'est pas grave, et ne laisse pas le système consommateur dans un état inconsistant de manière permanente.\nCa peut être aussi parce que faire l'action deux fois pose problème, alors le que le fait de la rater de temps en temps non.\nOn peut avoir une delivery at-least-once, en ne faisant le commit qu'après exécution complète de la callback du consumer.\nC'est utile dans le cas où la perte de donnée n'est pas acceptable, et où on est prêt à recommencer certains messages pour l'éviter.\nPar contre on doit être prêt à avoir la callback potentiellement exécutée plusieurs fois.\nEt enfin, si on veut une delivery exactly-once, on ne peut malheureusement pas compter sur le message broker à lui seul : on doit s'assurer d'avoir un flow idempotent.\nOn pourrait le vouloir pour avoir à la fois la consistance parce que la perte de donnée ou le fait de ne pas faire une action n'est pas acceptable, mais en même temps où le fait de le faire deux fois n'est pas acceptable non plus.\nPour réussir ça, on a besoin d'avoir une idempotence de bout en bout, c'est à dire que :\nLa callback du consumer ne doit faire que des changements idempotents. Par exemple un update en DB qui ne change pas l'état de la DB quand il est joué plusieurs fois.\nLe consumer doit vérifier si les side-effects qu'il fait ont déjà été faits pour ne pas les refaire une 2ème fois. Par exemple, Kafka offre un mécanisme de transaction qui permet de ne publier qu'une fois dans un topic sortant pour un message d'un topic entrant.\nDans le cas où on ne peut pas savoir si le side-effect a déjà été fait ou pas, il faut que le side-effect lui-même soit rendu idempotent de bout en bout.","7---serialization#7 - Serialization":"Le client Java a des serializers de base et une interface à implémenter pour créer des serializers Kafka custom.\nPour l'auteur, même si cette approche est idiomatique, il vaut mieux avoir Kafka et tout ce qui y est lié isolé dans une couche de messaging pour que la logique business n'y soit pas liée et soit testable.\nL'auteur préfère laisser la sérialisation côté logique business, et donc conseille de ne pas utiliser les serializers custom de Kafka dans ce cas.\nEt de la même manière, les choses spécifiques à Kafka comme le fait de mettre l'ID des customers comme clé, doivent être dans la couche de messaging pour pouvoir être mis en commun entre les use cases.\nQuand on est en mode commit manuel, on peut appeler la fonction qui fait le commit de manière asynchrone sans l'attendre.\nÇa aura pour effet d'avoir plus d'offsets pas encore commités mais un throughput plus élevé.\nOn respecte quand même le at-least-one delivery.\nDans le cas où on utilise le mécanisme de poll-process loop (où on consomme les messages par batch), le client Java va avoir deux threads : un pour aller chercher plus de records et un autre pour faire le processing des records qui sont déjà là.\nIl s'agit là d'un mécanisme de pipelining, où la 1ère étape va chercher de la donnée pour la mettre dans le buffer suivant jusqu'à ce que le buffer soit plein, auquel cas elle attend avant de continuer.\nL'auteur propose une version encore plus parallélisée, en ajoutant une 3ème étape dans la pipeline pour séparer la désérialisation du reste du traitement du message.\nL'avantage c'est que ça peut augmenter le throughput, mais l'inconvénient c'est une utilisation plus intensive du CPU.\nIl faut créer un thread à la main, et gérer la communication inter-thread à travers un buffer, avec tous les edge cases liés au parallélisme.\nSelon l'auteur, cette technique a du sens parce que :\nL'utilisation de Kafka est souvent associée à des cas d'usages qui ont besoin de performance.\nElle ajoute de la complexité, mais qu'on n'a à faire qu'une fois et qu'on peut isoler dans un adapter qu'on réutilise.\nCôté publisher ça aurait moins de sens vu que la sérialisation est moins coûteuse que la désérialisation.\nIl peut être pertinent de filtrer des messages au niveau de la couche adapter du consumer Kafka.\nPar exemple, si le topic contient plus de messages que ce que le use-case qui le consomme peut ou veut désérialiser.\nÇa peut être parce que le producer publie les messages plusieurs fois, en indiquant la version du schéma dans le header, et qu'on ne veut en lire qu'une version sans avoir à parser les autres.\nÇa peut aussi être un topic qui contient plusieurs types de messages, dont on ne veut traiter qu'un type.","8---bootstrapping-and-advertised-listeners#8 - Bootstrapping and Advertised Listeners":"Chaque partition a un leader broker, et n follower brokers qui contiennent sa donnée (avec n + 1 étant le replication factor).\nPour pouvoir écrire un record, un client publisher doit l'envoyer au broker leader de la partition qui l'intéresse.\nLe leader transférera aux followers, mais on ne peut pas compter sur un des followers pour transférer d'abord au leader.\nÇa veut donc dire que le client devra avoir une connexion directe avec quasi tous (ou même tous) les brokers, vu que tous les brokers peuvent être des leaders de partitions et qu'il risque de vouloir en lire plusieurs.\nLes brokers sont au courant de la topologie du cluster parce qu'ils ont l'info partagée via ZooKeeper.\nLe client peut donc demander la liste des adresses IP des brokers à n'importe lequel d'entre eux. Et donc, pour peu qu'il ait au moins une adresse de broker valide, il peut réobtenir toutes les autres.\nLe client est initialement fourni avec une bootstrap list des brokers, et ensuite se débrouille pour la mettre à jour en leur demandant.\nCette technique de base de demander la liste des adresses à au moins un broker dont on a l'adresse valide n'est pas super fiable : si le client n'a plus aucune adresse valide parce qu'elles ont toutes changé, il est coincé.\nCe que fait la communauté pour répondre à cette problématique c'est d'utiliser des alias DNS, pointant vers les bonnes adresses IP des brokers.\nLa spécification DNS permet même d'indiquer un seul nom qui sera associé à une liste d'adresses IP pointant vers chacun des brokers.\nIl y a un problème classique de configuration auquel beaucoup de monde se heurte, et qui empêche la connexion du client aux brokers : le client demande la liste des adresses, et le broker lui répond des adresses en localhost.\nLa solution est de configurer les advertised listeners dans le fichier config/server.properties.\nLes propriétés sont initialement commentées dans le fichier, et donc c'est les valeurs par défaut qui s'appliquent (on peut les retrouver dans la documentation de Kafka).\nadvertised.listeners permet d'indiquer les URI qui seront envoyées aux clients qui demandent la liste des adresses des brokers. C'est ça qu'il faut configurer avec le bon hostname pour résoudre le problème de config.\nDans le cas où on a des clients situés dans des environnements réseau différents, on a besoin de leur advertiser des adresses différentes pour les mêmes brokers.\nC'est le cas par exemple si on a un VPC (virtual private cloud) avec le cluster Kafka et des clients, et d'autres clients situés à l'extérieur et ne pouvant pas accéder aux adresses IP internes au VPC.\nDans ce cas, on va pouvoir configurer plusieurs URI (ou plutôt sockets) sur lesquels écoute chaque broker (dans listeners), et plusieurs URI qui sont advertised (dans advertised.listeners).\nIl faut faire attention à indiquer des ports différents pour chacune des URI si on ne veut pas de problèmes.\nLes problématiques de bootstrapping se posent largement dans les environnements conteneurisés. La simple utilisation de docker-compose nous amène à avoir l'équivalent d'un VPC interne aux containers lancés par docker-compose, et un mapping de port vers la machine hôte.\nExemple de config Kafka dans un docker-compose :\nkafka:\n image: bitnami/kafka:2\n ports:\n - 9092:9092\n environment:\n KAFKA_CFG_ZOOKEEPER_CONNECT: zookeeper:2181\n ALLOW_PLAINTEXT_LISTENER: \"yes\"\n KAFKA_LISTENERS: >-\n INTERNAL://:29092,EXTERNAL://:9092\n KAFKA_ADVERTISED_LISTENERS: >-\n INTERNAL://kafka:29092,EXTERNAL://localhost:9092\n KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: >-\n INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT\n KAFKA_INTER_BROKER_LISTENER_NAME: \"INTERNAL\"\n depends_on:\n - zookeeper\nOn définit ici deux protocoles propres à Kafka (et associés au type PLAINTEXT, donc non sécurisés) : un qu'on appelle INTERNAL pour l'URI depuis le réseau interne des containers docker-compose, et un autre qu'on appelle EXTERNAL pour le réseau de l'hôte.\nKAFKA_LISTENERS est l'équivalent de listeners dans config/server.properties, c'est-à-dire les sockets sur lesquels le broker écoute.\nOn choisit deux ports différents qui permettent de différencier les connexions internes et externes, et on indique qu'on écoute sur toutes les interfaces possibles (en n'indiquant aucun hostname ni adresse IP).\nKAFKA_ADVERTISED_LISTENERS est l'équivalent de advertised.listeners, c'est-à-dire les adresses URI communiquées aux clients pour joindre le broker.\nOn indique bien le hostname localhost aux clients du réseau externe, et le hostname kafka aux clients du réseau interne (le nom des containers sert aussi de hostname dans docker-compose).\nKAFKA_INTER_BROKER_LISTENER_NAME permet d'indiquer quel protocole doit être utilisé pour la communication avec les autres brokers du cluster.\ndepends_on permet d'indiquer l'ordre dans lequel on start les containers dans docker-compose.","9---broker-configuration#9 - Broker Configuration":"La configuration peut se faire sur 4 entités de Kafka : les brokers, les topics, les clients et les users.\nIl existe une configuration statique et une configuration dynamique.\nHistoriquement la configuration dynamique a été introduite pour faciliter l'administration de gros clusters, et pour ne plus avoir besoin de restart.\nLa communauté a décidé qu'enlever la configuration statique était trop radical, donc elle a été gardée en fallback.\nLa configuration statique se fait en changeant le fichier config/server.properties et en redémarrant le broker.\nLa configuration dynamique se fait via l'admin API de Kafka, au niveau du broker ou du cluster entier.\nElle est stockée dans Zookeeper, mais ne nécessite pas la communication directe avec Zookeeper pour faire des modifications de config.\nCôté précédence, c'est d'abord la config dynamique par entité qui prend le pas, puis la config dynamique au niveau du cluster, et enfin la config statique.\nSi rien n'est défini, les valeurs par défaut s'appliquent.\nDans le cas de propriétés dépréciées et remplacées par d'autres, les propriétés dépréciées sont prises en compte si elles sont utilisées, et sinon c'est la valeur par défaut des nouvelles propriétés qui est prise en compte.\nQuelques infos sur les changements de config des brokers.\nSur la configuration statique.\nToutes les propriétés de config/server.properties sont optionnelles, sauf zookeeper.connect qui contient la liste des adresses des nœuds ZooKeeper.\nIl est considéré comme une bonne pratique de spécifier la propriété broker.id qui représente l'identifiant du broker. Si on ne le fait pas, ZooKeeper assignera un ID automatiquement à chaque broker (par défaut en commençant par 1001).\nPour changer cette propriété, il faut :\nD'abord arrêter le broker.\nFaire le changement dans server.properties.\nFaire le changement dans le fichier meta.properties (qui se trouve dans le dossier de log du broker), ou même supprimer le fichier meta.properties qui sera régénéré.\nLe dossier de log contient des fichiers essentiels avec l'info des partitions et des records (rien à voir avec du logging, on parle des données de Kafka).\nSon path est configurée avec l'option log.dirs, par défaut c'est /tmp/kafka-logs.\nRedémarrer le broker.\nSur la configuration dynamique.\nOn peut changer la config via l'outil CLI fourni par Kafka sous forme de script bash : kafka-configs.sh, ou via une librairie cliente tierce qui va se connecter à Kafka.\nPar exemple pour afficher la liste des configurations dynamiques pour le broker 1001 sur un Kafka qui tourne localement :\n./kafka-configs.sh\n --bootstrap-server localhost:9092\n --entity-type brokers\n --entity-name 1001\n --describe\nIl faut faire attention avec les configurations dynamiques, on peut facilement mettre un cluster par terre si on fait une mauvaise manip.\nQuand on modifie une config pour tout le cluster, c'est une bonne pratique de la modifier d'abord pour un broker, au cas où elle aurait un impact non souhaité qui serait du coup plus limité.\nA propos de la configuration des topics.\nIls peuvent être configurés statiquement via config/server.properties, ou dynamiquement au niveau du cluster (une configuration de topic par broker n'aurait pas de sens).\nOn peut aussi modifier dynamiquement certaines propriétés par topic.","10---client-configuration#10 - Client Configuration":"La configuration du client est beaucoup plus sensible, en partie parce qu'elle tombe sous la responsabilité des développeurs applicatifs.\nEn général la configuration des brokers se fait par des personnes spécialistes de l'infra, gérant d'autres éléments d'infrastructure, et connaissant la manière de gérer les risques.\nOn voit aussi de plus un shift vers les versions de serveurs Kafka pré-configurées. Ça ne peut pas être le cas des clients.\nLa plupart des problèmes avec Kafka viennent d'une mauvaise utilisation côté client, parce que les développeurs ne le connaissent pas assez bien.\nExemple : il est notoire que Kafka offre des garanties importantes pour ce qui est de la durabilité des records. Mais en réalité ça dépend des paramètres.\nIl y a déjà la question du stockage, elle-même influencée par le nombre de brokers.\nEt ensuite il y a des configurations côté client :\nLe replication factor et quelques autres pour ce qui est de s'assurer que la donnée reste en cas de problème avec certaines machines.\nLe nombre d'acknowledgements que le broker leader de la partition doit demander avant de considérer le record comme validé, et le fait d'attendre soi-même l'acknowledgement du leader avant de considérer le message comme publié.\nLes développeurs imaginent aussi que le comportement par défaut de Kafka optimise la garantie d'ordre et de delivery des records. Mais ces valeurs sont issues de l'utilisation initiale de Linkedin qui avait surtout besoin de performance dans son cas d'usage.\nLa 1ère règle de l'optimisation avec Kafka est : ne le faites pas.\nLa plupart du temps, les configurations qui offrent des garanties vis-à-vis des records n'ont pas un si grand impact que ça. On peut attendre d'en avoir vraiment besoin.\nPour ce qui est des configurations communes à tous les types de clients (producer, consumer, admin).\nbootstrap.servers permet de contacter les brokers, mais ensuite le plus important c'est que les brokers envoient les bonnes adresses (cf. le chapitre précédent).\nclient.dns.lookup donne la possibilité d'utiliser des alias DNS liés à plusieurs adresses.\nclient.id permet de définir l'identifiant du client, comme on l'a fait pour le serveur dans le chapitre d'avant. Ça permet la traçabilité, et la gestion de quotas.\nretries indique le nombre de fois qu'on va recommencer une opération qui se termine par une erreur transiente, c'est-à-dire qui peut potentiellement ne pas se reproduire en réessayant.\nretry.backoff.ms indique la durée d'attente avant de réessayer.\nPar défaut on bourrine, en recommençant un nombre infini de fois toutes les 100 ms.\nL'autre possibilité c'est en gros de limiter les retries, en ayant conscience que du coup on se retrouvera à un moment où un autre à avoir des opérations qui sont en erreur pour des raisons temporaires. Mais on n'aura pas bloqué pendant longtemps.\nQuand on veut utiliser Kafka dans des tests d'intégration, il faut prendre en compte que le fait de le lancer dans un environnement virtualisé type Docker va ralentir considérablement son démarrage.\nLe fait que Kafka écoute sur le port ne suffit pas pour qu'il soit prêt à accepter des requêtes. Il peut donc falloir attendre un certain temps au début des tests pour qu'il démarre.\nEt c'est encore pire avec Docker sur MacOS.\nPour ce qui est de la configuration du producer.\nacks permet d'indiquer le nombre d'acknowledgements qu'on veut attendre de la part du broker leader avant de considérer que le message est publié.\n0 indique qu'on ne veut pas attendre du tout.\n1 indique qu'on veut attendre que le leader lui-même ait écrit le record dans son log à lui.\nC'est la valeur par défaut si enable.idempotence est false.\n-1 permet d'indiquer qu'on veut attendre que le leader mais aussi tous les followers aient écrit le record dans leurs log.\nC'est la valeur par défaut si enable.idempotence est true.\nmax.in.flight.per.connection indique le nombre de records qu'on veut pouvoir publier (par défaut 5), avant d'avoir à attendre le nombre d'acknowledgements qu'on a indiqué dans acks.\nAugmenter ce nombre permet de se prémunir contre la lenteur du réseau, vu qu'attendre la confirmation à chaque fois qu'on veut publier nous empêche de publier vite.\nPar contre, on risque de ne pas publier dans le bon ordre pour les records entre deux acknowledgements.\nIl suffit qu'un record A ait une erreur transiente qui est retentée puis réussie, mais que le record suivant B ait réussi immédiatement et avant le record A. Ce qui inverse l'ordre de publication de A et B.\nPour ne pas avoir le problème il faudrait soit avoir max.in.flight.per.connection à 1 (attendre la confirmation à chaque publication), soit retries à 0 (ne jamais réessayer les erreurs transientes).\nEn réalité il y a une 3ème option qui est d'activer enable.idempotence, où Kafka va utiliser un mécanisme qui remet le bon ordre pour les records qui arrivent avec le mauvais ordre.\nenable.idempotence.\nPermet de garantir que :\nLes records soient publiés au plus une fois (donc dédupliqués).\nLes records sont publiés dans l'ordre indiqué par le client producer.\nLes records sont d'abord persistés sur l'ensemble des réplicas avant d'envoyer l'acknowledgement.\nIl nécessite que (si ces propriétés ne sont pas renseignées, elles seront mises aux bonnes valeurs par défaut, mais il ne faut juste pas de conflit) :\nmax.in.flight.per.connection soit entre 0 et 5.\nretries soit plus grand que 0.\nacks soit à -1.\nPour ce qui est du problème de duplication, il peut se produire dans le cas où le producer subit un timeout alors que le message a été pris en compte par le serveur, mais avant qu'il ne reçoive l'acknowledgement. Il va donc réessayer d'envoyer le message juste après, ce qui fera un doublon.\nLe mécanisme passe par l'attribution à chaque message par le producer, d'un ID qui s'incrémente monotoniquement. Et le broker maintient le dernier ID traité pour chaque couple [ producer ID, partition où on publie le record ].\nSi le record qui arrive est identifié comme étant déjà arrivé, il est ignoré comme duplicata.\nSi le record qui arrive a un ID plus grand qu'un incrément de 1 par rapport au dernier message traité, alors le message est considéré comme étant dans le mauvais ordre, et le broker répond une erreur indiquant au producer qu'il faut le requeuer.\ncompression.type permet d'indiquer l'algo de compression qui sera utilisé par le producer (détaillé dans le chapitre 12).\nParmi les possibilités :\nnone\ngzip\nsnappy (optimisé pour le throughput, au dépend de la compression)\nlz4 (optimisé aussi pour le throughput, surtout la décompression)\nzstd (nouvel algo, qui est censé faire un bon ratio throughput / performance).\nkey.serializer et value.serializer servent à indiquer la sérialisation des clés et valeurs des records (cf. le chapitre 7).\npartitioner.class permet d'indiquer une classe Java qui va définir une manière différente de la manière par défaut d'associer les records et les partitions.\nLa manière par défaut va, dans l'ordre :\n1 - Si la partition est indiquée explicitement dans la publication du record, elle sera utilisée.\n2 - Sinon, si on a indiqué une clé, la clé sera hashée pour déterminer la partition.\n3 - Sinon, si le batch courant a une partition qui lui est assignée, on utilise cette partition.\n4 - Sinon, on assigne une partition au batch et on l'utilise.\nLe 3 et 4 ont été introduits dans Kafka plus récemment, et permettent, dans le cas où on n'a pas de préférence d'ordre liée à une clé, de n'impliquer qu'un broker pour les records d'un batch. Ça améliore les perfs par 2 ou 3, tout en assurant une distribution entre brokers quand on a un grand nombre de batchs.\nLe client Java a aussi deux autres classes disponibles :\nRoundRobinPartitioner permet d'alterner entre les brokers, sans prendre en compte la clé.\nUniformStickyPartitioner permet de garder les records d'un même batch pour une même partition, sans prendre en compte la clé.\nOn peut aussi donner une classe perso, mais l'auteur conseille d'envisager aussi d'encoder notre ordre custom dans une clé.\ninterceptor.classes permet de définir des classes Java qui vont faire quelque chose de particulier à l'envoi et à l'acknowledgement.\nÇa peut être utile pour le côté “plugin” réutilisable, parce qu'on est sur de l'AOP (Aspect Oriented Programming).\nOn peut par exemple l'utiliser pour ajouter une couche qui fait du logging, du tracing, de l'analyse de message pour empêcher la fuite de données etc.\nAttention par contre : les exceptions dans les interceptors ne sont pas propagées.\nGlobalement si on y met quelque chose, il vaut mieux que ce soit du code simple et non bloquant.\nmax.block.ms permet d'indiquer un timeout au processus de publication (par défaut 60 secondes).\nbatch.size permet d'attendre d'avoir une certaine taille de messages (par défaut 16 KB) avant d'envoyer un batch de publication.\nlinger.ms fait la même chose au niveau du temps (par défaut 0 ms) en ajoutant un temps minimal à attendre avant d'envoyer un autre batch.\nL'intérêt est de faire moins de requêtes au serveur et donc d'augmenter le throughput.\nrequest.timeout permet d'indiquer un timeout vis-à-vis de la réponse du broker pour faire l'acknowledgement (par défaut 30 secondes), avant de réessayer ou d'indiquer la publication comme échouée.\ndelivery.timeout permet d'indiquer un temps global pour une requête de publication, qui englobe l'envoi, les retries, et la réponse du serveur.\nPar défaut, c'est 120 secondes.\nIl doit être supérieur aux autres timeouts réunis.\ntransaction.id et transaction.timeout.ms permettent de gérer le comportement des transactions (cf. le chapitre 18).\nPour ce qui est de la configuration du consumer.\nkey.serializer et value.serializer servent à indiquer la désérialisation des clés et valeurs des records (cf. le chapitre 7).\ninterceptor.classes permet de faire la même chose que côté consumer, en traitant les records par batch.\nUne des choses les plus importantes à régler, c'est la taille de ce qu'on va aller chercher en une requête. Ça se configure en plusieurs propriétés.\nPlus on prendra de données, et plus le throughput sera grand, mais moins on aura un bon délai de propagation de bout en bout d'un record.\nLa propriété timeout donnée à Consumer.poll() permet de limiter son temps d'exécution.\nfetch.min.bytes (par défaut 1) permet de demander au broker d'attendre d'avoir au moins un minimum de données à envoyer avant de répondre.\nEn réalité, le broker doit quand même envoyer une requête même s'il n'a pas assez de données, dans le cas où il dépasse un timeout fixé par fetch.max.wait.ms (par défaut 500 ms).\nfetch.max.bytes (par défaut 50 MB) indique au broker à partir de quelle taille il doit arrêter d'ajouter des données.\nVu qu'un record à lui seul (et donc à fortiori un batch) peut de toute façon dépasser cette taille, la limite n'est qu'indicative.\nLa même propriété limite existe pour la taille des partitions : max.partition.fetch.bytes (par défaut 1 MB).\nCette propriété permet de limiter l'impact des partitions “gourmandes”, en laissant de la place aux partitions qui ont moins de données.\nIntéressant à savoir : les brokers ne font en général pas de traitement sur les batchs. Les batchs sont envoyés par les producers, stockés tels quels, et envoyés tels quels aux consumers. C'est un choix de design de Kafka pour garantir une grande performance.\nmax.poll.records (par défaut 500) permet de limiter le nombre de records retournés par Consumer.poll().\nContrairement aux autres propriétés, celle-ci n'impacte pas le broker. C'est le client qui reçoit le même nombre de records par batch, et il va lui-même limiter ceux qu'il rend disponible. Il bufferise les autres pour les rendre disponibles à l'appel suivant.\nElle est là pour éviter que le client n'ait à traiter trop de records, et ne puisse pas appeler à nouveau poll() avant max.poll.interval.ms.\ngroup.id permet d'indiquer le groupe d'un consumer. Si on ne le fournit pas, il deviendra sans groupe, et ne pourra pas bénéficier des mécanismes de d'assignation automatique de partition, détection des échecs, ni faire de commits au serveur pour sauvegarder son offset.\ngroup.instance.id consiste à indiquer un identifiant à un consumer, unique dans son groupe, rendant le consumer static. L'effet est que si le consumer n'est plus là, sa partition n'est pas réassignée, mais reste en attente de son retour.\nC'est pour éviter les rebalancing trop fréquents dans un contextes de manque d'availability transient.\nPour en savoir plus : chapitre 15.\nLa détection d'échecs est contrôlée par la combinaison de heartbeat.interval.ms, session.timeout.ms et max.poll.interval.ms.\nCe sujet fait partie des sujets délicats, source de nombreux problèmes.\nheartbeat.interval.ms (par défaut 3 secondes) contrôle la fréquence à laquelle le consumer envoie des heartbeats.\nLe broker coordinator du groupe de son côté vérifie que le consumer n'envoie pas son prochain heartbeat après le délai de session.timeout.ms (par défaut 10 secondes). Sinon il l'expulse et réassigne ses partitions dans le groupe.\nmax.poll.interval.ms (par défaut 5 minutes) est le délai maximal pour qu'un consumer rappelle poll(). S'il ne l'a pas fait, il va lui-même arrêter d'envoyer des heartbeats et demander à quitter le groupe.\nSi le consumer est statique, il arrête les heartbeats mais ne demande pas à quitter le groupe. Il sera évincé par le broker s'il dépasse la session.timeout.interval sans avoir réémis de heartbeats.\nLe but de ce comportement est d'éviter les situations où plusieurs consumers traitent les mêmes messages.\nauto.reset.offset permet d'indiquer ce qui se passe quand un consumer n'a pas d'offsets pour la partition qu'il consomme.\nLes options sont : earliest pour partir du low water mark, latest pour partir du high water mark, et none pour renvoyer une exception.\nLes offsets sont stockés par le group coordinator dans un topic nommé __consumer_offsets. Ce topic a un temps de rétention comme n'importe quel topic (par défaut 7 jours).\nL'offset peut ne pas exister si :\n1 - C'est le début de la formation du groupe et que la partition n'a pas encore été lue par lui.\n2 - Quand rien n'a été consommé sur cette partition par le groupe (et donc aucun offset n'a été commité dans __consumer_offsets) depuis plus longtemps que le délai de rétention de __consumer_offsets.\n3 - Quand on a un offset qui pointe vers un record qui est dans un topic où le délai de rétention est plus faible, et a été dépassé. Donc l'offset pointe vers le vide.\nenable.auto.commit permet d'indiquer si le commit automatique est activé pour un consumer. Il s'agit d'envoyer un commit jusqu'au dernier record traité par le dernier l'appel à poll(), pour mettre à jour son offset.\nPar défaut le client commit toutes les 5 secondes (temps réglable avec auto.commit.interval.ms).\nSi ça marchait vraiment comme ça (tel que le dit la doc), le client mettrait à jour son offset au dernier record reçu dans le batch envoyé par le dernier appel à poll(), alors même qu'il n'a pas forcément terminé de traiter le batch.\nEn réalité, l'implémentation règle le problème en envoyant le commit dans le même thread que celui qui traite les records, et seulement après que le batch ait été traité.\nMais ce comportement n'est pas garanti vu que la doc ne dit pas ça, Kafka pourrait à tout moment mettre à jour le comportement pour faire le commit dans un autre thread toutes les 5 secondes.\nPour éviter les problèmes, l'auteur conseille de faire le commit à la main.\nisolation.level permet d'indiquer le type de comportement d'une transaction vis-à-vis du consumer.\nLa valeur read_uncommitted va renvoyer tous les records sans prendre en compte les transactions.\nLa valeur read_committed va renvoyer les records qui ne font pas partie des transactions, et ceux qui font partie de transactions validées, mais pas ceux qui font partie de transactions qui ne sont pas encore validées.\nPour garantir l'ordre, tous les records qui doivent se trouver après les records qui sont dans des transactions non validées, seront aussi bloqués le temps de la transaction.","11---robust-configuration#11 - Robust Configuration":"Kafka fait le choix d'émettre un warning dans le cas où on donne un mauvais nom de propriété de configuration.\nPour éviter les typos, on peut utiliser les constantes pour donner les valeurs.\nNDLR : en TypeScript les clients sont typés.\nSi la propriété vient d'un fichier de config qui n'est pas du code, il n'y aura pas de check à la compilation.\nDans ce cas, il nous faut vérifier le contenu au runtime.\nL'auteur propose de faire une classe de validation, qui propose des méthodes de type fluent chaining.\nfinal var config = new TypesafeProducerConfig()\n .withBootstrapServers(\"localhost:9092\")\n .withKeySerializerClass(StringSerializer.class)\n .withValueSerializerClass(StringSerializer.class)\n .withCustomEntry(\n ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, 1\n );","12---batching-and-compression#12 - Batching and Compression":"Les batchs sont traités par Kafka comme un processus de bout en bout : le producer envoie les records par batchs, ils sont stockés comme tels, puis envoyés au consumer sous le même format.\nCa permet de recourir à la zero-copy optimization, où les données sont copiées depuis le réseau vers le disque, puis à nouveau vers le réseau, sans que le CPU n'ai eu à intervenir pour transformer la donnée.\nDans le cas où TLS serait activé, la zero-copy optimization ne serait plus vraiment possible puisqu'il faudrait au moins déchiffrer ce qu'envoie le producer et chiffrer ensuite pour envoyer au consumer, ce qui utilise du CPU proportionnellement à la donnée.\nCe processus de création de batchs arrive quand il y a beaucoup de records à traiter successivement : Kafka va batcher les records qui sont en attente d'être envoyés (en limitant la taille des batchs à batch.size). Quand le client veut publier au compte goutte, il ne fait pas de batchs.\nlinger.ms peut permettre d'avoir plus souvent des batchs : pendant ce temps qu'on attend, des records peuvent s'accumuler pour être batchés.\nKafka compte beaucoup sur du fine tuning fait par des admins pour la situation précise dans lequel il est utilisé.\nLe batching a encore plus d'intérêt quand on utilise la compression.\nIl n'est pas inhabituel d'obtenir des ratios de compression entre x5 et x7 sur du JSON.\nL'essentiel de la performance de compression est obtenue déjà avec de petits batchs.\nLa compression est réalisée par le producer, et la décompression dans le consumer, donc ça a l'avantage de ne pas mettre de charge sur le serveur.\nLe serveur offre aussi la possibilité de modifier la compression de son côté si on le veut vraiment : avec la propriété compression.type côté broker, qui a par défaut la valeur producer, et peut prendre une valeur de type de compression (gzip, snappy etc.).\nL'auteur recommande de toujours activer la compression pour les records textuels et binaires (sauf si on sait qu'ils ont une très grande entropie, c'est-à-dire que leur contenu est très variable et difficilement prévisible, donc difficilement compressible).\nCôté algo, il conseille les heuristiques suivantes :\nSi on a des clients legacy (avec une version inférieure à 2.1.0) :\nDe base LZ4.\nSi le réseau est identifié comme un bottleneck : Gzip.\nSi on a des clients récents :\nDe base LZ4.\nSi le réseau est identifié comme un bottleneck : ZStandard.\nBien sûr, si on a un vrai besoin de fine tuner la performance, il faut faire des benchmarks avec chacun des algos dans notre contexte spécifique.","13---replication-and-acknowledgements#13 - Replication and Acknowledgements":"Le système de réplication fonctionne par sequential consistency : un leader par partition envoie la donnée aux followers.\nPlus le replication factor est élevé, et plus l'acknowledgement des records peut être ralenti à cause du fait qu'il faut attendre le follower le plus lent.\nPour répondre à ce problème, chaque leader maintient dans ZooKeeper une liste des In-Sync Replicas (ISR), c'est-à-dire les followers qui ne dépassent pas un retard temporel spécifique vis-à-vis des records du leader.\nOn peut régler un nombre minimal de followers dans l'ISR avec min.insync.replicas (par défaut 1, mais l'auteur conseille au moins 2, pour toujours avoir au moins une autre copie à jour).\nEn dessous de ce nombre se trouvant dans l'ISR, le leader arrête d'accepter la publication de records et attend qu'un nombre suffisant de followers redeviennent éligibles à l'ISR.\nLe temps maximal de lag à partir duquel un follower est exclu de l'ISR est configuré avec replica.lag.time.max.ms (par défaut 10 secondes).\nC'est les followers de l'ISR dont on attendra la confirmation pour une durabilité maximale, et non pas celle de l'ensemble des followers.\nLe producer ne peut que dire s'il veut attendre l'acknowledgement de tous les followers (de l'ISR), du leader seulement, ou de personne. Il ne peut pas influer sur qui se trouve ou non dans l'ISR.\nSeuls les réplicas de l'ISR sont éligibles pour devenir leaders de partition.\nSauf si on a mis la propriété unclean.leader.election à true.\nQuelle que soit l'approche choisie, elle aura des désavantages plus ou moins grands :\nAvec un faible min.insync.replicas on risque de ne plus avoir de réplicas à jour pour prendre la main au moment où le leader est en échec.\nAvec un min.insync.replicas élevé proche ou égal au replication factor, on risque d'avoir des réplicas lents qu'on est obligés d'attendre.\nAvec un plus grand replication factor (la propriété default.replication.factor), et potentiellement plus de brokers, on risque quand même d'être lent parce qu'on a plus de réplicas à mettre à jour.\nOn peut augmenter le replication factor de topics existants, mais ça nécessite de créer un fichier de config de réassignement sous forme JSON, avec l'ordre des réplicas qu'on préfèrerait pour chaque partition (pour le choix des nouveaux leaders d'une manière qui les répartit entre brokers).\nPour nous aider avec cette config, il y a l'outil kafka-reassign-tool sur GitHub.\nLa création d'un réplica supplémentaire demande à copier les partitions pour lesquelles on augmente le replication factor, donc ça peut prendre du temps et occuper le cluster.\nPour décommissionner un broker, il faut d'abord le vider de son rôle de leader pour toutes les partitions où il l'est.\nOn peut pour ça utiliser la même technique avec le fichier de config de réassignement, en indiquant pour toutes les partitions où il est leader, les IDs d'autres brokers.\nConcernant l'acknowledgement.\nQuand un producer choisit de ne pas en recevoir (acks = 0), il n'a plus de garantie de durabilité sur ce qu'il envoie (bien que la réplication se fasse comme d'habitude côté serveur), et il n'est plus non plus informé de l'offset des records qu'il publie (par retour de la méthode send() par exemple).\nCa peut par exemple être utile dans un cas de traitement de données de température qu'on affiche en direct : la perte de quelques données n'est pas très grave.\nQuand un producer choisit d'en recevoir un quand seulement le leader a validé le record (acks = 1), en réalité il n'y a pas beaucoup plus de garantie qu'avec acks = 0.\nLe leader peut échouer à effectivement écrire le record (il répond avant que l'écriture soit complète), ou il peut lui-même être en situation d'échec juste après l'acknowledgement, et avant d'avoir envoyé le record aux autres réplicas.\nEn fait ça revient à se demander si la machine du leader est considérée comme plus fiable que celle du producer pour ce qui est de décider si un record est publié ou pas.\nDe manière générale ce mode est surtout utile dans les cas où la perte de quelques données est tolérable, mais où le client a besoin de connaître l'offset du record qu'il vient d'écrire.\nQuand un producer choisit de recevoir tous les acknowledgements (acks = -1 ou all), il a la garantie de durabilité maximale.\nL'auteur conseille comme heuristique par défaut d'adopter -1 ou all pour la valeur de acks (au lieu de 1 par défaut), et au moins 2 pour min.insync.replicas (au lieu de 1 par défaut) avec un replication factor d'au moins 3.\nSi on est dans des cas où la perte de données est tolérable, alors on pourra diminuer ces contraintes.","14---data-retention#14 - Data Retention":"Les données de chaque partition sont par défaut dans des dossiers de la forme /tmp/kafka-logs/getting-started-0/.\nLe dossier contient un fichier nommé leader-epoch-checkpoint, qui contient toutes les réassignation de leader pour la partition. De cette manière, chaque réplica peut ignorer les messages d'un collègue broker qui se prendrait pour le leader de la partition sans l'être.\nLe contenu des records se trouve dans fichiers nommés selon le 1er offset du record qu'ils ont, avec l'extension .log.\nChaque fichier de log a un index nommé de la même manière mais avec une extension .index. Il contient un map entre les offsets des records (ou des batchs) du fichier de log, et l'offset physique dans le fichier de log pour aller les lire.\nOn a enfin un autre fichier nommé pareil mais avec l'extension .timeindex, et qui contient un map entre des timestamps des records et l'offset physique dans le fichier de log.\nKafka a des propriétés configurables, liées à la taille des fichiers de log et à leur ancienneté, pour contrôler le moment où on switch au fichier suivant pour écrire.\nPar exemple log.segment.bytes (par défaut 1 GB), log.roll.hours (par défaut 1 semaine).\nOn peut aussi configurer un temps aléatoire de décalage du switch, pour que l'ensemble des partitions ne changent pas de fichier de log en même temps.\nLes fichiers d'index ont une place pré-allouée, dont la taille est contrôlable par une propriété.\nOn peut de la même manière activer la pré-allocation des fichiers de log, pour gagner en performance sur certains filesystems.\nLe script kafka-dump-log.sh dans les outils d'admin de Kafka permet de lire le contenu des fichiers qui composent les logs.\nIl existe des cleanup policies qui sont de deux types : supprimer les anciens records, ou faire de la compaction pour gagner en place.\nlog.cleanup.policy permet de contrôler le type de policy, cross-topic ou pour un topic spécifique.\nPar défaut la valeur est delete, l'autre valeur étant compact. On peut spécifier les deux en même temps, en les séparant par une virgule.\nLe cleanup ne s'applique qu'aux fichiers de log inactifs, c'est-à-dire les fichiers de log dont on a déjà switché vers un autre fichier.\nQuand la policy est delete.\nUn background process va régulièrement (toutes les log.retention.check.interval.ms, par défaut 5 minutes) vérifier pour chaque fichier de log inactif s'il est sujet à être supprimé ou non, en fonction des règles de rétention configurées (par exemple log.retention.bytes (non configuré par défaut), log.retention.hours (par défaut 1 semaine)).\nAvec les valeurs par défaut, un fichier de log sera supprimé au bout d'1 semaine. Par contre, il sera supprimé d'un coup. Donc si on n'avait qu'un seul fichier qui n'avait pas atteint la taille d'1 GB pour switch de fichier avant les 1 semaine, on va perdre tous les records d'un coup, et écrire les nouveaux dans un nouveau fichier.\nSi on veut une plus grande granularité, on peut configurer de plus petites valeurs pour pour le switch de fichier de log actif (log.segment.bytes ou log.roll.hours).\nQuand la policy est compact.\nLa compaction est utile par exemple dans le cas où on a des events de type ECST (l'auteur ne mentionne pas le terme).\nNormalement il faut une logique en deux temps : hydrater notre app downstream avec les données de l'app upstream, puis laisser l'app upstream publier ses changements sur Kafka.\nPour éviter d'avoir ce fonctionnement en deux temps, la compaction permet de publier dès le début les ECST dans Kafka, et de ne pas avoir besoin de l'autre mode puisque Kafka gardera toujours au moins le record le plus récent pour chaque entité.\nPar contre ça ne marche qu'avec les events qui ont la totalité de la donnée de l'entité et qui donc “déprécient” les events précédents pour cette entité. Ça ne marche pas avec les events qui indiquent seulement les champs qui ont changé dans l'entité.\nLa compaction consiste à transformer Kafka en snapshot, où on ne garde que les données les plus récentes pour chaque entité, qu'on différencie par la key associée au record.\nLa lecture de l'ensemble du topic prendra donc un temps proportionnel au nombre de keys différents dont il existe des records.\nD'un point de vue technique, la compaction est faite par des threads en arrière plan.\nCôté config :\nLeur nombre est contrôlé par log.cleaner.threads, par défaut 1.\nlog.cleaner.min.cleanable.ratio (par défaut 0.5) indique le ratio de log “sale“ à partir duquel il sera éligible à être compacté.\nlog.cleaner.min.compaction.lag.ms (par défaut 0) permet d'indiquer un temps minimal avant qu'un record ne puisse faire l'objet de compaction. Sachant que ça ne peut pas concerner le fichier de log actif, mais seulement ceux où il y a déjà eu un switch de fichier.\nlog.cleaner.min.compaction.lag.ms (par défaut infini) permet d'indiquer un temps maximal à partir duquel le log sera quand même compacté, même s'il ne satisfaisait pas le ratio de “saleté”.\nlog.cleaner.delete.retention.ms (par défaut 24 heures) indique la durée de vie des tombstones.\nOn peut aussi définir ces configs par topic (sauf pour le nombre de threads de compaction).\nPour calculer le ratio de “saleté”, Kafka maintient un cleaner point correspondant au point jusqu'où la compaction a déjà été faite, pour chaque fichier de log.\nLe ratio consiste à diviser le nombre de records pas encore traités par le nombre de records existants dans la partie déjà traitée.\nLa compaction laisse les records dans le même ordre, et ne change pas leur offset. Elle va juste éliminer des records.\nLes tombstones sont créés par les producers pour indiquer à Kafka que les entités d'une key particulière ne sont plus utiles.\nCe sont simplement des records, avec une valeur nulle, et la key pour laquelle on veut faire la suppression.\nLa raison pour laquelle ils restent un temps minimal (par défaut 24h) est de s'assurer que les consumers ont eu le temps d'avoir l'info de suppression du record, pour éviter qu'ils gardent l'entité en base alors qu'elle n'est plus censée exister.\nOn peut aussi combiner compaction et deletion.\nCette possibilité est utile dans des cas particuliers où les events perdent rapidement leur intérêt.\nOn peut alors potentiellement avoir une compaction plus agressive vu qu'on limite la taille des records en supprimant les plus anciens.\nUn exemple peut être le topic __consumer_offsets qui compacte pour que le group coordinator puisse rapidement reconstruire l'état des consumers, et supprime les anciens offsets pour les groupes qui n'ont pas été actifs depuis longtemps pour éviter de trop grossir.","15---group-membership-and-partition-assignment#15 - Group Membership and Partition Assignment":"Les consumer groups permettent de faire du load balancing au niveau de la consommation.\nKafka garantit qu'il y aura au plus un consumer d'un même groupe par partition.\n“au plus” pour prendre en compte le cas où aucun consumer ne serait disponible dans le groupe.\nL'assignation des consumers se passe en deux temps :\n1 - La phase group membership.\nIl s'agit d'identifier les consumers d'un groupe, et d'élire un group leader parmi eux, pour que celui-ci décide des assignations partition / consumer.\n1 - Les consumers envoient un message au broker qui est coordinator pour ce group, pour s'identifier comme membres de ce groupe.\nIls savent qui est leur coordinator parce que son id leur est renvoyé par un des brokers, qui lui même peut le savoir par un mécanisme déterministe de hachage entre le group id et une des partitions : le broker leader de cette partition devient le coordinator du group.\n2 - Le coordinator attend un certain temps avant de répondre, pour que tous les consumers aient pu s'identifier comme membres du groupe, et pour éviter les nombreux rebalancings au début.\nLe délai est appliqué quand le groupe est vide.\nLe délai est contrôlable avec group.initial.rebalance.delay.ms (par défaut 3 secondes).\nC'est typiquement inutile dans les scénarios où il n'y a qu'un consumer, comme dans des tests d'intégration par exemple où on peut le mettre à 0.\n3 - Il renvoie une réponse à chacun, contenant les IDs des consumers du groupe et l'ID du consumer leader.\n2 - La phase state synchronisation.\n1 - Le group leader va faire l'assignation des partitions aux consumers dont il a reçu la liste, et envoyer ça au coordinator.\n2 - Le coordinator à son tour renvoie les assignations à chaque consumer.\nA chaque fois qu'un consumer rejoint un groupe existant, le coordinator oblige les autres consumers à se réidentifier, et se voir potentiellement réassigner des partitions (on appelle ça le rebalancing).\nPendant le rebalancing, les consumers se verront refuser toutes leurs opérations (y compris heartbeats) par une réponse REBALANCE_IN_PROGRESS.\nA chaque rebalancing, le coordinator va assigner à chaque consumer un id qui est incrémenté monotoniquement. Donc un consumer zombie qui aurait oublié de se réidentifier serait rejeté la prochaine fois qu'il voudrait consommer.\nLe client met à disposition la possibilité d'enregistrer des callbacks sur les events d'un rebalancing :\nonPartitionsRevoked() est appelé dès que la consommation doit s'arrêter pour que le rebalancing puisse avoir lieu.\nonPartitionsAssigned() indique au client les éventuelles nouvelles partitions qui lui ont été assignées.\nonPartitionLost() indique d'éventuelles partitions perdues par le consumer.\nÇa peut se produire si le consumer n'avait pas émis de heartbeats et était considéré en échec.\nLe rebalancing par défaut (eager rebalancing) se fait en une étape.\nIl implique donc que les consumers doivent à chaque fois partir du principe que l'ensemble des assignations de partition sont potentiellement révoquées et cleaner les messages en cours de traitement.\nL'incremental cooperative rebalancing permet d'éviter ça en plaçant les assignations à la fin, en utilisant éventuellement plusieurs étapes :\nUne seule étape s'il n'y a que de nouvelles assignations de partitions.\nS'il y a aussi des révocations : une première étape de révocations, et une deuxième étape d'assignations.\nPour que l'incremental cooperative rebalancing soit plus efficace, et contrebalance le fait qu'il nécessite plus d'appels réseau, il faut que la stratégie d'assignation de partition soit sticky (c'est-à-dire qu'on essaye au maximum de garder les assignations qui existent pendant le rebalancing).\nDans les systèmes distribués, il y a deux propriétés importantes : la liveness qui est le fait qu'un système continue d'opérer et de progresser dans ses tâches, et la safety qui est le fait que les invariants du système soient préservés.\nKafka satisfait la liveness par :\nLes checks réguliers d'availability des consumers par le système de heartbeats à envoyer avant un timeout.\nLa vérification que les consumers progressent, en s'assurant qu'ils appellent régulièrement poll() avant de dépasser un timeout.\nPlus les valeurs des deux timeouts sont petites et plus le client détectera vite les échecs, mais au prix de plus de consommation de ressources et de plus de faux positifs.\nGlobalement l'auteur trouve ces valeurs par défaut raisonnables dans la plupart des cas.\nDans le cas où on doit retenter une requête vers un composant externe (DB, broker etc) qui échoue plusieurs fois, on risque d'échouer nous-mêmes à respecter le timeout prouvant qu'on progresse (max.poll.interval.ms), on alors 5 possibilités :\n1 - Mettre une très grande valeur à max.poll.interval.ms, pour “désactiver” le timeout.\nIl s'agit d'un cas où on veut que l'ordre soit absolument respecté, et que les actions pour chaque record soient absolument réalisées, au prix d'une potentielle attente jusqu'à ce que la ressource externe réponde correctement.\nLe problème c'est qu'on ne prend pas en compte qu'on pourrait avoir un problème en interne, notamment des bugs dans le consumer lui-même, et que notre timeout nous protégeait aussi de ça.\n2 - Mettre une valeur raisonnable pour le timeout. Dans ce cas, tant que le service externe est down, le consumer va recommencer jusqu'au timeout, et être rebalancé (exclu puis réintégré).\nC'est le même comportement que le 1- où on veut faire les records dans l'ordre coûte que coûte, mais là on règle les éventuels problèmes de consumer bloqué.\nIl y a par contre un risque de perdre en performance à force d'enchaîner les rebalancings.\n3 - Détecter nous mêmes dans le consumer le fait qu'on va bientôt dépasser le timeout, et se déconnecter après avoir nettoyé ses tâches en cours, pour se reconnecter tout de suite après.\nEn fait, vu qu'on se déconnecte/reconnecte, on va entraîner un rebalancing de fait.\nLe petit avantage par rapport à la 2- c'est qu'on va pouvoir faire des checks supplémentaires localement sur le fait de ne processer le record qu'une fois.\n4 - Mettre en place une deadline par record, et si la deadline est dépassée, considérer qu'il a été traité en passant au suivant, mais le republier dans le topic pour qu'il soit retraité plus tard.\nCette solution implique que l'ordre de traitement des records n'est pas essentiel.\nOn pourrait aussi avoir un temps maximal ou un nombre de retries maximal dans le record, indiquant combien de temps ou de fois il faut continuer à essayer de le republier avant que ça ne serve plus à rien, dans le cas où il devient obsolète avec le temps.\nLes consumer groups étant indépendants et pouvant lire dans un même topic, requeuer un message derrière le topic juste parce qu'un consumer group n'a pas pu le traiter à temps n'est pas vraiment ce qu'on veut.\nAu lieu de ça, on peut avoir une topologie de type fanout, c'est-à-dire un topic qui publie dans un fanout group, qui lui-même publie dans un topic par consumer group. Et dans ce deuxième niveau on pourra requeuer un message non géré par un consumer groupe spécifique.\n5 - Mettre en place une deadline comme dans le 4-, mais sans requeuer le record du tout.\nIl peut être intéressant, tout comme pour le 4-, de penser à mettre les records non traités dans une dead letter queue pour pouvoir investiguer la lenteur plus tard.\nMalgré les garanties apportées par Kafka, il est possible que deux consumers traitent le même record.\nÇa arrive dans le cas suivant :\n1 - Un consumer met beaucoup de temps à traiter un record, et dépasse le timeout pour appeler poll().\n2 - Il se fait exclure parce que son thread responsable des heartbeats n'en émet plus et demande même explicitement à être révoqué.\n3 - Le coordinator révoque le consumer et fait un rebalancing pour assigner sa partition à un autre consumer.\n4 - Le nouveau consumer commence à traiter les records non commités.\n5 - pendant ce temps, le consumer révoqué continue de traiter son record en cours, sans savoir qu'il a été arrêté.\nPour éviter ça, Kafka ne propose pas grand chose. L'auteur propose 3 approches à faire soi-même :\n1 - Se débrouiller pour que le consumer ne dépasse jamais le timeout, et que sinon on gère les conséquences à la main pour ne pas avoir de rebalancing.\n2 - Utiliser un distributed lock manager (DLM) pour protéger les sections critiques d'être traitées en même temps par deux consumers.\nLa protection agit sur le fait de traiter en même temps, pas le fait de traiter plusieurs fois en général.\nCeci dit, on peut du coup vérifier qu'on n'a pas déjà traité la section critique du record avant de la traiter à nouveau.\nAttention à ne pas être tenté de faire l'optimisation de faire déconnecter/reconnecter le consumer dans le cas où on remarque que le record a déjà été traité : le consumer qui l'a traité n'a peut-être pas encore commité, et donc on risquerait de le retraiter à nouveau.\nL'impact du DLM sur le throughput et la latence peuvent être minimisés en regroupant les records du buffer, par exemple par partition, et en faisant le lock avant et après le traitement de chacun de ces lots.\nA la place du DLM on pourrait aussi avoir n'importe quel store persistant, comme Redis ou une DB.\n3 - Utiliser un process qui vérifie régulièrement le process qui tourne pour consommer les records, pour s'assurer qu'il consomme bien régulièrement.\nS'il est bloqué depuis un certain temps, le process vérificateur le restart avant que le timeout côté Kafka soit déclenché.\nL'idée des static members va très bien avec le fait d'avoir un système de health check externe à Kafka : par exemple Kubernetes qui s'assurerait de détecter les consumers en échec, et de les arrêter puis restarter.\nCôté liveness, ça peut permettre d'éviter des rebalancings de la part de Kafka, et donc d'avoir un throughput plus important, au prix de certaines partitions spécifiques qui n'avancent plus pendant un temps plus long qu'en mode non static.\nCôté safety, Kubernetes peut jouer le rôle d'orchestrateur pour s'assurer que les partitions ne sont pas traitées par plusieurs consumers en même temps.\nLa raison pour laquelle l'assignation des partitions se passe dans un consumer leader, c'est de permettre le changement de stratégie d'assignation pour chaque consumer, plutôt que quelque chose de commun en tant que config Kafka.\nIl existe 4 assignors disponibles pour choisir ces stratégies :\nLe range assignor est l'assignor par défaut, il consiste à classer les partitions pour un même topic et les consumers dans l'ordre du plus petit au plus grand, et ensuite d'attribuer des groupes de partitions de part égales aux consumers successifs.\nEt on recommence la même chose pour chaque topic.\nSi le nombre de partitions n'est pas divisible par le nombre de consumers, les premiers consumers se verront attribuer une partition de plus.\nSon désavantage c'est qu'il assigne les partitions équitablement par topic, mais si on prend en compte l'ensemble des partitions existantes dont les consumers doivent s'occuper, on peut tomber sur une répartition assez inégale.\nÇa se produit en particulier quand le nombre de partition par topic est plus petit que le nombre de consumers : les premiers consumers reçoivent une partition de chaque topic, alors que les derniers n'en reçoivent pas.\nLe round robin assignor consiste à rassembler les partitions de tous les topics, puis de les attribuer un par un, dans l'ordre, aux consumers, en rebouclant sur la liste de consumers s'il y a plus de partitions que de consumers.\nLa répartition est bien meilleure que pour le range assignor, puisqu'elle est cross-topic.\nLe sticky assignor consiste à assigner de manière à peu près équilibrée, mais surtout s'évertue à préserver le plus possible les assignations déjà faites, quand il faut faire une réassignation.\nLa répartition est de la même qualité que pour le round robin assignor, mais celui-ci minimise le nombre de partitions changées de main pendant un rebalancing.\nLe cooperative sticky assignor consiste à faire la même chose que le sticky assignor, mais en utilisant le cooperative rebalancing protocol qui permet de réduire les pauses de rebalancing.\nLes consumers ne sont plus obligés de se préparer à la révocation de toutes leurs partitions à chaque rebalancing. Ils savent lesquelles seront révoquées, et ensuite lesquelles leur seront assignées.\nPour changer la stratégie d'assignation, on ne peut pas simplement mettre à jour la propriété de config qui le fait (partition.assignment.strategy).\nLe premier consumer qui sortirait du groupe pour y revenir avec la nouvelle stratégie provoquerait un problème d'inconsistance de stratégie au niveau de ce groupe.\nOn assigne d'abord l'ancienne stratégie et la nouvelle, puis on supprime l'ancienne. Au final on aura eu 2 bounces.","16---security#16 - Security":"Kafka n'est pas configuré par défaut pour fonctionner de manière sécurisée.\nPar défaut, n'importe quel client peut se connecter, y compris à ZooKeeper.\nLes connexions ne sont pas chiffrées.\nMême une fois l'authentification mise en place, les autorisations sont maximales.\nLa première sécurité est le blocage au niveau réseau avec un firewall.\nL'auteur propose une topologie réseau en 4 blocs séparés par des firewalls :\nLe bloc ZooKeeper, accédé uniquement par les brokers.\nLe bloc Kafka brokers, accédé par les clients.\nLe bloc clients : consumers, publishers, admin clients.\nLe bloc externe qui passe par internet, et peut contenir un site distant, ou encore des télétravailleurs accédant via VPN.\nEnsuite il faut activer le chiffrement TLS supporté par Kafka.\nChaque broker a besoin d'une clé RSA et d'un certificat CA, correspondant à son hostname.\nLa configuration SSL/TLS peut se faire dans server.properties.\nIl faut notamment utiliser le protocole existant SSL sur un port différent de PLAINTEXT, à la fois pour listeners et advertised.listeners.\nPour activer le chiffrement pour les communications inter-broker, on peut ajouter inter.broker.listener.name=SSL.\nIl suffit ensuite de connecter le client sur le bon port de Kafka, en indiquant les bonnes creds et le protocole utilisé.\nUne fois qu'on s'est assuré que la connexion chiffrée fonctionne, l'auteur recommande de désactiver le socket non chiffré dans le serveur, en enlevant la version qui utilise PLAINTEXT dans listeners et advertised.listeners.\nOn peut même configurer le firewall pour interdire les connexions sur le port 9092.\nKafka n'a pas de mécanisme de chiffrement de la donnée elle-même, par défaut elle sera stockée en clair sur le filesystem des brokers.\nUne des possibilités peut être d'utiliser un chiffrement au niveau du filesystem ou du disque dur entier des machines des brokers.\nLa méthode la plus sûre est de recourir à du chiffrement de bout en bout de la donnée, en chiffrant dans le publisher, et déchiffrant dans le consumer.\nIl existe plusieurs projets open source qui permettent de le faire. Par exemple, le projet Kafka Encryption.\nSi on publie des messages déjà chiffrés, il devient inutile d'activer la compression dans Kafka, puisque l'entropie des messages sera alors maximale.\nLe chiffrement de bout en bout ne rend pas l'utilisation de TLS inutile.\nTLS protège l'ensemble du record, y compris les headers par exemple.\nIl protège contre les attaques man in the middle, en assurant l'identité de l'émetteur.\nKafka supporte plusieurs types d'authentification.\nLe mutual TLS permet d'utiliser le mécanisme TLS habituellement utilisé pour que le client fasse confiance au serveur aussi dans l'autre sens : le client lui aussi envoie un certificat signé par un CA auquel le serveur fait confiance.\nCôté serveur, on peut activer la fonctionnalité avec la propriété ssl.client.auth :\nElle vaut none par défaut.\nrequired permet de forcer les clients à fournir un certificat valide s'ils veulent se connecter.\nIl y a une 3ème option utile pour faire une migration progressive : requested permet d'accepter l'authentification par ce moyen, mais sans le rendre obligatoire le temps que tous les clients aient été migrés.\nCôté client, il faut obtenir un certificat certifié par un CA valide du point de vue du serveur, et se connecter avec les propriétés de config qui sont le miroir de celles qu'utilise le serveur pour lui-même configurer TLS.\nPour obtenir l'information sur l'identité du client qui se connecte, c'est par défaut le champ CN du certificat qui sera utilisé.\nLa propriété ssl.principal.mapping.rules permet de personnaliser le champ à prendre par des règles de type regex.\nPar contre, la fiabilité de la méthode pour déterminer l'identité dépend de la rigueur avec laquelle les certificats sont établis :\nSi un des clients peut mettre l'identité d'un autre client dans le champ CN d'un certificat qu'il fait générer par l'autorité de confiance, alors il pourra se faire passer pour l'autre client.\nUn autre problème aussi c'est que Kafka ne permet pas de révoquer un certificat pour un client particulier.\nLe mieux qu'on puisse faire c'est de déployer un nouveau CA, et de faire signer tous les certificats des clients par ce CA.\nIl faut aussi penser à utiliser des CA différents si on a plusieurs clusters Kafka.\nL'authentification mutual TLS ne peut pas être utilisée en même temps que d'autres types d'authentification au niveau applicatif, même si la mutual TLS se trouve dans une couche réseau différente.\nSASL (Simple Authentication and Security Layer) consiste à ajouter une méthode d'authentification à un protocole utilisateur.\nIl est en général utilisé avec du TLS.\nL'une des variantes supportées est GSSAPI (Generic Security Service API), aussi connu sous le nom de son implémentation principale qui est Kerberos.\nCet outil va avec l'usage de répertoires centralisés type Active Directory.\nIl est surtout adapté aux utilisateurs individuels, mais Kafka a besoin d'avoir une authentification plutôt orientée autour de service accounts, parce qu'on ne peut pas démarrer et arrêter un client à chaque interaction utilisateur.\nLe problème avec l'approche service accounts c'est que Kerberos n'étant pas forcément compatible avec toutes les ressources (par exemple Redis), on va pouvoir désactiver un account mais sans être sûr que l'ensemble des ressources le sont pour cet account.\nL'auteur trouve que Kerberos est un système complexe pour ce qu'il apporte, et conseille plutôt les autres méthodes SASL.\nLes autres variantes SASL supportées par Kafka sont PLAIN et SCRAM.\nPLAIN est le diminutif de plaintext, pour dire que le user et mot de passe sont transmis en clair.\nSCRAM est l'acronyme de Salted Challenge Response Authentication Mechanism, et il a la particularité de ne pas impliquer d'envoyer les credentials directement au serveur. Il apporte donc une meilleure sécurité.\nComme dit plus haut, l'authentification SASL n'est pas compatible avec l'authentification SSL côté client (avec le client qui fournit un certificat signé par un CA de confiance) : Kafka ne saurait pas quoi prendre comme identifiant entre le username dans SASL et le champ CN du certificat.\nSi on fournit les deux, la configuration pour l'authentification SSL côté client ne sera pas prise en compte.\nAvec SCRAM, les versions hashées des credentials valides sont stockées dans ZooKeeper, par exemple avec le script kafka-config.sh.\nPLAIN quant à lui les stocke en clair dans server.properties.\nOn peut aussi utiliser SASL pour la communication inter-broker au lieu de juste SSL.\nAttention à bien protéger le fichier server.properties qui va du coup contenir le username et password en clair, par exemple avec un petit chmod 600.\nUne autre solution peut être de créer un fichier dans config/ qu'on protège, et d'y mettre la config jaas. Il faudra alors passer ce fichier dans l'option CLI java.security.auth.login.config au moment de démarrer le broker.\nOn peut à chaque fois utiliser netstat -an | egrep \"9092|9093|9094\" pour vérifier sur quels ports il y a Kafka en écoute et sur quels ports il y a une connexion établie.\nLa version de SASL avec OAuth bearer est là seulement dans un objectif de testing. Elle n'est pas sécurisée.\nL'application peut spécifier un user arbitraire dans un token JWT.\nOn peut utiliser une implémentation open source comme Kafka OAuth.\nLes delegation tokens sont un mécanisme complémentaire à SASL, permettant de faciliter la gestion des credentials sur un grand nombre de brokers.\nPour Kerberos il va s'agir de remplacer le déploiement des TGT ou keytab, et pour PLAIN et SCRAM ça va être les user / password.\nLes delegation tokens sont limités dans le temps et donc permettent de ne pas compromettre les vrais credentials.\nC'est particulièrement pratique dans le cas où les brokers sont créés de manière éphémère dans des workers.\nIl faut faire la configuration côté broker en mettant en place delegation.token.master.key à la même valeur pour tous les brokers du cluster.\nOn crée ensuite les tokens avec une commande CLI kafka-delegation-tokens.sh.\nLe token doit être renouvelé avant la période d'expiration (par défaut 1 jour) avec le même fichier de commande CLI.\nCôté client, il faut indiquer qu'on utilise l'authentification avec delegation token, et indiquer les valeurs de TOKENID et HMAC qu'on a pu récupérer au moment de créer le token sur le broker.\nOn peut configurer une authentification sur ZooKeeper, en utilisant SASL.\nÇa concerne du coup les brokers et les clients admin.\nZooKeeper a en plus un mécanisme d'autorisation à base d'ACL, permettant d'attribuer 5 types de droits aux utilisateurs anonymes et utilisateurs authentifiés par SASL.\nDe l'aveu de l'auteur, ajouter une authentification à un ZooKeeper qu'on a déjà isolé dans un réseau à part peut être excessif. Le niveau de sécurité dont on a besoin dépendra du contexte.\nPour activer l'authentification, il faut modifier zookeeper.properties et y ajouter la config pour activer SASL.\nIl ne faut pas oublier de changer les autorisations qui existent pour les utilisateurs anonymes avec la commande CLI zookeeper-security-migration.sh.\nIl faut ensuite activer l'authentification sécurisée à ZooKeeper depuis le broker, en ajoutant la propriété zookeeper.set.acl=true, et en redémarrant le broker avec l'option java.security.auth.login.config pointant vers le fichier de config contenant les identifiants SASL.\nLes clients admin, que ce soit en script CLI ou de type Kafdrop, doivent aussi être configurés pour se connecter aux brokers qui ont une authentification activée.\nPour les scripts CLI, on peut changer le fichier client.properties.\nPour Kafdrop, il faut modifier le fichier kafka.properties.\nConcernant l'autorisation.\nKafka a un système d'autorisations sous forme d'ACL centrées sur les ressources.\nIl s'agit d'un système distinct de celui de ZooKeeper.\nOn a la possibilité d'autoriser des droits :\nPar utilisateur.\nPar host.\nPour un type de ressource particulier (par exemple Topic ou Group).\nPour un pattern spécifique à appliquer aux ressources (par exemple toutes les ressources commençant par un préfix, ou avec des * pour dire qu'on peut avoir n'importe quoi dans une partie du nom).\nPour une opération particulière (par exemple Read, Write, Describe etc.).\nPour commencer, il faut activer l'autorisation dans le serveur en ajoutant la classe d'autorisation et la liste des super users dans server.properties :\nauthorizer.class.name=kafka.security.auth.SimpleAclAuthorizer\nsuper.users=User:admin\nLes super users sont séparés par des point-virgules.\nA partir de là, seuls les super users auront la possibilité de faire des choses. Tous les autres seront bloqués.\nParmi les scripts CLI, kafka-acls.sh permet de visualiser et configurer les ACLs.\nLa bonne pratique c'est d'assigner des utilisateurs distincts à chaque application (publisher, consumer etc.).\nLa même chose vaut pour le user utilisé pour l'inter-broker communication : il vaut mieux lui donner les bons droits plutôt que le mettre en super admin.\nKafka donne la possibilité d'autoriser ou interdire pour une règle d'autorisation donnée.\nLes règles d'interdiction prennent toujours le pas sur les règles d'autorisation, quelles que soient les granularités.\nOn peut grâce à ça, par exemple :\nPartir du fait que le défaut dans Kafka c'est que tout est interdit pour tout le monde.\nPuis mettre une règle qui autorise un droit de lecture sur un topic pour tous les utilisateurs (en utilisant le wildcard “*”).\nEt enfin mettre des règles interdisant ce droit d'écriture pour certains utilisateurs particuliers (par exemple un utilisateur guest dont on donne les identifiants à ceux qui veulent essayer).\nIl y a des moyens de filtrer plus ou moins de choses quand on liste des droits avec le script CLI kafka-acls.sh :\nOn a la possibilité de lister tous les droits qui pourraient s'appliquer au nom qu'on indique (parce qu'ils comportent des règles de wildcard ou de préfix etc.), grâce à l'option --resource-pattern-type=any.\nOu alors lister seulement les droits qui portent sur le nom exact qu'on indique, grâce à l'option --resource-pattern-type=match.\nKafka permet de créer des règles d'autorisation ou d'interdiction pour les clients basé sur leurs adresses IP.\nL'auteur déconseille cette fonctionnalité, étant donné la nature mouvante des topologies de client dans le cloud.\nIl conseille à la limite d'utiliser le firewall pour faire ce genre de restrictions.\nVoilà quelques scénarios d'autorisation habituels qu'on met en place :\nCréer des topics : l'opération Create qu'on attribue pour les topics commençant par un préfixe.\nSupprimer des topics : l'opération Delete sur les topics avec le même préfixe.\nPublier dans un topic : l'opération Write ou IdempotentWrite (pour que ça marche avec la publication en mode idempotent), qu'on attribue pour les topics commençant par un préfixe.\nConsommer depuis un topic :\nPour un consumer sans groupe, il faut l'opération Read sur le topic. En général on met le topic exact pour éviter d'augmenter l'exposition des données.\nSi le consumer fait partie d'un groupe, alors il faudra aussi l'opération Read sur le groupe.","17---quotas#17 - Quotas":"Les quotas servent à :\nEmpêcher les attaques DOS en faisant du throttling.\nAider à planifier la capacité de la machine pour assurer une bonne qualité de service.\nEn particulier quand on commence à avoir suffisamment de clients Kafka pour que les quelques brokers initialement nécessaires commencent à manquer de ressources.\nLes quotas s'appliquent aux utilisateurs au niveau de chaque broker.\nCa veut dire qu'il faut prendre en compte le nombre de brokers, et potentiellement revoir les quotas quand on ajoute des brokers.\nIl existe deux types de quotas :\n1 - Network bandwidth quotas.\nVérifie que les producers et consumers ne dépassent pas une certaine quantité de données transférées (en bytes / seconde).\nCa permet d'englober de nombreux aspects : bande passante réseau, ressources I/O, ressources mémoire à cause du buffering, ressources CPU dans le cas du chiffrement TLS.\nLe broker calcule l'utilisation de la bande passante de chaque client par fenêtre glissante.\nQuand il y a un dépassement, le broker va introduire artificiellement un délai avant de répondre. Le client ne saura donc pas s'il a subi une restriction ou si c'est juste des lenteurs réseau.\n2 - Request rate quotas.\nVérifie que les producers et consumers n'utilisent pas plus d'un certain pourcentage de CPU d'un thread I/O.\n50% correspond à la moitié de l'utilisation du thread I/O, 200% correspond à l'utilisation pleine de 2 threads.\nLe network bandwidth quota couvre déjà une grande partie des cas. Request rate quotas vient le compléter dans les cas où un client a fait un mauvais réglage qui l'amène à faire un très grand nombre de requêtes vers le serveur, sans qu'il n'y ait forcément beaucoup de données dans ces requêtes.\nCa peut être par exemple si un consumer a configuré une valeur de fetch.max.wait.ms très basse, le poussant à faire des requêtes très régulières pour demander plus de records.\nComme autre cas de mauvaise configuration, ça peut aussi être de nombreuses requêtes qui aboutissent à “unauthorized”, ou encore une configuration différente de la compression entre client et serveur, aboutissant à une sur-utilisation du CPU inutile.\nCe mode de quota fonctionne aussi par fenêtre glissante, et ajoute aussi des pénalités d'attente silencieuses en cas de dépassement.\nLes quotas sont attribués aux usernames et aux client IDs.\nLes usernames sont ceux qui sont utilisés et vérifiés par Kafka par les mécanismes d'authentification (champ CN du certificat en cas d'authentification par mutual TLS, et champ username en cas d'authentification SASL) et d'autorisation.\nLes client IDs sont les identifiants qu'un client déclare librement au moment de se connecter au serveur, avec le champ client.id.\nOn utilise souvent une combinaison des deux : le username pour l'authentification, et le client ID pour distinguer plusieurs machines appartenant à la même personne ou au même groupe de personnes.\nL'attribution se fait via configuration dynamique, via le script CLI kafka-configs.sh ou un autre client admin.\nIl est possible de spécifier des quotas pour un couple username / client ID, sachant que chaque membre du couple de valeurs peut avoir soit une valeur, soit la valeur <default>, soit ne pas avoir de valeur.\nLe fait de savoir quelle règle de quota va s'appliquer se fait par matching parmi les règles existantes, avec une priorité aux règles les plus précises.\nEn fonction de la règle de quota qui est retenue pour chaque consumer, si deux consumers partagent la même règle, ils partageront aussi la valeur du quota.\nD'un point de vue sécurité, l'auteur conseille de spécifier d'abord des valeurs par défaut qui sont très basses (en commençant par le couple username / client ID : <default> / <default>), et ensuite de les écraser par des règles plus spécifiques ayant des quotas plus larges.\nLa propriété buffer.memory (par défaut 32 Mo) côté client permet de le bloquer quand le buffer dépasse cette taille, ce qui peut permettre d'éviter le throttling côté serveur.\nLe fait que le client ne sache pas s'il fait l'objet de pénalités d'attente ou s'il y a simplement de la congestion sur le réseau, peut poser problème dans certains cas.\nIl peut bombarder de requêtes et finir par subir une attente si longue qu'elle dépasserait le delivery timeout. Il pourrait alors avoir tendance à réessayer plusieurs fois, menant à une forme de congestive collapse.\nEn général on peut résoudre ce problème en diminuant la propriété buffer.memory (par défaut 32 Mo) côté client pour obliger le client à attendre avant de publier plus que ce qu'il a en buffer.\nParfois on se trouve dans un cas où le client veut publier beaucoup de messages, et parmi eux la plupart des messages sans urgence particulière, et certains messages urgents dont il ne veut pas qu'ils fassent l'objet de ralentissement.\nDans ce cas, il est obligé d'essayer de deviner (par des moyens probabilistes) s'il fait l'objet de pénalités liées au quotas ou pas, pour éviter d'envoyer les autres messages le temps d'envoyer les messages urgents.\n1 - Il peut noter le nombre records envoyés mais pas encore acknowledgés par le broker : normalement ce chiffre devrait augmenter en cas de throttling, et diminuer pour atteindre presque 0 dans le cas contraire.\n2 - Il peut noter le timestamp du dernier record, et le comparer au temps actuel : s'il y a une différence importante, il est possible qu'il y ait eu du throttling.\nLa méthode de fenêtres glissantes qui calcule s'il faut appliquer des pénalités d'attente est configurable.\nLe calcul se fait sur N samples d'une durée de S secondes, qui se renouvellent sample par sample.\nN est configurable par quota.window.num (par défaut 11 samples).\nS est configurable par quota.window.size.seconds (par défaut 1 seconde).\nUne fois qu'un client a dépassé le quota dans la fenêtre de samples, il pourra à chaque sample de temps publier une quantité minimale, jusqu'à ce que sa consommation totale sur la fenêtre redescende en dessous de sa limite de quota.\nÇa implique qu'un client qui publie à fond produise des pics tous les N samples, suivis de très faibles quantités publiées.\nA propos de la stratégie de tuning de ces règles :\nPlus on va augmenter quota.window.num, et plus le pic ponctuel pourra être élevé avant de subir une pénalité.\nL'auteur conseille d'éventuellement modifier ce paramètre en conséquence.\nPlus on va augmenter quota.window.size.seconds, et plus le temps d'attente de pénalité sera long.\nL'auteur conseille de ne pas y toucher et de le laisser au minimum, c'est à dire 1 seconde.\nAttention cependant, ce comportement non uniforme qui provoque des pics n'est pas documenté, et pourrait être modifié sans avoir besoin d'un process long.","18---transactions#18 - Transactions":"Les transactions permettent de réaliser des exactly-once deliveries à travers une pipeline de plusieurs jobs (qu'on appelle stages) chaînés via des topics Kafka successifs.\nIls y arrivent parce qu'ils permettent de réaliser l'idempotence à travers plusieurs stages, et qu'en combinant ça avec l'at-least-one delivery, on obtient l'exactly-one delivery.\nLa problématique est la suivante :\nOn part d'un cas où on a un stage qui a besoin de consommer un topic Kafka, et pour chaque record consommé, publier un record dans un autre topic Kafka.\nOn ne s'intéresse pas ici à d'autres side-effects comme l'écriture en DB pour laquelle les transactions Kafka ne peuvent rien, mais bien seulement aux messages Kafka publiés et consommés.\nLes problèmes suivants peuvent se produire :\nDes erreurs réseau et des crashs du serveur, pour lesquelles on n'a pas besoin des transactions.\nLe consumer peut les gérer grâce au mécanisme de retries tant qu'il n'a pas fait le commit d'offset.\nLe producer peut les gérer grâce au mécanisme de retries tant qu'il n'a pas reçu d'acknowledgement, et au mécanisme d'idempotence qui garantit l'ordre et la déduplication.\nPour les crashs du process client on a un point faible : le cas où le client a déjà commencé à exécuter la callback du record, et est arrivé jusqu'à publier le record sortant, mais n'a pas encore fait le commit de son offset en tant que consumer.\nS'il crash à ce moment-là, la prochaine fois qu'il se réveille il va traiter le même record entrant, et va publier encore le record qu'il avait déjà publié.\nOn a donc un risque de publier le message sortant plusieurs fois, sans que la publication avec l'option d'idempotence ne puisse rien y faire, puisqu'il ne s'agit pas de retries d'un même message.\nLe même problème peut se généraliser avec la publication de plusieurs messages qui doivent tous n'être publiés qu'une fois par le stage.\nAlors que Kafka permet de base une bonne durability (notamment grâce à la réplication des données dans chaque broker), avec le mécanisme de transactions il se voit doté d'autres caractéristiques d'ACID :\nAtomicity : l'ensemble des messages publiés dans une même transaction sont soit tous validés, soit tous non validés, y compris dans des topics et partitions différents.\nConsistency : on ne se retrouve pas dans un demi-état, soit tous les records sont validés, soit aucun.\nIsolation : les transactions faites en parallèle ont le même résultat que si elles étaient faites les unes après les autres.\nD'un point de vue performance, on n'a que 3 à 5% de diminution du throughput quand on utilise les transactions.\nPour ce qui est du fonctionnement détaillé.\nLes transaction coordinators tournent sur les brokers.\nIls ont pour rôle :\n1 - d'assigner un ID à chaque producer (Producer ID, ou PID) qui en fait la demande.\n2 - gérer le statut des transactions dans un topic caché de Kafka (dont le nom est __transaction_state).\nPour que le système de transactions fonctionne, il faut que le PID du producer reste le même entre deux records consommés.\nEt pour ça, il faut que le producer du stage suivant déclare le même transactional.id que le précédent.\nCe qui a pour effet que le transaction coordinator va assigner le même PID, tant que le délai transactional.id.expiration (par défaut 1 semaine) n'est pas dépassé.\nL'association [ transactional ID, PID ] contient une propriété epoch qui indique la date de la dernière mise à jour de cette association.\nCe mécanisme permet de bloquer les process client zombies, c'est-à-dire qui ont été éjectés, mais qui continuent de penser que c'est à eux de publier : si leur epoch est plus ancien, ils ne pourront pas publier.\nGarder le même PID permet aussi au producteur successeur de terminer les transactions non terminées du producer qui vient de crash ou timeout.\nL'essentiel de l'aspect transactionnel se passe côté API du producer.\nLe client producer Java a ces méthodes :\ninitTransactions() permet d'initialiser le système de transactions pour un producer donné.\nOn ne l'appelle qu'une fois, et ça assigne un PID et un epoch pour l'association [ transactional ID, PID ].\nÇa va aussi attendre que les transactions précédentes associées à ce transactional ID soient terminées (soit COMMITED, soit ABORTED).\nDans le cas où le consumer précédent n'a pas eu le temps de dire s'il voulait commit ou abort, par défaut le broker va déclarer la transaction ABORTED.\nbeginTransaction() permet de commencer la transaction.\nsendOffsetsToTransaction() envoie les offsets du consumer.\nLe consumer va donc faire son commit à travers l'API du producer, et non pas avec sa méthode commit habituelle.\nIl faut bien sûr que l'auto-commit soit désactivé pour le consumer.\ncommitTransaction() permet de valider la transaction.\nabortTransaction() permet de l'annuler.\nLe choix du transactional ID est un des sujets majeurs de confusion autour des transactions Kafka.\nParmi les possibilités naïves qu'on pourrait imaginer :\nSi on lui attribue une même valeur parmi l'ensemble des producer process d'un même stage, seul l'instance de producer la plus récente pourra prendre la main, en transformant les producers qui sont issus de la lecture de toutes les autres partitions, en zombies.\nSi on lui attribue une valeur complètement aléatoire et unique du type UUID, alors aucun producer ne sera transformé en zombie, pas même ceux qui auront été éjectés à cause d'un timeout.\nCes process à qui on aurait enlevé la responsabilité de leurs partitions, et qui seraient encore en train d'attendre qu'une transaction se termine, pourraient encore bloquer le fait que de nouveaux messages apparraissent dans leurs anciennes partitions pendant transactional.id.expiration.ms (par défaut 1 heure).\nDans le cas où ces process auraient encore des messages dans leur buffer, ils pourraient aussi continuer à exécuter leurs callbacks.\nLa bonne solution est d'assigner un transactional ID composé de la concaténation entre l'input topic et l'index de la partition de ce topic qu'on est en train de consommer.\nLe résultat c'est potentiellement un grand nombre de producers créés, avec chacun son transactional ID composé du topic et de l'index de la partition.\nPour éviter d'en avoir trop, l'approche privilégiée est de ne créer que les producers pour les partitions assignées à un consumer donné, et de les supprimer si les partitions sont rebalancées et enlevées.\nCôté consumers, la notion de transaction se matérialise dans le choix de ce qui sera lu.\nQuand le producer publie des messages dans des topics dans le cadre d'une transaction, il va les publier directement et de manière irrévocable, mais ils seront entourés de markers.\nIl y a un marker pour indiquer le début de la transaction dans la partition, et un autre pour indiquer la fin de transaction réussie (COMMITTED) ou échouée (ABORTED).\nLe consumer dispose d'une option isolation.level (par défaut read_uncommited).\nLa valeur read_uncommited permet de lire tous les records de la partition, ceux qui ne font pas partie d'une transaction comme ceux qui en font partie, que la transaction soit validée, annulée, ou toujours en cours.\nLa valeur read_commited permet de ne lire que les records qui ne font pas partie d'une transaction, ou ceux qui sont dans une transaction validée.\nPour un consumer qui a read_commited activé, l'End Offset est remplacé par la notion de LSO (Last Stable Offset), qui pointe vers le dernier record qui ne fait pas partie d'une transaction non terminée.\nTant que la transaction est en cours, le consumer ne pourra pas lire plus loin.\nLes transactions ont un certain nombre de limitations.\nLe système de transaction de Kafka n'est pas compatible avec d'autres systèmes de transaction comme XA ou JTA.\nUne transaction est limitée à un même producer (même transactional ID, même PID).\nLa transaction peut être lue de manière partielle par des consumers sans qu'ils s'en rendent compte : il suffit que le consumer n'ait à sa charge que certaines partitions où la transaction a publié des messages, mais pas les autres.\nLa exactly-once delivery ne s‘applique pas aux side effects en dehors de Kafka : par exemple on peut jouer une callback plusieurs fois, et ajouter plusieurs entrées en DB, même si côté Kafka les messages sont bien publiés exactly-once.\nFaut-il utiliser les transactions ?\nOn peut se poser la question de la complexité additionnelle par rapport à ce que ça apporte : une déduplication des messages à travers les stages.\nDans le cas où la consommation de nos messages n'a que des side-effects idempotents, alors avoir des messages en double dans Kafka peut ne pas être problématique.\nD'un autre côté, la complexité en question peut être abstraite dans une couche adapter."}},"/books/get-your-hands-dirty-on-clean-architecture":{"title":"Get Your Hands Dirty on Clean Architecture","data":{"1---maintainability#1 - Maintainability":"L'architecture représente la structure du système logiciel.\nLa maintenabilité (dans le contexte de ce livre la capacité** à changer le logiciel** facilement) est plus importante que toutes les autres caractéristiques, y compris fonctionnelles.\nSi on peut facilement changer le logiciel, alors on peut le faire aller dans n’importe quelle direction : plus scalable, plus sécurisé etc. et on peut ajouter des fonctionnalités.\nElle est vectrice de pacification : quand le logiciel est difficile à changer, le business se retourne contre les devs et on tombe sur un manque de confiance et des conflits.\nElle rend les développeurs heureux, et donc plus productifs et moins prompts à changer d’entreprise.\nElle permet de se reposer sur la possibilité de faire des choix de design faciles : il suffit de choisir l’option qui offre la meilleure maintenabilité.\nLes entreprises qui ont un logiciel qui marche, et qui n’est pas maintenable ont :\nSoit un logiciel en fin de vie avec peu de changements.\nSoit elles jettent énormément d’argent par la fenêtre et peuvent se le permettre.\nComment être sûr qu’on conserve la maintenabilité ? Au moins en s’assurant qu’on choisit et conserve une architecture qui la permet.\nLa clean archi en est une possibilité.","2---whats-wrong-with-layers#2 - What's Wrong With Layers?":"La layered architecture est tout à fait classique : 3 couches successives (web -> domain -> persistance).\nElle peut même permettre une bonne architecture qui laisse les options ouvertes (par exemple remplacer la persistance en ne touchant que cette couche-là).\nLe problème de la layered architecture c'est qu'elle se détériore rapidement et encourage les mauvaises habitudes.\nElle promeut le database-driven design : vu que la persistance est à la base, on part toujours depuis la modélisation de la structure de la DB.\nAu lieu de ça on devrait mettre au centre le comportement, c'est-à-dire le code métier, et considérer la persistance comme périphérique.\nUn des éléments qui pousse au database-driven design aussi c'est l'ORM, qui peut être utilisé depuis le layer domain, et introduit des considérations techniques dedans.\nElle encourage les raccourcis : quand un layer du dessus a besoin d'un élément du dessous, il suffit de le pousser vers le bas et il y aura accès.\nLa couche de persistance finit par grossir et devenir une énorme couche “utilitaire” qui contient la logique et les aspects techniques entremêlés.\nElle devient de plus en plus difficile à tester :\nLe logique métier a tendance à fuiter vers la couche web, parce que la persistance y est directement utilisée.\nTester la couche web devient de plus en plus difficile parce qu'il faut mocker les deux autres, et parce que la logique y grossit.\nElle cache les use cases : la logique fuitant vers les autres layers, on ne sait pas où ajouter un nouveau use-case, ni où chercher un existant.\nOn peut ajouter à ça que vu qu'il n'y a pas de limite, la couche domain a des unités (services) qui grossissent au fil du temps, ce qui rend plus difficile de trouver où va chaque fonctionnalité.\nElle rend le travail en parallèle difficile :\nComme on fait du database-driven design, on doit toujours commencer par la couche de persistance, et on ne peut pas être plusieurs à la toucher.\nSi en plus les services du domaine sont gros, on peut être en difficulté pour être plusieurs à modifier un gros service pour plusieurs raisons.","3---inverting-dependencies#3 - Inverting dependencies":"Le Single Responsibility Principle (SRP) dit qu'un composant doit avoir une seule raison de changer.\nÇa veut dire que si on change le logiciel pour n'importe quelle autre raison, il ne devrait pas changer.\nQuand nos composants dépendent les uns des autres, ça leur donne autant de raisons à chaque fois de changer, si un des composants dont ils dépendent change lui aussi.\nLa layered architecture fait que la couche web et la couche domain ont des raisons de changer liées à la couche de persistance. On n'a pas envie que la couche domain change pour d'autres raisons qu'elle-même, donc on va inverser les dépendances qu'elle a.\nC'est le Dependency Inversion Principle (DIP).\nOn va copier les entities depuis la persistance vers la couche domain qui en a besoin aussi.\nEt on va créer une interface de persistance dans le domaine, à laquelle va adhérer la couche persistance qui aura donc une dépendance vers le domaine plutôt que l'inverse.\nSelon Robert Martin la clean architecture doit garder le domaine séparé du reste (frameworks, infrastructure etc.), et les dépendances doivent être tournées vers le code du domaine pour que celui-ci n'en ait pas d'autres que lui-même.\nLes entities du domaine sont au centre.\nIls sont utilisés par les use cases, qui représentent les services, mais impliquent d'avoir une granularité fine.\nCette séparation a un coût, qui est qu'il faut dupliquer les entités entre le domaine et l'infrastructure, notamment pour éviter que les entités du domaine soient polluées par la technique.\nL'hexagonal architecture est similaire à la clean architecture mais un peu moins “abstraite”, c'est la version d'Alistair Cockburn.\nL'hexagone contient les entities et les use cases, et en dehors on trouve des adapters pour intégrer la communication avec l'extérieur.\nOn a deux types d'adapters :\nLes adapteurs de gauche drivent l'hexagone, parce qu'ils appellent des fonctions exposées par l'hexagone.\nExemple : handlers HTTP.\nLes adapters de droite sont drivées par l'hexagone, parce que l'hexagone appelle des méthodes sur eux.\nExemple : communication avec la DB.\nPour permettre la communication, l'hexagone définit des ports (interfaces), qui doivent être implémentés par les adapters.\nC'est pour ça qu'on parle de Ports & Adapters.\nQuel que soit leur nom, tout l'intérêt de ces architectures c'est de permettre d'avoir un domaine isolé, dont on pourra gérer la complexité sans qu'il ait d'autres raisons de changer que lui-même.","4---organizing-code#4 - Organizing Code":"On peut organiser le code par couches : le classique web, domain et persistance, mais avec une inversion de la persistance vers le domain.\n- web\n- AccountController\n- domain\n- Account\n- AccountService\n- AccountRepositoryPort\n- persistance\n- AccountRepositoryImpl\nMais cette organisation est sous-optimale pour 3 raisons :\nIl n'y a pas de séparation sous forme de dossiers ou de packages pour les fonctionnalités. Donc elles vont vite s'entre-mêler au sein de chaque couche.\nComme les services sont gros, on peut difficilement repérer la fonctionnalité exacte qu'on cherche tout de suite.\nOn ne voit pas au premier coup d'œil quelle partie de la persistance implémente quel port côté domain. L'architecture ne saute pas aux yeux.\nOn peut ensuite organiser le code par features : les limites de dossier/package sont définies par les features qui contiennent un fichier par couche.\n- account\n- Account\n- AccountController\n- AccountRepository\n- AccountRepositoryImpl\n- SendMoneyService\nOn a nos features visibles immédiatement (Account -> SendMoneyService), ce qui fait qu'on est dans le cadre d'une screaming architecture.\nPar contre, nos couches techniques sont très peu protégées, et le code du domaine n'est plus protégé du reste par des séparations fortes.\nOn peut enfin organiser le code dans une architecture expressive, reprenant le meilleur des deux autres :\nUne séparation initiale par features majeures.\nPuis une séparation par couches à l'intérieur de ces features majeures.\nEt enfin la séparation explicite des ports et adapters, en explicitant leur nature entrante ou sortante.\n- account\n- adapter\n - in\n - web\n - AccountController\n - out\n - persistance\n - AccountPersistanceAdapter\n- domain\n - Account\n- application\n - SendMoneyService\n - port\n - in\n - SendMoneyUseCase\n - out\n - LoadAccountPort\n - UpdateAccountStatePort\nLe fait que l'architecture soit alignée avec la structure en packages fait que nous avons moins de chances d'en dévier. Elle est incarnée de manière très concrète dans le code.\nLe domaine étant isolé, on peut très bien en faire ce qu'on veut, y compris y appliquer les patterns tactiques du DDD.\nCôté visibilité des packages :\nLes adapters peuvent rester privés, puisqu'ils ne sont appelés qu'à travers les ports.\nLes ports doivent être publics pour être accessibles par les adapters.\nLes objets du domaine doivent être publics pour être accessibles depuis les services et les adapters.\nLes services peuvent rester privés parce qu'ils sont appelés à travers les ports primaires.\nConcernant la manière dont fonctionne l'inversion de dépendance ici :\nPour les adpaters entrants il n'y a pas besoin d'inversion puisqu'ils sont déjà entrants vers l'hexagone. On peut, au besoin, quand même protéger l'hexagone derrière des ports quand même.\nPour les adapters sortants par contre il faut inverser la dépendance, en les faisant respecter le port de l'hexagone, puis en les instanciant et les donnant à l'hexagone.\nIl faut donc un composant tiers neutre qui instancie les adapters sortants pour les donner à l'hexagone, et instancie l'hexagone pour le donner aux adapters entrants.\nIl s'agit de l'injection de dépendance.","5---implementing-a-use-case#5 - Implementing a Use Case":"Comme on a une forte séparation hexagone/adapters, on peut implémenter l'hexagone de la manière dont on veut, y compris avec les patterns tactiques du DDD, mais pas forcément.\nDans ce chapitre on implémente un use-case dans l'hexagone de l'exemple buckpal qui est une application de gestion de paiement.\nLa couche domain se trouve dans buckpal -> domain, et contient une entité Account, qui a des méthodes pour ajouter et retirer de l'argent.\nChaque ajout ou retrait se fait en empilant des entités Activity qui contiennent chaque transaction dans un tableau interne à Account.\nLe tableau interne ne contient qu'une fenêtre d'Activity pour des raisons de performance, et une variable permet de connaître la valeur du compte avant ce nombre restreint d'Activity.\nLes use-cases se trouvent dans la couche applicative, dans buckpal -> application -> service.\nUn use-case va :\nRécupérer l'input (qu'il ne valide pas par lui-même pour laisser cette responsabilité à un autre composant).\nValider les règles business, dont la responsabilité est partagée avec la couche domain.\nManipuler l'état du domaine :\nÇa se fait en instanciant des entités et appelant des méthodes sur elles.\nEt en général en passant les entités à un adapter pour que leur état soit persisté.\nAppeler éventuellement d'autres use-cases.\nRetourner l'output.\nLes use-cases vont être petits pour éviter le problème de gros services où on ne sait pas quelle fonctionnalité va où.\nLa validation des inputs se fait dans la couche applicative pour permettre d'appeler l'hexagone depuis plusieurs controllers sans qu'ils aient à valider ces données, et pour garantir l'intégrité des données dans l'hexagone.\nOn va faire la validation dans une classe de type command. Cette classe valide les données dans son constructeur, et refuse d'être instanciée si les données sont invalides.\nL'auteur déconseille d'utiliser le pattern builder et conseille d'appeler directement le constructeur.\nExemple de builder :\nnew CommandBuilder().setParameterA(value1).setParameterB(value2).build();\nCette classe va se trouver dans buckpal -> application -> port -> in.\nElle constitue une sorte d'anti-corruption layer protégeant l'hexagone.\nOn pourrait être tenté de réutiliser des classes de validation d'input entre plusieurs use-cases ressemblants, par exemple la création d'un compte et la modification d'un compte. L'auteur le déconseille.\nSi on réutilise, on va se retrouver avec quelques différences (par exemple l'ID du compte) qui vont introduire de potentielles mauvaises données.\nOn va se retrouver à gérer les différences entre les deux modèles dans les use-cases alors qu'on voulait le faire dans un objet à part.\nGlobalement, faire des modèles de validation d'input permet, au même titre que le fait de faire des petits use-cases, de garder l'architecture maintenable dans le temps.\nLa validation des business rules doit quant à elle être faite dans les use-cases.\nLa différence avec la validation des inputs c'est que pour les inputs il n'y a pas besoin d'accéder à l'état du modèle de données, alors que pour les business rules oui.\nLe use-case peut le faire directement en appelant des fonctions, ou alors le déléguer à des entities.\nDans le cas où l'essentiel de la logique est fait dans les entities et où le use-case orchestre juste des appels et passe les données, on parle d'un rich domain model. Dans le cas où c'est le use-case qui a l'essentiel de la logique et les entities sont maigres, on parlera d'un anemic domain model.\nLe use-case lève une exception en cas de non-respect des règles business comme pour les règles d'input. Ce sera à l'adapter entrant de décider de ce qu'il fait de ces exceptions.\nConcernant l'output renvoyé par le use-case, il faut qu'il soit le plus minimal possible : n'inclure que ce dont l'appelant a besoin.\nSi on retourne beaucoup de choses, on risque de voir des use-cases couplés entre eux via l'output (quand on ajoute un champ à l'objet retourné, on a besoin de changer tous ceux qui le retournent).\nLes use-cases qui font uniquement de la lecture pour renvoyer de la donnée peuvent être distinguées des use-cases qui écrivent.\nPour ça on peut les faire implémenter un autre port entrant que les use-cases d'écriture, par exemple le port GetAccountBalanceQuery.\nOn pourra à partir de là faire du CQS ou du CQRS.","6---implementing-a-web-adapter#6 - Implementing a Web Adapter":"Tous les appels extérieurs passent par des adapters entrants.\nComme le flow d'appel est déjà dirigé de l'adapter vers l'hexagone, on pourrait enlever le port, et laisser l'adapter appeler directement les use-cases.\nLa matérialisation des ports permet d'avoir un endroit où tous les points d'entrée de l'hexagone sont clairement identifiés et spécifiés. C'est utile pour la maintenance à long terme.\nEnlever ces ports fait partie des raccourcis discutés dans le chapitre 11.\nDans le cas où notre adapter est un WebSocket qui va appeler mais aussi qui sera appelé, alors il faut obligatoirement avoir les ports entrants, et surtout sortants, puisque là on a bien une inversion du sens d'appel.\nLes adapters web vont :\nCréer des objets internes à partir des objets HTTP.\nOn parle aussi de désérialisation.\nVérifier les autorisations.\nValider l'input, et le faire correspondre à l'input du use-case qui va être appelé.\nVu qu'on valide déjà l'input à l'entrée du use-case, on va ici seulement valider le fait que l'input reçu peut bien être converti dans l'input du use-case.\nAppeler le use-case.\nRécupérer l'output, et reconstruire un objet HTTP à partir de ça pour le renvoyer.\nOn parle aussi de sérialisation.\nL'adapter se trouve dans buckpal -> adapter -> in -> web.\nL'adapter web a la responsabilité de communiquer avec le protocole HTTP. C'est une responsabilité qu'il doit avoir seul, et donc ne pas faire fuiter des détails du HTTP dans le use-case.\nConcernant la taille des controllers, il vaut mieux les avoir les plus petits et précis possibles.\nIl vaut mieux éviter de créer par exemple une classe AccountController qui va avoir plusieurs méthodes associées chacune à un endpoint.\nCe genre de classe peut grossir et devenir difficile à maintenir. De même pour les tests associés.\nLe fait d'être dans une classe fait que ces controllers vont partager des fonctions et objets entre eux. Et donc vont être plus facilement couplés.\nOn peut nommer les classes de web adapter avec des noms plus précis que UpdateX, CreateX etc. Par exemple RegisterAccount.\nLes petits controllers permettent aussi de travailler plus facilement sur le code en parallèle.","7---implementing-a-persistence-adapter#7 - Implementing a Persistence Adapter":"Avec l'adapter de persistance, on a une vraie inversion des dépendances qui fait que malgré le sens des appels de l'hexagone vers l'adapter, grâce au port c'est l'adapter qui dépend de l'hexagone.\nOn va donc pouvoir faire des modifications dans l'adapter de persistance sans que ça n'affecte la logique business.\nLes adapters de persistance vont :\nPrendre l'input.\nCa peut être une domain entity ou un objet spécifique pour une opération en base.\nLe mapper au format base de données et l'envoyer à DB.\nEn général on va mapper vers les entities de l'ORM, mais ça peut aussi être vers des requêtes SQL directement.\nRécupérer la réponse DB et la mapper au format applicatif.\nEn général une domain entity.\nRenvoyer la valeur à l'application service.\nL'adapter se trouve dans buckpal -> adapter -> out -> persistance.\nIl faut que les modèles d'input et d'output vers et depuis le persistance adapter soient tous deux dans l'application core (hexagone).\nPlutôt qu'avoir un gros port qui permet d'accéder à toutes les méthodes de l'adapter correspondant à une entity, il vaut mieux avoir des ports plus fins.\nUne des raisons c'est qu'il est plus difficile de mocker le persistance adapter en entier que de mocker certaines de ses méthodes correspondant aux ports utilisés.\nLa plupart du temps on va se retrouver avec une méthode de l'adapter par port.\nPour ce qui est de l'adapter lui-même (celui qu'on accède via les ports), on peut l'avoir gros, mais on peut aussi le découper, par exemple pour avoir un adapter par entity, ou un par aggregate (si on utilise les patterns tactiques du DDD).\nOn pourrait aussi créer deux adapters pour une même entity : un pour les méthodes utilisant notre ORM, et un autre pour les méthodes utilisant du SQL directement.\nDans l'adapter on pourra donc utiliser les entities de l'ORM, ou alors des repositories faites à la main, qui contiendront des méthodes exécutant du SQL.\nSéparer les aggregates dans des adapters distincts permet aussi de faciliter l'extraction d'un bounded context par la suite.\nOn va donc avoir une duplication des domain entities dans le persistance adapter, en général sous forme d'entities d'ORM.\nUtiliser les entities ORM dans le domaine peut être un choix possible pour éviter des mappings (discuté dans le chapitre 8), mais ça a le désavantage de faire fuiter des contraintes spécifiques à la DB vers le domaine.\nL'adapter en lui-même :\nPour une lecture, il appelle des méthodes sur les entities ORM ou sur les repositories maison, puis il map le résultat à une entity domaine et la renvoie.\nPour une écriture, il récupère l'entity domaine, la map vers l'entity ORM ou vers la méthode de repository maison, et exécute la méthode sur celle-ci.\nConcernant les transactions, elles ne peuvent pas être dans l'adapter de persistance. Elles doivent être dans la fonction qui orchestre les appels à la persistance, c'est-à-dire les use-cases applicatifs.\nSi on veut garder nos use-cases purs, on peut recourir à l'aspect-oriented programming, par exemple avec AspectJ.","8---testing-architecture-elements#8 - Testing Architecture Elements":"Il y a 3 types de tests dans la pyramide :\nPlus on monte dans la pyramide, et plus les tests sont lents et fragiles, donc plus on monte et moins nombreux doivent être les tests.\nLes unit tests testent en général une seule classe.\nLes integration tests mettent en jeu le code de plusieurs couches, et vont souvent mocker certaines d'entre-elles.\nLes system tests testent le tout de bout en bout, sans mocks (autre que les composants qu'on ne peut pas instancier dans notre test).\nOn pourra parler de end-to-end tests si on teste depuis le frontend et non depuis l'API du backend.\nNDLR : l'auteur adopte une vision proche de l'école de Londres, en considérant qu'un test teste une unité de code (et pas une unité de comportement peu importe la quantité de code), et qu'en testant plusieurs bouts de code ensemble il faut mocker les bouts de codes voisins (c'est comme ça qu'on obtient des tests fragiles aux refactorings).\nCôté implémentation :\nLes domain entities sont testés avec des unit tests (state-based).\nNDLR : Dans l'exemple on teste Account, mais pour autant on ne mock pas Activity, on en construit une vraie instance. Donc sur cet exemple en tout cas, on n'est pas tout à fait dans l'école de Londre non plus.\nLes use-cases sont testés avec des unit tests (communication based) vérifiant que le use-case appelle la bonne méthode sur le domain entity et sur l'adapter de persistance.\nNDLR : Là on est bien dans la London school.\nL'auteur fait remarquer que le test utilisant des mocks, il est couplé à la structure du code et pas seulement au comportement. Et donc il conseille de ne pas forcément tout tester, pour éviter que ces tests cassent trop souvent.\nLes web adapters sont testés avec des tests d'intégration.\nOn parle ici d'intégration parce qu'on est “intégré” avec autre chose que du code pur, en l'occurrence avec la librairie de communication HTTP.\nIl s'agit de tests communication based, envoyant un faux message HTTP sur le bon path, et vérifiant qu'on a fait un appel sur le bon use-case mocké, avec la bonne commande.\nLes persistance adapters sont testés avec des tests d'intégration.\nIci pas de mocks, on construit des domain entities et on les passe à l'adapter pour qu'il le mette en base, et on vérifie depuis la base avec des méthodes de repository qu'on a écrit la bonne chose.\nSi besoin de données préalables, par exemple pour de la lecture, on exécute d'abord du SQL pour mettre la DB dans un état qui permettra cette lecture.\nLe fait de ne pas mocker est justifié par le fait qu'on perdrait toute confiance dans nos tests puisque chaque type de DB vient avec ses propres spécificités SQL.\nLes chemins principaux complets sont testés avec des system tests.\nOn utilise de vrais messages HTTP, et on ne mock rien dans nos couches.\nOn peut utiliser des fonctions helper pour rendre nos tests plus lisibles en extrayant des bouts de code. Ces fonctions forment un domain specific language qu'il est bon de cultiver.\nConcernant la quantité de tests :\nL'auteur suggère d'aller vers du 100% de coverage du code important. Garder quand même le 100% permet de lutter contre la théorie des vitres cassées.\nPour mesurer la fiabilité des tests, on peut mesurer à quel point on est confiant pour mettre en production notre changement.\nPour être toujours plus confiant, il faut mettre en production souvent.\nPour chaque bug en production, il faut se demander ce qu'on aurait pu faire pour qu'un test trouve le bug, et l'ajouter.\nLe fait de documenter les bugs comme ça permet d'avoir une mesure de la fiabilité des tests dans le temps.","9---mapping-between-boundaries#9 - Mapping Between Boundaries":"Les 3 composants principaux (driving adapter, hexagone et driven adapter) doivent avoir un modèle qui leur permet d'appréhender le système et ses entités. On peut choisir différentes stratégies de mapping entre ces modèles en fonction de leur unicité ou de leur différence.\n1- La no-mapping strategy consiste à avoir le même modèle dans l'adapter web, l'hexagone, et l'adapter de persistance.\nOn n'implémente la représentation des entities qu'une fois pour la réutiliser partout.\nChaque couche va avoir besoin de champs ou d'éléments techniques qui lui sont spécifiques, par exemple des annotations liées à HTTP pour l'adapter web, des annotations d'ORM pour l'adapter de persistance. Nos entities auront donc plusieurs raisons de changer.\nTant que toutes les couches ont besoin des informations formatées de la même manière, cette stratégie marche. C'est le cas pour les applications CRUD.\nA partir du moment où on commence à gérer des problèmes spécifiques au web ou à la persistance dans l'hexagone, alors il faut passer à une autre stratégie.\n2- La two-way mapping strategy consiste à avoir un modèle spécifique pour chacun des composants principaux (adapter web, hexagone et adapter de persistance), et de faire un mapping quand la donnée rentre, et un autre quand elle sort.\nL'avantage c'est que cette séparation des modèles permet d'adapter leur structure pour les besoins de chaque couche : besoins web (ex: sérialisation JSON), besoins du modèle, besoins de la persistance (ex: ORM).\nLe désavantage principal c'est le boilerplate conséquent.\nEt même avec l'utilisation de librairies de mapping, les bugs sont compliqués à trouver parce que le mapping est caché.\nUn autre désavantage c'est que malgré la séparation, les objets du modèle de l'hexagone sont quand même utilisés par les autres couches externes, ce qui fait qu'ils pourraient avoir besoin de changer pour des nécessités de ces couches.\n3- La full mapping strategy consiste à utiliser un mapping entre les 3 composants principaux comme pour la two-way mapping strategy, mais cette fois on va établir des modèles d'input et d'output fins spécifiques à chaque use-case.\nOn a encore plus de mapping que si on mappait juste les modèles des 3 composants, mais ce mapping va aussi être plus maintenable parce qu'il sera à chaque fois spécifique au use-case sans avoir besoin d'être adapté pour correspondre à de nouveaux besoins.\nL'auteur conseille ce pattern plutôt entre l'adapter web et l'hexagone qu'entre l'hexagone et l'adapter de persistance (parce qu'il y aurait vraiment trop de mappings).\nOn pourra aussi faire des variantes, par exemple utiliser cette stratégie pour le modèle d'entrée dans l'hexagone depuis l'adapter web, mais renvoyer les objets du modèle de l'hexagone en sortie vers l'adapter web.\n4 - La one-way mapping strategy consiste à avoir une interface commune aux trois modèles de chacun des trois composants.\nDe cette manière les objets peuvent être passés sans devoir obligatoirement les mapper. Si le mapping est nécessaire, il suffira à la couche qui en a besoin de le faire.\nQuand l'objet est passé de l'hexagone vers l'extérieur ils peuvent l'utiliser tel quel sans risquer de le modifier parce que les setters ne sont pas exposés.\nQuand l'objet passe vers l'hexagone, il devra en général être mappé pour reconstruire le comportement riche du domaine.\nOn peut le faire avec le pattern Factory du DDD.\nCette stratégie a de l'intérêt quand les modèles sont proches.\nLe modèle web peut facilement ne pas avoir besoin de mapper l'output venant de l'hexagone.\nLe désavantage c'est que cette stratégie est plus difficile à appréhender étant donné son caractère non systématique.\nIl faut adapter la stratégie en fonction des cas d'usage et de leur nature, et pas adopter une seule stratégie pour toute la codebase.\nOn peut avoir des différences de stratégie en fonction :\nDes lectures et écritures.\nDe la communication entre adapter web et hexagone, et hexagone et adapter de persistance.\nIl ne faut pas avoir peur de changer de stratégie en cours de route.\nLa plupart des applications commencent en étant CRUD, puis soit le restent, soit se complexifient suffisamment pour mériter un changement de stratégie de mapping.\nL'équipe doit se mettre d'accord sur des stratégies à choisir pour chaque partie de la codebase, et surtout noter pourquoi elle fait ce choix pour pouvoir réévaluer plus tard si le choix doit être modifié ou non.\nIl peut être intéressant aussi de noter dans quel cas elle prévoit de changer de stratégie.","10---assembling-the-application#10 - Assembling the Application":"Nous voulons garder l'inversion de dépendance entre les composants externes et l'hexagone.\nDonc nous devons instancier les adapters pour les donner au constructeur des objets de l'hexagone par le mécanisme qui s'appelle l'injection de dépendance.\nNous devons avoir un composant de configuration qui soit neutre du point de vue notre architecture, et qui ait accès à tous les composants pour les instancier.\nIl va :\nCréer les adapters web et s'assurer que les requêtes HTTP sont câblées aux bons adapters.\nCréer les adapters de persistance et s'assurer qu'elles aient accès à la base de données.\nCréer les use cases, et au moment de leur création, leur donner les adapters web et les adapters de persistance dans leur constructeur, pour qu'elles y aient accès (injection de dépendance).\nIl va aussi passer certaines valeurs de configuration aux autres composants (serveur de base de données, serveur SMTP etc).\nIl aura toutes les raisons de changer\nCôté implémentation :\nOn peut créer le composant avec du code sans librairie :\nfunction main() {\n const accountRepository = new AccountRepository();\n const activityRepository = new ActivityRepository();\n const accountPersistanceAdapter = new AccountPersistanceAdapter(\n acountRepository,\n activityRepository\n );\n const sendMoneyUseCase = new SendMoneyService(accountPersistanceAdapter);\n const sendMoneyController = new SendMoneyController(sendMoneyUseCase);\n startProcessingWebRequests(sendMoneyController);\n}\nCette méthode a le désavantage d'amener à écrire beaucoup de code, et d'obliger à ce que les classes de chaque composant soient accessibles publiquement.\nParmi les techniques impliquant une librairie, en Java on a\nLe classpath scanning où il s'agit d'annoter les classes de chaque composant et demander à Spring de scanner les classes pour trouver celles qu'il faut instancier et injecter.\nElle est plus rapide mais peut mener à des bugs difficiles à trouver parce que le système de scanning est obscur.\nL'autre méthode c'est d'écrire des classes de configuration qui vont indiquer quelles classes doivent être instanciées et injectées.\nOn va écrire plus de code pour obtenir plus de découplage et de transparence sur ce qui est fait.","11---enforcing-architecture-boundaries#11 - Enforcing Architecture Boundaries":"Ce chapitre traite de la manière d'éviter que l'architecture choisie s'érode au fil du temps, causant un manque de clarté et une lenteur à faire des changements.\nLa principale chose à faire respecter est le sens des dépendances parmi les couches (domaine -> application -> adapters -> configuration).\nOn peut utiliser la visibilité de package, si le langage le permet (package-private modifier en Java), pour rendre le contenu des couches plus cohésives.\nSi on part du principe que notre composant de configuration est une librairie qui passe outre la visibilité de package et qu'elle peut instancier même les classes privées à leur package, alors on n'a que les relations entre couches à se préoccuper :\nLes adapters et les use cases peuvent être privés au package de leur couche.\nLes entities du domaine doivent être publiques pour être accédées par les couches du dessus, de même que les ports qui doivent être accédés par les adapters qui les implémentent.\nOn peut aussi utiliser des moyens au runtime pour empêcher que le sens des dépendances s'inverse, par exemple au moment des tests.\nEn Java il y a la librairie ArchUnit qui permet de faire des tests d'architecture, y compris des tests sur le sens des dépendances, en vérifiant qu'un package ne dépend pas d'un autre package (importe rien qui vienne de lui).\nOn peut construire par dessus ce genre de librairie pour obtenir un DSL (Domain Specific Language) dans nos tests, qui vérifie les dépendances pour un bounded context donné :\nHexagonalArchitecture.boundedContext(\"account\")\n .withDomainLayer(\"domain\")\n .withAdaptersLayer(\"adapter\")\n .incoming(\"web\")\n .outgoing(\"persistence\")\n .and()\n .withApplicationLayer(\"application\")\n .services(\"service\")\n .incomingPorts(\"port.in\")\n .outgoingPorts(\"port.out\")\n .and()\n .withConfiguration(\"configuration\")\n .check(new ClassFileImporter().importPackages(\"buckpal\"));\n}\nAttention par contre : ce genre de test est vulnérable aux refactorings. Il suffit que le nom des packages change et aucun problème ne sera trouvé sur des packages qui n'existent pas.\nOn peut enfin profiter de la phase de build de notre application pour créer autant d'artefacts de build que nécessaire, et profiter de l'outil de build pour garantir les limites des composants de notre architecture.\nOn peut découper avec diverses granularités :\nUne première solution c'est de faire 3 groupes de build : la configuration, les adapters, et l'application (hexagone).\nOn peut décider de séparer les types d'adapter pour qu'ils restent autonomes.\nUn cran plus loin encore, on peut isoler les ports dans une unité à part. De cette manière on empêche la no mapping strategy.\nOn peut enfin aussi séparer les types entrants et sortants des ports pour plus de clarté, et séparer le domaine et l'application service dans deux unités pour empêcher le domaine d'accéder aux use cases.\nUn des avantages c'est que l'outil de build nous empêchera d'avoir des dépendances circulaires entre nos unités de build, améliorant l'aspect single responsibility de nos modules.\nÊtre obligé de maintenir un script de build permet aussi de faire des choix en conscience pour ce qui est du placement des diverses classes.\nD'un autre côté c'est un certain travail de maintenance quand même, donc il vaut mieux que l'architecture soit un minimum stable d'abord.\nL'idée c'est de combiner les trois méthodes pour avoir une architecture solide dans le temps.\nA noter que plus on découpe finement, plus on devra faire de mappings.","12---taking-shortcuts-consciously#12 - Taking Shortcuts Consciously":"Les raccourcis doivent être connus et compris pour être évités dans le but de garantir l'intégrité de l'architecture, et dans certains cas acceptés en toute conscience.\nLa théorie des vitres cassées vient d'un psychologue (Philip Zimbardo) qui a conduit une expérience en laissant une voiture dans un quartier chaud, et une autre dans un quartier chic.\nLa première a été désossée rapidement puis les passants ont commencé à la dégrader.\nLa 2ème a été laissée intacte pendant une semaine. Puis le psychologue a cassé une de ses vitres, et à partir de là elle a été dégradée aussi vite que la première, par des gens de tout type.\nL'idée est de dire que nous avons une tendance naturelle à en rajouter quand les choses sont déjà en mauvais état ou mal rangées. La même chose s'applique au code et à son architecture.\nIl est de la responsabilité des développeurs de garder l'architecture la plus clean possible dès le début.\nOn peut cependant prendre des raccourcis choisis en conscience, faire des compromis qu'on assume.\nPour que l'effet des vitres cassées soit limité, il faut documenter les raccourcis choisis par l'équipe dans des ADR (architecture Decision Record).\nParmi les raccourcis possible dans l'architecture hexagonale on a :\nUtiliser le même modèle pour les inputs (ou les outputs) de deux use cases.\nLa question à se poser est : est-ce que ces deux use cases sont voués à évoluer ensemble ou non ?\nSi oui alors il faut que leurs inputs et leurs outputs aient des modèles séparés. Si non alors on peut (et on doit) les coupler au niveau de leurs inputs et outputs.\nIl ne faut donc pas oublier de régulièrement reconsidérer si deux use cases imaginés comme voués à évoluer ensemble, ne doivent pas être désormais considérés de manière séparée.\nUtiliser des entities du domaine comme modèles d'entrée ou de sortie de la couche applicative.\nIl s'agit d'avoir les ports gauches (l'entrée et la sortie depuis l'adapter web vers l'hexagone) basés sur des domain entities.\nEn faisant ça on va être tenté d'ajouter des champs à l'entity du domaine à chaque fois qu'on en aura besoin dans notre input ou output de la couche applicative. Donc le domaine va dans une certaine mesure dépendre de l'application..\nC'est OK de le faire si on est sur des use cases simple type create/update parce que dans ce cas c'est bien le contenu exact des domain entities qu'on veut.\nIl ne faut pas oublier de reconsidérer régulièrement les use cases où on l'a fait et qui se sont complexifiés.\nNe pas utiliser de ports entrants pour l'adapter web.\nLe sens du flow étant le même que le sens des appels pour l'adapter web, on peut très bien lui permettre d'appeler directement les use cases, sans passer par une interface.\nOn perd alors la clarté des points d'entrée qui ne sautent plus aux yeux aussi bien qu'avec des ports explicites.\nOn ne peut plus non plus utiliser des moyens pour forcer l'hexagone à ne pas dépendre de l'adapter web.\nCette technique est donc à réserver aux petites applications, ou celles qui n'ont qu'un adapter web.\nNe pas créer d'application service.\nParfois l'application service ne fait rien à part appeler une méthode dans l'adapter de persistance et retourner son résultat à l'adapter web. Dans ce cas, on peut laisser l'adapter web appeler directement l'adapter de persistance.\nC'est le cas quand on a des use cases CRUD.\nOn a alors nos domain entities qui sont passés entre adapters gauches et droits.\nLe danger c'est que si la fonctionnalité se complexifie, la logique métier sera ajoutée dans l'adapter de persistance. Il faut dans ce cas absolument créer un application service.\nLa plupart des raccourcis sont adaptés aux fonctionnalités simples de type CRUD. On commence en général comme ça, et on réévalue les raccourcis si l'application se complexifie. Si elle reste simple on pourra garder les raccourcis.","13---managing-multiple-bounded-contexts#13 - Managing Multiple Bounded Contexts":"On finit en général par créer des Bounded Contexts (BC) pour séparer certaines parties de la codebase des autres,. C’est utile entre autres pour leur permettre d’évoluer indépendamment, et pour garder des morceaux de code indépendants qui peuvent rentrer dans la mémoire immédiate d’un être humain.\nLa solution la plus immédiate pour gérer plusieurs BCs en clean architecture est de faire les choses de manière bien découplée :\nOn crée :\nUn output port sur le core du BC appelant.\nUn input port sur le core du BC appelé.\nUn output adapter (qu’on pourrait appeler Domain Adapter) qui implémente l’output port, et appelle l’input port.\nLe problème est qu’avec un grand nombre de BCs, le nombre de ces ports et adapters à créer va être potentiellement de plus en plus grand.\nDans l’article original d’Alistair Cockburn sur l’hexagonal architecture, il n’y a pas de mention de Bounded Context mais seulement d’Application pour ce qui est du core d’un hexagone.\nL’auteur en déduit qu’on peut considérer que les BCs peuvent exister à l’intérieur d’un même hexagone.\nOn se retrouve avec un input adapter web, plusieurs use-cases à l’intérieur du core, et un output adapter de persistance.\nL’auteur dit qu’on peut avoir un ou plusieurs input ports pour chaque BC selon les cas.\nPour les output ports de persistance, il dit qu’il est préférable de ne pas les partager entre BCs, parce qu’on ne veut pas que les BCs partagent la DB et aient le même data model. Ils doivent au moins avoir des schémas différents, ou au minimum des tables différentes.\nIl ne dit rien sur les adapters en eux-mêmes, il s’agit bien des ports dont il faut éventuellement en faire plusieurs.\nPour faire communiquer les BCs entre eux, l’auteur propose deux solutions tirées du DDD, et nous propose de lire la littérature DDD pour en savoir plus.\n1 - Un use-case qui a besoin d’une info venant d’un use-case d’un autre BC peut consommer des domain events que l’autre émet, les stocker en base chez lui, et les réutiliser ensuite.\n2 - On peut avoir un application service à l’intérieur de l’hexagone qui coordonne les appels aux use-cases de chaque BC (qui sont appelés Domain Service).\nDans ce cas précis, on peut utiliser l'application service extérieur aux BCs pour porter une transaction DB, qui pourrait être utile pour l’ensemble des BCs mis en jeu.\nNDLR : déjà que c’est chaud d’envisager une transaction pour plusieurs aggregates, comment on fait pour envisager une transaction pour plusieurs use-cases dans plusieurs BCs différents ?","14---a-component-based-approach-to-software-architecture#14 - A Component-Based Approach to Software Architecture":"La component-based architecture peut être utile :\nEn complément de l’hexagonal architecture dans le cas où on choisit d’avoir un hexagone pour plusieurs bounded contexts, mais qu’on veut faire apparaître les limites entre chaque bounded context à l’intérieur du core.\nA la place de l’hexagonal architecture si celle-ci s’avère trop complexe pour notre projet, ou si les compétences dans l’équipe sont insuffisantes et qu’on veut se rabattre sur une architecture plus simple.\nLa modularité permet de construire des systèmes complexes.\nElle nécessite d’avoir des modules (l’auteur préfère le terme component) qui :\n1 - Encapsulent des détails d’implémentation.\n2 - Exposent une interface.\nUn component peut contenir des sub-components qui encapsulent eux-aussi de la logique et exposent une API. Leur API ne sera accessible qu’à l’intérieur du composant qui les contient.\nCes quelques règles simples permettent de définir une architecture modulaire.\nL’auteur présente un exemple de projet utilisant cette architecture : il s’agit d’un logiciel faisant du scraping de pages web, de l’analyse sur ce qui a été récolté, et stocke ensuite ça dans une DB.\nLe logiciel est un component en lui-même puisqu’il présente une API publique et cache le reste.\nIl contient 3 sub-components :\nUn component servant d’input adapter consommant une queue.\nUn component contenant la logique business pour analyser les données des pages et sortir les résultats à stocker.\nUn component servant d’output adapter de persistance, pour stocker le résultat en DB.\nIl ne faut pas oublier d’ajouter des contraintes pour garantir l’architecture (avec des outils comme ArchUnit). Les règles sont très simples :\nAucun code extérieur à un component ne doit accéder au code à l’intérieur de celui-ci. Il ne peut accéder qu’à son API publique.\nQuand un component est à l’intérieur d’un autre component, il fait partie des détails d’implémentation de celui-ci.","15---deciding-on-an-architecture-style#15 - Deciding on an Architecture Style":"La question dans ce chapitre est : quand est-ce qu'il faut utiliser l'architecture hexagonale ?\nLa première chose c'est que cette architecture met le domaine au centre et l'isole du reste, pour pouvoir travailler dessus.\nSi on n'a pas besoin de ça, on n'a sans doute pas besoin d'architecture hexagonale.\nÇa va bien avec le DDD.\nLa deuxième chose c'est qu'il faut expérimenter cette architecture au moins sur un module de notre application, pour voir ce qu'elle apporte et voir où est-ce qu'on peut l'utiliser.\nEt enfin la 3ème chose c'est que le choix de l'architecture dépend de nombreux critères :\nLe type du logiciel.\nLe rôle du domaine dans le code.\nL'expérience de l'équipe.\netc.\nNDLR : l'auteur ne se mouille pas beaucoup.\nPour info Vlad Khononov conseille dans Learning Domain Driven Design l'heuristique d'adopter l'architecture hexagonale dans le cas où on a un code subdomain et qu'on choisit les patterns tactiques du DDD, et d'adopter une architecture en couches sinon.\nOn peut imaginer qu'étant donné que l'auteur donne des raccourcis pour les use cases CRUD, y compris ceux qui resteront CRUD, il serait pour privilégier l'usage de l'architecture hexagonale la plupart du temps, en modulant avec ces raccourcis."}},"/books/effective-typescript":{"title":"Effective TypeScript","data":{"1---getting-to-know-typescript#1 - Getting to Know TypeScript":"","item-1--understand-the-relationship-between-typescript-and-javascript#Item 1 : Understand the Relationship Between TypeScript and JavaScript":"Tout programme JavaScript est un programme TypeScript, mais l'inverse n'est pas vrai.\nLe transpiler TypeScript indique des problèmes y compris sur du code JavaScript pur.\nIl y a une différence entre transpiler et type-checker du code.\nLe type-check est plus strict, et ne laisse pas passer certaines des bizarreries de JavaScript.","item-2--know-which-typescript-options-youre-using#Item 2 : Know Which TypeScript Options You're Using":"On a la possibilité de choisir des options pour le type-checker. Parmi les plus importants que l'auteur conseille d'activer :\nnoImplicitAny : on empêche l'inférence automatique de type any. Les any ne seront autorisés que s'ils sont explicitement écrits.\nstrictNullChecks : on empêche l'assignation de null et d'undefined à n'importe quelle variable, sauf si on le définit explicitement.\nPar exemple, on n'aura plus le droit de faire const x: number = null;.\nCa aide à repérer les cas où on va avoir une erreur “Cannot read properties on undefined” au runtime.\nstrict : empêche la plupart des erreurs runtime que TypeScript peut éviter, et inclut les deux autres.","item-3--understand-that-code-generation-is-independent-of-types#Item 3 : Understand That Code Generation Is Independent of Types":"Le type-checking et la transpilation sont indépendants. On peut tout à fait transpiler avec tsc du code qui a des erreurs au type-checker.\nUn des avantages c'est qu'on peut exécuter le code avant même d'avoir fixé toutes les erreurs de type.\nLes types disparaissent et n'ont aucun impact au runtime.\nPour faire du type-checking au runtime, il faut se baser sur des objets JavaScript : par exemple des classes.\nLes tagged unions sont aussi courants :\ninterface Square {\n kind: \"square\";\n width: number;\n}\ninterface Rectangle {\n kind: \"rectandle\";\n width: number;\n height: number;\n}\ntype Shape = Square | Rectangle;\nfunction calculateArea(shape: Shape) {\n if(shape.kind === \"rectangle\") {\n// [...]","item-4--get-comfortable-with-structural-typing#Item 4 : Get Comfortable with Structural Typing":"Le typage de TypeScript est structurel. Ca veut dire qu'une valeur avec un type structurellement compatible sera acceptée, même si le type n'est pas exactement le même.\nEn pratique, ça veut surtout dire qu'un objet qui a des attributs supplémentaires pourra être passé là où on attendait un objet avec moins d'attributs.\nC'est pour cette raison par exemple qu'Object.keys(v) ne renvoie pas le type des keys de l'objet mais des strings : on n'est pas sûr qu'il n'y ait pas des attributs en plus.\nCa s'applique aussi aux classes : attendre un type d'une classe ne garantit pas qu'on n'aura pas un objet custom ou une autre classe qui a au moins les mêmes attributs et éventuellement d'autres en plus.","item-5--limit-use-of-the-any-type#Item 5 : Limit Use of the any Type":"L'utilisation d'any ou d'as any permet de désactiver le type-checking, il faut l'éviter au maximum.\nIl permet de “casser les contrats”, par exemple une fonction attendant un type précis acceptera quand même un objet qu'on a typé any.\nIl empêche l'autocomplétion, et même le renommage automatique d'attribut (si une variable est marquée comme any, l'éditeur ne pourra pas savoir qu'il faut renommer un de ses attributs).\nIl sape la confiance dans le type system.","2---typescripts-type-system#2 - TypeScript's Type System":"","item-6--use-your-editor-to-interrogate-and-explore-the-type-system#Item 6 : Use Your Editor to Interrogate and Explore the Type System":"TypeScript fournit un compilateur (tsc), mais aussi un serveur standalone (tsserver) permettant notamment de faire de l'introspection de types. C'est ça qui est utilisé par l'éditeur.\nIl ne faut pas hésiter à passer la souris sur un appel de fonction dans une chaîne d'appels pour connaître les types inférés à ce moment-là.","item-7--think-of-types-as-sets-of-values#Item 7 : Think of Types as Sets of Values":"Le typage de TypeScript peut être interprété comme un set de types.\nnever est le set vide.\nUn littéral contient une seule valeur dans le set.\nA | B réalise l'union entre A et B.\nA & B réalise l'intersection entre A et B.\n& entre deux objets permet d'obtenir le type d'un objet avec l'ensemble des attributs des deux.\nExemple :\ninterface Person {\n name: string;\n}\ninterface Lifespan {\n birth: Date;\n death?: Date;\n}\ntype PersonSpan = Person & Lifespan;\nC'est le cas parce qu'on peut ajouter autant d'attributs en plus qu'on veut, vu que c'est du structural typing. Donc l'intersection se trouve être un objet avec les propriétés des deux obligatoirement (sinon ce ne serait pas une intersection), et d'autres propriétés non indiquées optionnellement.\nPour assigner une valeur à une variable, il faut que tous les éléments du set du type de la valeur soient contenus dans le type de la variable.\nextends permet d'indiquer la même chose : tous les éléments du type qui était doivent être inclus dans le type qui est étendu.","item-8--know-how-to-tell-whether-a-symbol-is-in-the-type-space-or-value-space#Item 8 : Know How to Tell Whether a Symbol Is in the Type Space or Value Space":"Il existe deux espaces différents dans lesquels des symboles peuvent se référer à des choses : le Type space et le Value space.\nUn même symbole peut être défini dans l'un et l'autre de ces espaces pour désigner différentes choses.\nLe fait d'être dans l'un ou l'autre de ces espaces dépend du contexte dans lequel on se trouve. Par exemple en assignation à un type, en assignation à une variable let ou const, après une variable suivie d'un : etc…\nLe TypeScript Playground permet facilement de se rendre compte de ce qui est dans le Type space : ça disparaît à la transpiration.\nLes classes et les enums créent en même temps un symbole dans le Type space, et un autre dans le Value space.\nLe type issu d'une classe représente sa structure d'attributs.\nLe mot clé typeof agit différemment en fonction de l'espace où il est utilisé :\nDans le Value space il va renvoyer un string caractérisant la valeur, par exemple \"object\" ou \"function\".\nDans le Type space il va renvoyer le type caractérisant la valeur.\ntypeof MaClasse (si utilisé dans le Type space) retourne le type de la classe elle-même, alors que MaClasse (si utilisé dans le Type space) représente le type d'une instance de cette classe.\nInstanceType permet de retrouver le type de l'instance à partir du type de la classe. Par exemple :\nInstanceType; // donne le type MaClasse\nOn peut accéder aux attributs d'un objet :\nSi c'est une valeur, avec objet[\"nom\"] ou objet.nom.\nSi c'est un type, avec seulement Type[\"nom\"].","item-9--prefer-type-declarations-to-type-assertions#Item 9 : Prefer Type Declarations to Type Assertions":"Il vaut mieux utiliser les type declarations plutôt que les type assertions.\nExemple :\ntype Person = { name: string };\n// Type declaration, à préférer\nconst alice: Person = { name: \"Alice\" };\n// Type assertion, déconseillé\nconst bob = { name: \"Bob\" } as Person;\nLa raison est que le type declaration va vérifier le type qu'on assigne, alors que le type assertion ne vérifie pas, et permet d'outrepasser TypeScript dans le cas où on en sait plus que lui sur le contexte d'un cas particulier.\nPour autant, même avec le type type assertion, on ne peut pas assigner n'importe quoi, il faut au minimum que la valeur qu'on assigne soit d'un sous-type de la valeur à laquelle on l'assigne.\nPour forcer un type complètement arbitraire, on peut passer par unknown ou any.\ndocument.body as unknown as Person;\nEn plus du as, on a aussi le ! placé en suffixe qui permet de faire une forme de type assertion, en indiquant qu'on est sûr que la valeur n'est pas null.\nconst el = document.getElementById(\"foo\")!;\nPour utiliser le type declaration dans la fonction passée à un map, on peut typer sa valeur de retour.\n[\"alice\", \"bob\"].map((name): Person => ({ name }));\nIci on demande à TypeScript d'inférer la valeur de name, et on indique que la valeur de retour devra être Person.","item-10--avoid-object-wrapper-types-string-number-boolean-symbol-bigint#Item 10 : Avoid Object Wrapper Types (String, Number, Boolean, Symbol, BigInt)":"Les types primitifs (string, number, boolean, symbol et bigint) ne possèdent pas d'attributs comme peuvent en posséder les objets.\nQuand on utilise un attribut connu sur l'un d'entre eux, JavaScript crée un un objet éphémère correspondant (respectivement String, Number, Boolean, Symbol et BigInt) pour le wrapper et fournir l'attribut en question.\n// l'attribut charAt vient de l'objet String\n\"blabla\".charAt(3);\nC'est pour ça que si on assigne une propriété à une valeur primitive, la propriété disparaît (avec l'objet associé créé pour l'occasion et détruit aussitôt).\nIl vaut mieux éviter d'instancier les objets correspondant aux types primitifs, ça n'apporte rien à part de la confusion.\nExemple :\nconst person = new String(\"Alice\");\nEn revanche, utiliser ces objets sans le new est tout à fait OK, ça nous donne une valeur primitive comme résultat.\nBoolean(3); // renvoie true\nIl vaut mieux éviter d'utiliser les objets correspondant aux types primitifs dans le Type space. Ça pose le problème que le type primitif est assignable au type objet wrapper, alors que l'inverse n'est pas vrai.\nExemple :\nfunction getPerson(person: string) {\n // [...]\n}\ngetPerson(new String(\"Alice\")); // Erreur de type","item-11--recognize-the-limits-of-excess-property-checking#Item 11 : Recognize the Limits of Excess Property Checking":"Bien que TypeScript ait un typage structurel, il existe un mécanisme particulier qui s'appelle excess property checking, et qui permet d'avoir un comportement strict et non pas structurel.\nCe mode s'active quand on passe une valeur littérale à une fonction, ou qu'on l'assigne à une variable.\nEt il est actif uniquement quand on est dans le cadre d'une type declaration, pas dans le cadre d'une type assertion.\nExemple :\ntype Person = {\n name: string;\n}\nconst alice: Person = {\n name: \"Alice\";\n age: 20; // Erreur, age n'existe pas sur Person\n}\nDans le cas où on veut que le type ait systématiquement un comportement structurel, même dans le cas de l'excess property checking, on peut l'indiquer :\ntype Person = {\n name: string;\n [other: string]: unknown;\n};\nIl existe un autre mécanisme similaire : il s'agit des weak types, c'est-à-dire des types objets qui n'ont que des attributs optionnels.\nCe mécanisme s'applique tout le temps, et non pas juste dans le cas d'assignation de valeur littérale.\nLa règle c'est qu'on doit assigner une valeur qui a au minimum un attribut en commun avec le weak type.\nExemple :\ntype Person = {\n name?: string;\n age?: number;\n};\nconst alice = { firstName: \"alice\" };\nconst alicePerson: Person = alice; // Erreur : aucun attribut en commun","item-12--apply-types-to-entire-function-expressions-when-possible#Item 12 : Apply Types to Entire Function Expressions When Possible":"On peut typer une fonction entière si elle est une function expression, c'est-à-dire si elle n'est pas une fonction déclarée classiquement, mais plutôt une valeur qu'on peut passer à une variable.\nTyper une fonction entière est utile notamment si :\nOn a plusieurs fonctions qui ont la même signature, et qu'on veut être plus concis.\ntype BinaryFn = (a: number, b: number) => number;\nconst add: BinaryFn = (a, b) => a + b;\nconst sub: BinaryFn = (a, b) => a - b;\nOn a une fonction qui doit avoir la même signature qu'une fonction existante. Dans ce cas on peut utiliser typeof.\nconst checkedFetch: typeof fetch = async (input, init) => {\n const response = await fetch(input, init);\n // [...]\n return response;\n};","item-13--know-the-differences-between-type-and-interface#Item 13 : Know the differences Between type and interface":"Selon l'auteur, la convention consistant à mettre un I à chaque interface en TypeScript est considéré aujourd'hui comme une mauvaise pratique (inutile, apporte peu de valeur etc.).\nUne interface peut étendre un type, et un type peut étendre une interface :\ninterface StateWithPop extends State {\npopulation: number;\n}\ntype StateWithPop = State & { population: number; };\nUne classe peut implémenter un type comme elle peut implémenter un interface.\nclass State implements TypeState {\n // [...]\n}\nDe manière générale, un type offre plus de possibilités qu'une interface. Par exemple l'utilisation d'unions.\nUn exemple notable est le declaration merging qui permet d'augmenter une interface sans changer son nom.\ninterface State {\n name: string;\n}\ninterface State {\n population: number;\n}\nconst wyoming: State = {\n name: \"Wyoming\",\n population: 500_000,\n};\nPour le choix entre type et interface, l'auteur conseille de se baser sur :\nLa consistance au sein de la codebase.\nLe fait qu'on ait besoin ou non que d'autres personnes puissent augmenter nos types.","item-14--use-type-operations-and-generics-to-avoid-repeating-yourself#Item 14 : Use Type Operations and Generics to Avoid Repeating Yourself":"Il existe de nombreuses techniques pour éviter la duplication de type :\n1 - Extraire la duplication dans un sous-type.\n2 - Dans le cas de deux fonctions qui ont la même signature, créer un type de fonction, et l'utiliser pour les typer sous forme de function expressions (cf. Item 12).\n3 - Dans le cas où on a un type objet qui reprend une partie des propriétés d'un autre type, et qu'on veut garder ce lien sans extraire un sous-type :\nPar exemple, on State, et TopNavState qu'on veut dépendre d'une partie de State :\ntype State = {\n userId: string;\n pageTitle: string;\n recentFiles: string[];\n}\ntype TopNavState = {\n userId: string;\n pageTitle: string;\n}```\nOn va pouvoir utiliser un mapped type :\ntype TopNavState = {\n [k in \"userId\" | \"pageTitle\"]: State[k];\n};\nOu encore l'équivalent avec Pick :\ntype TopNavState = Pick;\n4 - Dans le cas où on veut le même type objet qui existe mais avec tous les attributs optionnels :\nPar exemple pour le même type State, on peut utiliser un mapped type :\ntype OptionalState = {\n [k in keyof State]?: State[k];\n};\nOu encore l'équivalent avec Partial :\ntype OptionalState = Partial;\n5 - Si on veut récupérer la valeur de retour inférée d'une fonction dans un type à réutiliser ailleurs, on peut le faire avec ReturnType :\ntype UserInfo = ReturnType;","item-15--use-index-signatures-for-dynamic-data#Item 15 : Use Index Signatures for Dynamic Data":"Les index signatures doivent être utilisées seulement dans le cas où la donnée est dynamique et qu'on ne connaît pas les attributs d'un objet à la transpilation.\nExemple :\ntype State = {\n [property: string]: string;\n};\nSinon, il faut utiliser des types plus précis.\nTyper intégralement l'objet.\nAjouter undefined aux propriétés peut ajouter un peu de safety en obligeant à vérifier leur présence.\ntype State = {\n [property: string]: string | undefined;\n};\nUtiliser Record peut permettre d'être plus précis sur les noms de clés.\ntype State = Record<\"userId\" | \"pageTitle\", string>;\nNDLR : l'auteur n'en parle pas, mais souvent on va vouloir parser la donnée…","item-16--prefer-arrays-tuples-and-arraylike-to-number-index-signatures#Item 16 : Prefer Arrays, Tuples, and ArrayLike to number Index Signatures":"Les objets JavaScript sont représentés par des collections de clés / valeurs, avec les clés ne pouvant être que des strings (ou des symbols depuis ES6), et les valeurs n'importe quoi.\nDans le cas où on donne autre chose en clé, ce sera converti en string avec l'appel à toString(), y compris pour un number par exemple.\nLes arrays sont des objets aussi. On les indexe par des entiers, mais ils sont convertis automatiquement en strings par JavaScript.\nTypeScript type l'index des arrays comme des number pour éviter au maximum les erreurs.\nPour toutes ces raisons :\nIl faut éviter les for..in pour les arrays.\nIl faut de manière générale éviter les numbers en tant que clé d'objet, puisque ce sera converti de toute façon en string par JavaScript. A la place on peut soit :\nUtiliser string ou symbol.\nUtiliser un type array, par exemple : Array, MonType[].\nUtiliser un type tuple, par exemple : [number, number].\nOu encore utiliser ArrayLike qui permet de désigner seulement les caractéristiques basiques d'un array (pouvoir accéder aux attributs par un index numérique et l'attribut length), sans les autres attributs du prototype.","item-17--use-readonly-to-avoid-errors-associated-with-mutation#Item 17 : Use readonly to Avoid Errors Associated with Mutation":"readonly permet d'indiquer qu'une variable ou un paramètre ne pourra pas être modifié. L'auteur conseille de l'utiliser dès que possible.\nUne valeur readonly peut être passée à une valeur mutable, mais pas l'inverse.\nCa a l'avantage d'être “contaminant” : si une de nos fonctions appelle une autre fonction en lui donnant une valeur qu'on n'a pas le droit de toucher, il faudra que l'autre fonction prenne aussi un paramètre readonly.\nDans le cas où on appelle des librairies sur lesquelles on n'a pas la main, on pourra toujours faire des type assertions avec as.\nreadonly est par nature “shallow”, c'est à dire qu'il n'agit que sur un niveau.\nPar exemple :\nconst dates = readonly Date[];\ndates.push(new Date); // Error\ndates[0].setFullYear(2037); // OK\nIl n'y a pas de version récursive de readonly dans la librairie standard, mais on peut par exemple trouver DeepReadonly dans une librairie comme ts-essentials.","item-18--use-mapped-types-to-keep-values-in-sync#Item 18 : Use Mapped Types to Keep Values in Sync":"On peut obliger un objet à avoir les mêmes attributs qu'un autre type en utilisant un mapped type.\ntype ScatterProps = {\n x: number[];\n y: number[];\n};\nconst REQUIRES_UPDATE: {[k in keyof ScatterProps]: boolean} = {\n x: true;\n y: false;\n // Si on ajoute 'y', on aura une erreur\n}","3---type-inference#3 - Type Inference":"","item-19--avoid-cluttering-your-code-with-inferable-types#Item 19 : Avoid Cluttering Your Code with Inferable Types":"Il ne faut pas ajouter des types partout, mais plutôt en ajouter juste assez pour permettre à TypeScript de tout typer par inférence.\nÇa permet notamment de faciliter le refactoring.\nDu code TypeScript idéal va typer la signature des fonctions, mais pas les variables créés dans ces fonctions.\nDans certains cas quand on donne une lambda fonction il n'y a même pas besoin de typer ses paramètres qui seront inférés.\nParmi les cas où il faut typer quand même :\nDans certains cas, on voudra faire une type declaration pour éviter les erreurs dès la définition de l'objet, grâce à l'excess property checking.\nAnnoter le type de retour d'une fonction peut aussi être parfois utile :\nNe pas faire fuiter les erreurs vers les appelants.\nSpécifier le contrat d'une fonction avant même de l'implémenter.\nEtre cohérent dans certains cas où la fonction va par exemple prendre un type en paramètre et retourner le même type.\nIl existe une règle eslint qui s'appelle no-inferrable-types, et qui permet d'éviter les types qui pourraient être inférés..","item-20--use-different-variables-for-different-types#Item 20 : Use Different Variables for Different Types":"Il faut éviter de réutiliser une même variable pour porter une valeur d'un autre type.\nAu lieu de ça, on pourrait typer avec un type plus large, mais la meilleure solution est de créer deux variables.","item-21--understand-type-widening#Item 21 : Understand Type Widening":"Chaque variable doit avoir un seul type, et ce type est déterminé par typescript au mieux au moment de la déclaration : c'est le type widening.\nLe type widening peut être contrôlé par certaines techniques :\nDéclarer une variable comme const plutôt que let permet d'avoir un type widening moins important.\nOn peut utiliser une type declaration pour spécifier un type spécifique plus précis pour un objet ou un tableau.\nOn peut utiliser la type assertion as const pour obtenir le type le plus précis possible (sans type widening du tout).","item-22--understand-type-narrowing#Item 22 : Understand Type Narrowing":"TypeScript rend les types plus précis, notamment avec des type guards.\nÇa marche avec la condition de vérité (pour évacuer null et undefined).\nÇa marche avec une condition sur instanceof.\nÇa marche avec le check l'attribut : \"attr\" in object.\nÇa marche avec Array.isArray().\nIl faut faire attention avec les comportements qui seraient contre-intuitifs en JavaScript, TypeScript les suit aussi.\nPar exemple if(!x) { ... } pourrait mener à x ayant pour type string | number | null | undefined.\nUn autre moyen de rendre le type plus précis est l'utilisation d'objets avec tag\nswitch (object.type) {\n case \"download\":\n object; // de type Download\n break;\n case \"upload\":\n object; // de type Upload\n break;\n}\nOn peut aussi définir des custom type guards.\nfunction isInputElement(el: HTMLElement): el is HTMLInputElement {\n return \"value\" in el;\n}\nSi on veut que filter donne le bon type, on peut utiliser un custom type guard plutôt qu'une callback normale.\nfunction isDefined(x: T | undefined): x is T {\n return x !== undefined;\n}\nconst members = [\"Janet\", \"Michael\", undefined].filter(isDefined);","item-23--create-objects-all-at-once#Item 23 : Create Objects All at Once":"Il vaut mieux créer les objets d'un coup quand c'est possible.\nCréer un objet partiel assigne un type à la variable, et l'ajout de propriété plus tard devient plus compliqué.\nUne des techniques pour aider à créer un objet d'un coup est le spread operator ...\nOn peut construire un objet à partir de plusieurs autres :\nconst namedPoint = { ...pt, ...id };\nOn peut construire des variables intermédiaires avec un type partiel de notre objet final :\nconst pt0 = {};\nconst pt1 = { ...pt0, x: 3 };\nconst pt: Point = { ...pt1, y: 4 };\nDans le cas où on veut des propriétés conditionnelles, on peut utiliser un petit utilitaire :\nfunction addOptional(\n a: T,\n b: U | null\n): T & Partial {\n return { ...a, ...b };\n}\nconst president = addOptional(\n firstLast,\n hasMiddle ? { middle: \"S\" } : null\n);\npresident.middle; // string | undefined","item-24--be-consistent-in-your-use-of-aliases#Item 24 : Be Consistent in Your Use of Aliases":"Quand on crée une variable servant de référence à une autre valeur (aliasing), il faut s'assurer qu'on utilise les type guards sur cette valeur pour rester consistant avec la suite du code.\nconst { bbox } = polygon;\nif (bbox) {\n const { x, y } = bbox;\n}\nQuand on utilise un type guard sur un objet, et qu'on appelle une fonction l'objet qu'on a vérifié, cette fonction pourrait altérer l'objet, mais TypeScript fait le choix de ne pas invalider le type guard à chaque appel de fonction.","item-25--use-async-functions-instead-of-callbacks-for-asynchronous-code#Item 25 : Use async Functions Instead of Callbacks for Asynchronous Code":"Il vaut mieux utiliser les promesses avec async / await que les promesses à l'ancienne ou même les callbacks asynchrones.\nLa syntaxe est plus concise et infère mieux les types.\nCa force une fonction à être soit synchrone soit asynchrone, mais pas l'une ou l'autre conditionnellement. De cette manière on sait comment l'appeler.\nOn peut utiliser Promise.race() qui termine dès qu'une des promesses termine, pour mettre en place un timeout :\nfunction timeout(millis: number): Promise {\n return new Promise((resolve, reject) => {\n setTimeout(() => reject(\"timeout\"), millis);\n });\n}\nawait Promise.race([fetch(url), timeout(ms)]);","item-26--understand-how-context-is-used-in-type-inference#Item 26 : Understand How Context Is Used in Type Inference":"En général TypeScript va inférer le type d'une valeur à sa création. Si on l'utilise plus tard dans un autre contexte (par exemple après qu'on l'ait placée dans une variable intermédiaire), l'inférence peut être mauvaise vis-à-vis de l'utilisation finale.\nLe contexte est conservé par exemple pour :\nLes types string littéraux, qui sinon vont plutôt être inférés en string général dans le cas d'une déclaration dans une variable let.\nLes types tuple, qui sinon vont plutôt être inférés en tableau dans le cas d'une déclaration dans une variable let.\nLes objets contenant des strings littéraux ou des tuples.\nLes callbacks dont on n'a pas besoin de fournir le type des paramètres quand ils sont directement fournis à la fonction.\nPour corriger le type en cas de perte de contexte, on va en général :\n1 - Utiliser une type declaration pour contraindre la valeur au type de notre choix.\n2 - Utiliser la const assertion as const pour contraindre la valeur au plus précis.\nAttention par contre : ça va transformer la valeur en deeply constant. Une solution peut être de propager ce comportement dans les endroits où on passe la valeur.","item-27--use-functional-constructs-and-libraries-to-help-types-flow#Item 27 : Use Functional Constructs and Libraries to Help Types Flow":"Il vaut mieux utiliser les fonctions built-in et les librairies externes (par exemple Lodash, Ramda etc.) plutôt que de coder les choses à la main. Ce sera plus lisible et mieux typé.\nJavaScript n'a pas vraiment de librairie standard, les librairies externes jouent en grande partie ce rôle, et TypeScript a été construit pour les supporter.\n.flat() sur un tableau multidimensionnel permet de le transformer en tableau à une dimension.\nLodash permet de chaîner des appels à ses fonctions utilitaires en donnant la valeur à la fonction _, puis permet de réobtenir la valeur finale avec .value().\nOn aura _(v).a().b().c().value() :\n_(vallPlayers)\n .groupBy((player) => player.team)\n .mapValues((players) => _.maxBy(players, (p) => p.salary))\n .values();","4---type-design#4 - Type Design":"","item-28--prefer-types-that-always-represent-valid-states#Item 28 : Prefer Types That Always Represent Valid States":"Il vaut mieux écrire un type plus long ou plus complexe, mais qui permet d'interdire les états invalides.\nOn peut par exemple utiliser les tagged unions (ou discriminated unions), qui sont une union d'objets ayant un attribut commun qui permet de savoir dans quel cas on est, et des attributs spécifiques à chaque cas possible.\ninterface RequestError {\n state: \"error\";\n error: string;\n}\ninterface RequestSuccess {\n state: \"ok\";\n pageText: string;\n}\ntype RequestState = RequestError | RequestSuccess;","item-29--be-liberal-in-what-you-accept-and-strict-in-what-you-produce#Item 29 : Be Liberal in What You Accept and Strict in What You Produce":"Il faut être strict avec les types qu'on retourne dans une fonction (éviter les attributs optionnels par exemple), et on peut au contraire être lâche avec les types qu'on accepte en paramètre (par exemple une union de types).\nUne utilisation classique est de définir un type canonique pour l'output, et un type dérivé de celui-là et plus large pour l'input.\nC'est un peu la distinction entre Array et ArrayLike.\nExemple :\ninterface Camera {\n zoom: number;\n pitch: number;\n}\nsetCamera(camera: Partial): Camera {\n // ...\n}","item-30--dont-repeat-type-information-in-documentation#Item 30 : Don't Repeat Type Information in Documentation":"Il faut éviter les informations de type dans les commentaires, docstrings etc. les types sont là pour ça.\nOn peut envisager de mettre les unités dans les noms de variable, par exemple timeMs ou temperatureC.","item-31--push-null-values-to-the-perimeter-of-your-types#Item 31 : Push Null Values to the Perimeter of Your Types":"Il faut éviter les types objets dont une partie des attributs peut être null ou undefined : soit tout est défini, soit le tout est null.\nIl faut éviter les relations de non-nullité implicite entre deux variables, si elles sont liées on les mets au sein d'un même objet où elles ne pourront être que définies toutes les deux, ou l'objet tout entier non défini.\ntype Bounds: [number, number] | null;\nconst bounds = [10, 20];\nSi possible, il vaut mieux créer une classe avec des membres non nuls, au besoin avec une méthode statique asynchrone sur la classe, qui va chercher de la donnée et renvoie ensuite une instance de la classe avec cette donnée.\nclass User {\n user: UserInfo;\n constructor(user: UserInfo) {\n this.user = user;\n }\n static async init(userId: string): Promise {\n const [user] = await fetchUser(userId);\n return new User(user);\n }\n}\nAttention aux méthodes asynchrones : elles sont pratiques pour aller chercher de la donnée asynchrone à l'extérieur, mais le sont moins si on les utilise pour attendre qu'une valeur null finisse par être définie.","item-32--prefer-unions-of-interfaces-to-interfaces-of-unions#Item 32 : Prefer Unions of Interfaces to Interfaces of Unions":"Quand on a un type d'objet avec des attributs étant des unions, il faut se demander si on ne peut pas remplacer ça par une union de types d'objets.\nExemple :\n// Il vaut mieux\ninterface FillLayer {\n layout: FillLayout;\n paint: FillPaint;\n}\ninterface LineLayer {\n layout: LineLayout;\n paint: LinePaint;\n}\n// plutôt que\ninterface Layer {\n layout: FillLayout | LineLayout;\n paint: FillPaint | LinePaint;\n}\nCa permet notamment d'interdire les valeurs incohérentes, faisant suite à l'Item 28.","item-33--prefer-more-precise-alternatives-to-string-types#Item 33 : Prefer More Precise Alternatives to String Types":"Le fait d'avoir de nombreux attributs typés string dans un objet est un code smell.\nLe fait d'indiquer une précision de format pour un string en commentaire en est un autre.\nDans la mesure du possible, string peut être remplacé par :\nUne union de string literals.\nUn value object qui porte le string.\nUn type générique qui met au moins une contrainte sur le string.\nDans le cas particulier où on veut la valeur des attributs d'un objet, ne pas oublier d'utiliser keyof NotreObjet.","item-34--prefer-incomplete-types-to-inaccurate-types#Item 34 : Prefer Incomplete Types to Inaccurate Types":"Il vaut mieux un type moins précis qu'un type précis mais incorrect.\nIl faut aussi prendre en compte la developer experience :\nUn type trop complexe risque d'être plus prompt aux erreurs quand il sera modifié.\nUn type produisant une erreur incompréhensible ne fera pas gagner de temps.","item-35--generate-types-from-apis-and-specs-not-data#Item 35 : Generate Types from APIs and Specs, Not Data":"Il vaut mieux obtenir des types officiels (de librairie par exemple), ou générer les types à partir de spécifications, plutôt que de les générer à partir d'exemples de données.\nPar exemple, un schéma GraphQL peut facilement servir de spécification pour générer des types pour les entrées et sorties d'une API.","item-36--name-types-using-the-language-of-your-problem-domain#Item 36 : Name Types Using the Language of Your Problem Domain":"Il faut utiliser le vocabulaire du domaine dans le code.\nSi deux mots désignent la même chose, on n'en garde qu'un seul dans le code.\nDans le cas où le domaine en question a des spécifications ou normes, ne pas hésiter à les adopter, y compris dans le typage, plutôt que de rester vague ou d'inventer sa propre spécification.\nExemple : pour indiquer le climat de vie d'un animal, il existe une classification appelée Köppen Climage. On peut du coup créer une union des string literals de climats possibles plutôt qu'un string.\nNDLR : le livre ne fait pas le lien explicite, mais il s'agit ici de l'ubiquitous language du DDD.","item-37--consider-brands-for-nominal-typing#Item 37 : Consider “Brands” for Nominal Typing":"Si on veut forcer un nominal type quelque part, c'est-à-dire empêcher les valeurs qui conviennent structurellement mais ont des choses en plus, on peut ajouter un attribut caché qu'on appellera _brand.\ninterface Vector2D {\n _brand: \"2d\";\n x: number;\n y: number;\n}\nCette technique du brand peut aussi servir à simuler le comportement d'un Value Object, c'est-à-dire garantir qu'une valeur aura certaines caractéristiques vérifiées au runtime.\nOn peut par exemple demander un type string & {_brand: 'quelque chose'}, pour obliger le passage par un type guard custom.\ntype AbsolutePath = string & { _brand: \"abs\" };\nfunction listAbsolutePath(path: AbsolutePath) {\n //...\n}\nfunction isAbsolutePath(path: string): path is AbsolutePath {\n return path.startsWith(\"/\");\n}","5---working-with-any#5 - Working with any":"","item-38--use-the-narrowest-possible-scope-for-any-types#Item 38 : Use the Narrowest Possible Scope for any Types":"Il faut restreindre l'utilisation de any au plus petit scope possible.\nPréférer la type assertion as any qui n'agira que sur une instruction, plutôt qu'une type declaration de any qui va propager le any partout où la variable sera utilisée.\n// On préfère la type assertion\nconst x = expressionReturningFoo();\nprocessBar(x as any);\n// Plutôt que la type declaration\nconst x: any = expressionReturningFoo();\nprocessBar(x);\nÉviter à tout prix de retourner any dans une fonction. Au besoin on peut typer son retour pour l'éviter.\nSi un objet ne match pas un type particulier à cause d'un seul attribut, mais qu'on sait qu'il devrait, il vaut mieux faire une type assertion sur l'attribut seulement, et pas sur l'objet entier.\nconst config: Config = {\n a: 1,\n b: {\n key: value as any,\n },\n};","item-39--prefer-more-precise-variants-of-any-to-plain-any#Item 39 : Prefer More Precise Variants of any to Plain any":"Dans un souci d'utiliser le type le plus restrictif possible, dans le cas où on doit utiliser any, il faut voir si on ne peut pas utiliser un type un peu plus précis à la place.\nExemples :\nany[] si on sait que le type sera un tableau.\n{[id: string]: any} si on sait que le type sera un objet.\n() => any si on sait que le type sera une fonction.","item-40--hide-unsafe-type-assertions-in-well-typed-functions#Item 40 : Hide Unsafe Type Assertions in Well-Typed Functions":"Dans le cas où ça ne vaut pas le coup de faire une version type safe d'un bout de code, et où on se contente d'une type assertion, alors il faut préférer la contenir à l'intérieur d'une fonction qui aura ses paramètres et valeurs de retour typés.\nDe cette manière la type assertion ne sera pas faite et refaite partout dans le code, mais seulement dans un endroit précis, idéalement à côté du code qui prouve qu'elle a du sens.","item-41--understand-evolving-any#Item 41 : Understand Evolving any":"Alors que les types sont en général évalués à la création des valeurs, et ne peuvent être qu'affinés plus tard (avec des type guards par exemple), dans le cas d'une valeur any inférée, le type va pouvoir évoluer dans le code de la fonction en fonction des écritures dans la variable.\nÇa peut être par exemple une valeur initiale de tableau évaluée à any[], qui finit par évoluer vers number[] à la sortie de la fonction, parce qu'on aura poussé des nombres dans le tableau.\nfunction range(start, limit) {\n const out = [];\n for (let i = start; i < limit; i++) {\n out.push(i);\n }\n return out;\n}\nÇa peut être aussi une variable qu'on crée comme let ou var, sans assignation initiale, ou en assignant null. Son type va alors évoluer au gré des assignations.\nlet val; // any\nif (Math.random() < 0.5) {\n val = /hello/;\n val; // RegExp\n} else {\n val = 12;\n val; // number\n}\nval; // number | RegExp\nCe comportement se produit seulement si any est inféré automatiquement, que noImplicitAny est activé.","item-42--use-unknown-instead-of-any-for-values-with-an-unknown-type#Item 42 : Use unknown Instead of any for Values with an Unknown Type":"Quand on ne connaît pas le type d'une valeur (par exemple si elle vient du réseau à l'exécution), il faut la typer unknown.\nCa va forcer à typer la valeur pour y accéder :\nSoit avec une type assertion.\nSoit avec une vérification au runtime : par exemple instanceof ou un type guard custom.\nA propos des caractéristiques des types :\nany peut être assigné à tout type, et toute valeur peut lui être assignée.\nunknown ne peut être assigné à aucun type sauf any et lui-même, mais toute valeur peut lui être assignée.\nnever peut être assigné à tout type, mais aucune valeur ne peut lui être assignée.","item-43--prefer-type-safe-approaches-to-monkey-patching#Item 43 : Prefer Type-Safe Approaches to Monkey Patching":"En JavaScript on peut faire du monkey-patching, c'est-à-dire ajouter des attributs à un objet à la volée. On le fait souvent côté client pour ajouter des choses à document, ou à des balises HTML.\nC'est une mauvaise pratique parce ça revient à ajouter des variables à un niveau global, accessibles depuis trop d'endroits.\nSi on doit quand même faire ce monkey-patching parce qu'on n'a pas le choix, plutôt que de typer la balise avec any à chaque fois, on peut :\n1 - Utiliser l'augmentation d'interface, et augmenter par exemple Document.\ndeclare global {\n interface Document {\n monkey: string;\n }\n}\ndocument.monkey = \"Tamarin\";\n2 - Créer une interface dérivée, et faire une type assertion vers celle-là (plutôt que vers any) dès que nécessaire.\ninterface MonkeyDocument extends Document {\n monkey: string;\n}\n(document as MonkeyDocument).monkey = \"Macaque\";\nLa 2ème solution nécessite une type assertion, mais au moins l'attribut n'est pas disponible partout tout le temps, mais seulement dans le contexte où on sait qu'il est là.","item-44--track-your-type-coverage-to-prevent-regressions-in-type-safety#Item 44 : Track Your Type Coverage to Prevent Regressions in Type Safety":"Il existe des librairies qui font du type coverage pour tracker les any (explicites ou implicites) dans le code.\n_ Même avec noImplicitAny, on peut encore avoir des any explicites, et des any venant de librairies.\n_ Ca peut valloir la peine passer en revue les any, explicites ou implicites, pour voir si ils ont toujours du sens. Par exemple avec la librairie type-coverage :\nnpx type-coverage --detail","6---types-declarations-and-types#6 - Types Declarations and @types":"","item-45--put-typescript-and-types-in-devdependencies#Item 45 : Put TypeScript and @types in devDependencies":"Les dépendances liées à TypeScript doivent aller dans les devDependencies du package.json.\nCa vaut pour la dépendance \"typescript\" elle-même, et aussi pour toutes les dépendances de type \"@types/*\".","item-46--understand-the-three-versions-involved-in-type-declarations#Item 46 : Understand the Three Versions Involved in Type Declarations":"En général _la version du package et la version du type \"@types/_\" doivent correspondre vis-à-vis de la composante majeure et mineure. Le patch peut être différent parce que le package de types gère ses propres bugs.\nPar exemple \"react@16.8.6\" et \"@types/react@16.8.19\" est OK.\nIl est possible que la version de TypeScript nécessaire pour une de nos dépendances \"@types/*\" et notre code soit incompatible. Dans ce cas il faut soit downgrade la lib de type, soit notre version de TypeScript, soit créer des types nous-mêmes pour remplacer ceux de la lib de types.\nIl est possible qu'un de nos package \"@types/*\" dépende d'un autre de ces packages de type, mais dans une version incompatible avec celle dont on a nous-même besoin. NPM va essayer de les installer tous les deux, mais pour les types ça marche rarement.\nOn peut jeter un œil aux packages dupliqués avec npm ls @types/mon-package.\nL'auteur conseille aux développeurs de librairie JavaScript de créer un package de \"@types/*\" séparé et disponible via DefinitelyTyped, plutôt qu'intégré à la librairie qui a besoin d'être typée.\nCes librairies de type sont exécutées contre chaque nouvelle version de TypeScript, et les erreurs sont reportées aux mainteneurs. La communauté DefinitelyTyped peut aussi nous aider si on utilise ça.\nCes librairies peuvent être disponibles pour plusieurs versions de TypeScript, et globalement régler les problèmes de type lié à des dépendances sera plus facile.","item-47--export-all-types-that-appear-in-public-apis#Item 47 : Export All Types That Appear in Public APIs":"Conseil aux développeurs de librairie : si on exporte du code typé, autant exporter l'ensemble des types aussi. De toute façon, de l'autre côté il sera possible de récupérer les types.\nPar exemple pour récupérer le type des paramètres ou de retour d'une fonction :\n// Côté librairie\nexport function getGift(name: SecretName): SecretSanta {\n //...\n}\n// Côté utilisateur\ntype SecretSanta = ReturnType;\ntype SecretName = Parameters[0];","item-48--use-tsdoc-for-api-comments#Item 48 : Use TSDoc for API Comments":"Quand on veut décrire une fonction, classe, interface, type etc. avec du commentaire, il vaut mieux le faire dans un style “JSDoc” /** */. La raison est que les éditeurs vont le traiter comme documentation.\nEt on peut aussi penser à utiliser les @param, @returns etc. dans la JSDoc pour la même raison.\nIl est tout à fait conventionnel d'utiliser du markdown dans le JSDoc.","item-49--provide-a-type-for-this-in-callbacks#Item 49 : Provide a Type for this in Callbacks":"En JavaScript, la valeur de this dépend de la manière dont la fonction dans laquelle il est utilisé est appelée.\nSi on donne une méthode de classe en tant que callback, et qu'on l'appelle telle quelle, le this à l'intérieur d'elle sera celui de l'environnement appelant.\nOn peut obliger à utiliser le this de la classe avec call() :\nmaMethode.call();\nUne autre méthode classique pour obliger à ce que la méthode ne soit exécutable que sur sa classe est de la bind dans le constructeur :\nclass MaClasse {\n constructor() {\n this.onClick = this.onClick.bind(this);\n }\n onClick() {\n //...\n }\n}\nUne arrow function n'a pas de this à elle, mais utilise automatiquement le this du parent de là où elle a été définie.\nOn peut créer un type de fonction qui indique le type du this contextuel avec lequel la fonction va être appelée. C'est en particulier utile quand la fonction utilise this.\nPour créer ce type il faut indiquer un paramètre this à la fonction en question :\nfunction addKeyListener(\n el: HTMLElement,\n fn: (this: HTMLElement, e: KeyboardEvent) => void\n) {\n fn.call(el, e);\n}","item-50--prefer-conditional-types-to-overloaded-declarations#Item 50 : Prefer Conditional Types to Overloaded Declarations":"Il vaut mieux utiliser des types conditionnels plutôt que des surcharges de types pour typer une fonction.\nC'est en particulier utile dans le cas d'unions de types : les surcharges de type vont être évaluées une par une sur le type qu'il soit une union ou non, alors que le type conditionnel sera évalué contre chaque élément de l'union de type indépendamment, pour former une union finale.\nExemple avec une fonction qui double un string ou un nombre :\n// Fonction initiale non typée.\nfunction double(x) {\n return x + x;\n}\n// Avec la surcharge on ne peut pas donner une\n// valeur string | number à la fonction : aucune\n// des surcharges ne le supporte.\nfunction double(x: number): number;\nfunction double(x: string): string;\n// Avec le type conditionnel on obtient string | number\n// en sortie (d'abord c'est string qui est évalué vis-à-vis\n// de la condition, puis number pour former l'union finale).\nfunction double(\n x: T\n): T extends string ? string : number;","item-51--mirror-types-to-server-dependencies#Item 51 : Mirror Types to Server Dependencies":"Conseil pour les développeurs de librairie : si on a une librairie utilisable côté client et serveur, et qu'on a besoin d'un type qui n'est disponible que côté serveur, alors il vaut mieux créer une version réduite de ce type et l'utiliser, pour éviter de forcer les utilisateur de la lib côté client d'installer les types de Node.js.\nPar exemple, si on a besoin du type Buffer de Node.js uniquement dans le cas où le code est appelé côté serveur, et qu'on fallback sur string sinon, on peut créer une interface CustomBuffer avec juste les méthodes qu'il nous faut au lieu du type Buffer.\n// Faire ça\ninterface CSVBuffer {\n toString(encoding: string): string;\n}\nfunction parseCSV(contents: string | CSVBuffer) {\n if (typeof contents === \"object\") {\n //...\n }\n}\n// Plutôt que ça\nfunction parseCSV(contents: string | Buffer) {\n if (typeof contents === \"object\") {\n //...\n }\n}","item-52--be-aware-of-the-pitfalls-of-testing-types#Item 52 : Be Aware of the Pitfalls of Testing Types":"Il est possible de tester les types TypeScript complexes avec des types, mais cette technique présente de nombreux problèmes. Il vaut mieux utiliser un outil externe à la place, par exemple dtslint.\ndtslint lit les commentaires pour faire des vérifications, par exemple :\n// $ExpectType string","7---writing-and-running-your-code#7 - Writing and Running Your Code":"","item-53--prefer-ecmascript-features-to-typescript-features#Item 53 : Prefer ECMAScript Features to TypeScript Features":"Historiquement, TypeScript avait créé des fonctionnalités côté runtime pour palier au manque de JavaScript, mais comme ces fonctionnalités se sont ajoutées à JavaScript petit à petit d'une manière incompatible, l'équipe derrière TypeScript a choisi de ne se concentrer plus que sur le typage, et de laisser le runtime au TC39.\nIl existe des reliquats runtime ajoutés par TypeScript, et que l'auteur déconseille d'utiliser :\nLes enums.\nIls existent en plusieurs variantes :\nLes enums basés sur les nombres ne procurent pas assez de type safety puisqu'on peut assigner n'importe quel nombre à la place.\nLes enums basés sur les strings sont type safe, mais ont un comportement incohérent par rapport au reste du système de type : ils n'utilisent pas le structural typing. On ne peut pas passer un string à la place de l'élément d'enum.\nL'auteur conseille d'utiliser les unions de strings à la place.\nLes parameter properties sont la manière compacte de déclarer des variables membres et de leur assigner une valeur en paramètre du constructeur.\nL'auteur reconnaît qu'il y a un désaccord dans la communauté à leur sujet, mais il les déconseille personnellement parce qu'il trouve qu'ils ajoutent de la confusion en mélangeant les paramètres déclarés ou non, et ne sont pas en phase avec le reste des patterns de TypeScript.\nLes namespaces et les triple-slash imports.\nExemple :\nnamespace foo {\n function bar() {}\n}\n/// \nfoo.bar();\nDepuis que ES6 a ajouté les modules, ils ne sont plus utiles que pour les types. Il ne faut pas les utiliser pour autre chose.","item-54--know-how-to-iterate-over-objects#Item 54 : Know How to Iterate Over Objects":"L'auteur propose deux manière d'itérer sur les attributs d'un objet :\nfor in avec keyof T permet d'itérer sur un objet dont on pense connaître raisonnablement le type d'attributs.\nOn va déclarer une variable avant la boucle, et faire une type declaration dessus avec le type de la clé de l'objet, par exemple :\nlet key: keyof typeof myObject;\nfor (key in myObject) {\n // ...\nIl faut que cet objet soit bien connu parce qu'il pourrait très bien avoir d'autres attributs non exprimés par son type (du fait du structural typing), et donc y compris des attributs complètement inconnus et non prévus, qui pourraient causer une erreur au runtime.\nObject.entries permet d'itérer sur un objet inconnu, mais le typage sera moins précis : on va obtenir string pour la clé, et any pour la valeur.\nExemple :\nfor (const [k, v] of Object.entries(myObject)) {\n // ...\nAu moins on a bien le any qui marque que la valeur peut être n'importe quoi, y compris des choses non prévus par le type de l'objet.","item-55--understand-the-dom-hierarchy#Item 55 : Understand the DOM hierarchy":"Le DOM côté client a une hiérarchie d'objets pour exprimer son contenu :\nVoici la hiérarchie :\nEventTarget est le plus high level, il peut représenter n'importe quel élément émettant des events, y compris window, XMLHttpRequest.\nNode est un peu plus précis, et peut représenter par exemple document, Text, Comment.\nElement est plus précis, et peut représenter HTMLElement, SVGElement.\nHTMLElement est plus précis, et peut représenter des balises comme , .\nHTMLButtonElement est spécifique à la balise
Reading notes
Continuous Discovery Habits

Continuous Discovery Habits: Discover Products that Create Customer Value and Business Value

+
Reading notes
Continuous Discovery Habits

Continuous Discovery Habits: Discover Products that Create Customer Value and Business Value

Part I - What is continuous discovery

1 - The what and why of continuous discovery

-

Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/books/designing-cloud-data-platforms/index.html b/books/designing-cloud-data-platforms/index.html index 2b90aceb..92e67804 100644 --- a/books/designing-cloud-data-platforms/index.html +++ b/books/designing-cloud-data-platforms/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Reading notes
Designing Cloud Data Platforms

Designing Cloud Data Platforms

+

Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/books/designing-data-intensive-applications/index.html b/books/designing-data-intensive-applications/index.html index b7979843..03eba7af 100644 --- a/books/designing-data-intensive-applications/index.html +++ b/books/designing-data-intensive-applications/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Reading notes
Designing Data-Intensive Applications

Designing Data-Intensive Applications

+

Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/books/effective-kafka/index.html b/books/effective-kafka/index.html index ff878506..f5a50dde 100644 --- a/books/effective-kafka/index.html +++ b/books/effective-kafka/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Reading notes
Effective Kafka

Effective Kafka

+

Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/books/effective-typescript/index.html b/books/effective-typescript/index.html index be96f417..f9e6d528 100644 --- a/books/effective-typescript/index.html +++ b/books/effective-typescript/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Reading notes
Effective TypeScript

Effective TypeScript

+
Reading notes
Effective TypeScript

Effective TypeScript

1 - Getting to Know TypeScript

Item 1 : Understand the Relationship Between TypeScript and JavaScript

    @@ -1264,4 +1264,4 @@

    On peut laisser peu de temps à l'équipe pour s'habiter à TypeScript avant d'aller vers des règles plus strictes comme "strict": true et "strictNullChecks". -


Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/books/get-your-hands-dirty-on-clean-architecture/index.html b/books/get-your-hands-dirty-on-clean-architecture/index.html index 8331bf8b..1258698d 100644 --- a/books/get-your-hands-dirty-on-clean-architecture/index.html +++ b/books/get-your-hands-dirty-on-clean-architecture/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Reading notes
Get Your Hands Dirty on Clean Architecture

Get Your Hands Dirty on Clean Architecture

+

Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/books/learning-domain-driven-design/index.html b/books/learning-domain-driven-design/index.html index 43014577..68e0637f 100644 --- a/books/learning-domain-driven-design/index.html +++ b/books/learning-domain-driven-design/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Reading notes
Learning Domain-Driven Design

Learning Domain Driven Design: Aligning Software Architecture and Business Strategy

+
Reading notes
Learning Domain-Driven Design

Learning Domain Driven Design: Aligning Software Architecture and Business Strategy

Introduction

  • Le problème principal qui met en échec la plupart des projets de dev c'est la communication. Et c'est le cœur de ce qu'est le DDD.
  • @@ -1707,4 +1707,4 @@

    A propos des bounded contexts, comme évoqué précédemment, le mieux est de les choisir larges, puis de les découper quand le besoin se fait sentir, et que la connaissance du domaine augmente.
  • Finalement la startup a été profitable assez vite, et a fini par être rachetée par un de leur gros client. Pendant ces années, ils ont été en mode startup : changer les priorités et requirements rapidement, des timeframes agressives, et une petite équipe R&D. Pour Vlad le DDD a rempli ses promesses.
  • -


Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/books/learning-to-scale/index.html b/books/learning-to-scale/index.html index a856742f..f48b9c7a 100644 --- a/books/learning-to-scale/index.html +++ b/books/learning-to-scale/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Reading notes
Learning to Scale

Learning to Scale

+

Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/books/monolith-to-microservices/index.html b/books/monolith-to-microservices/index.html index 5f506afc..10110cbb 100644 --- a/books/monolith-to-microservices/index.html +++ b/books/monolith-to-microservices/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Reading notes
Monolith to Microservices

Monolith to Microservices: : Evolutionary Patterns to Transform Your Monolith

+
Reading notes
Monolith to Microservices

Monolith to Microservices: : Evolutionary Patterns to Transform Your Monolith

1 - Just Enough Microservices

  • Les microservices sont un type particulier de service-oriented architecture (SOA). @@ -1422,4 +1422,4 @@

    La solution peut être d'avoir une forme ou une autre de registry alimentée par les équipes, ou par un outil automatique qui crawl les repositories.

-

Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/books/outcomes-over-output/index.html b/books/outcomes-over-output/index.html index ffa337cb..7e4e1c7e 100644 --- a/books/outcomes-over-output/index.html +++ b/books/outcomes-over-output/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Reading notes
Outcomes Over Output

Outcomes Over Output

+

Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/books/refactoring/index.html b/books/refactoring/index.html index 49f28888..df0a5c93 100644 --- a/books/refactoring/index.html +++ b/books/refactoring/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Reading notes
Refactoring: Improving the Design of Existing Code

Refactoring: Improving the Design of Existing Code

+
Reading notes
Refactoring: Improving the Design of Existing Code

Refactoring: Improving the Design of Existing Code

1 - Refactoring : exemple introductif

  • @@ -5756,4 +5756,4 @@

    L'auteur conseille quand même, comme dans la technique précédente, de partir sur de l'héritage par défaut, et de remplacer si besoin par la délégation avec ce refactoring.

-

Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/books/reinventing-organizations/index.html b/books/reinventing-organizations/index.html index b0f25617..2b7f6ba7 100644 --- a/books/reinventing-organizations/index.html +++ b/books/reinventing-organizations/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Reading notes
Reinventing Organizations

Reinventing Organizations

+
Reading notes
Reinventing Organizations

Reinventing Organizations

Introduction

  • Il est possible que nous ne voyions pas certaines choses évidentes par aveuglement idéologique. @@ -1523,4 +1523,4 @@

    Le modèle des entreprises Opales ne demande qu'à être généralisé et poussé plus loin. C'est à nous de construire le futur.

  • -

Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/books/team-topologies/index.html b/books/team-topologies/index.html index 5894b9fc..ef65640f 100644 --- a/books/team-topologies/index.html +++ b/books/team-topologies/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Reading notes
Team Topologies

Team Topologies

+

Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/books/the-design-of-web-apis/index.html b/books/the-design-of-web-apis/index.html index a66df995..b720abfd 100644 --- a/books/the-design-of-web-apis/index.html +++ b/books/the-design-of-web-apis/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Reading notes
The Design of Web APIs

The Design of Web APIs

+

Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/books/the-five-dysfunctions-of-a-team/index.html b/books/the-five-dysfunctions-of-a-team/index.html index 576f3429..678a76c0 100644 --- a/books/the-five-dysfunctions-of-a-team/index.html +++ b/books/the-five-dysfunctions-of-a-team/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Reading notes
The Five Dysfunctions of a Team

The Five Dysfunctions of a Team

+

Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/books/turn-the-ship-around/index.html b/books/turn-the-ship-around/index.html index 31d01b2c..abcba1c7 100644 --- a/books/turn-the-ship-around/index.html +++ b/books/turn-the-ship-around/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Reading notes
Turn the Ship Around!

Turn the Ship Around

+
Reading notes
Turn the Ship Around!

Turn the Ship Around

Introduction

  • La motivation des salariés est au plus bas aux Etats Unis, et la plupart des programmes d’empowerment ne donnent rien.
  • @@ -761,4 +761,42 @@

    Comment faire pour que ces erreurs ne soient pas propagées mais plutôt arrêtées, d’où qu’elles viennent ?

-

Made by Roman Mkrtchian
\ No newline at end of file + +

27 - Homecoming

+
    +
  • Au retour de la mission, l’auteur est fier de faire le constat de nombreux succès : +
      +
    • Ils ont passé avec succès l’ensemble des exercices et inspections.
    • +
    • Ils ont maintenu et développé un fonctionnement leader-leader. +
        +
      • Stephen Covey est même venu sur le sous-marin et a félicité l’équipage pour l’empowerment.
      • +
      +
    • +
    • Ils ont permis à un grand nombre de membres de l’équipage d’avoir des promotions, et ont eu un taux de réenrolements très élevé.
    • +
    +
  • +
  • L’une des pratiques majeures qui a permis de minimiser les erreurs a été l’utilisation de l’action délibérée de la part de l’équipage.
  • +
  • Pour adopter le modèle leader-leader, l’auteur conseille : +
      +
    • 1 - Il faut trouver où l’excellence est créée dans notre organisation. En général il s’agit de l’interface avec le monde physique, et de l’interface avec les clients.
    • +
    • 2 - Trouver les décisions que les personnes à l’interface doivent prendre pour réaliser l’excellence technique.
    • +
    • 3 - Trouver ce qu’il faudrait pour que ces employés puissent prendre ces décisions. +
        +
      • Il leur faut en général : +
          +
        • De la connaissance technique.
        • +
        • Une bonne compréhension des objectifs de l’entreprise.
        • +
        • De l’autorité pour prendre les décisions.
        • +
        • De la responsabilité vis-à-vis des conséquences de ces décisions.
        • +
        +
      • +
      +
    • +
    +
  • +
  • Questions à se poser : +
      +
    • Est-ce qu’on est prêt à entamer la transition leader-leader pour notre organisation ?
    • +
    +
  • +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/books/unit-testing/index.html b/books/unit-testing/index.html index accf63a8..9b49b18b 100644 --- a/books/unit-testing/index.html +++ b/books/unit-testing/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Reading notes
Unit Testing: Principles, Practices, and Patterns

Unit Testing: Principles, Practices, and Patterns

+

Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file diff --git a/index.html b/index.html index 86cf9777..5fe67793 100644 --- a/index.html +++ b/index.html @@ -11,7 +11,7 @@ --nextra-primary-hue: 204deg; --nextra-primary-saturation: 100%; } -
Introduction

Reading notes

+

Made by Roman Mkrtchian
\ No newline at end of file +

Made by Roman Mkrtchian
\ No newline at end of file