Home
Leveraging Leadership, Technology, and Product Management to Build Great Products
Publié le 30 mars 2024Leadership is about managing uncertainty, bringing order to chaos, providing hope for a better future and progressing toward that future
– Napoleon Bonaparte
L’architecture peut être présentée en trois couches :
L’objectif d’une architecture logicielle consiste à construire des systèmes qui rencontrent la qualité attendue par les standards et qui fournissent le plus haut retour sur investissement à long terme. Les incertitudes suivantes compliquent une architecture :
Les solutions consistent à :
L’architecture est une affaire de compromis, en fonction d’un contexte actuel (et temporel) et d’un ensemble d’informations dont nous disposons. D’autre part, l’expérience montre que le délai de mise à disposition sont souvent non-négotiables ; les fonctionnalités à ajouter à chaque nouvelle version (ou itération) sont celles qui apportent la plus grande valeur au plus grand nombre d’utilisateurs (et avec le moins d’effort). Soyez humble : concevez un système qui fonctionne pour 10k à 50k utilisateurs, et faites-vous à l’idée qu’il sera nécessaire de le réécrire entièrement lorsque le moment sera venu.
Eliminez tout ce que vous ne comprenez pas et apprenez des preuves en travaillant dès que possible sur les problèmes difficiles, et en parallèle. Ajoutez du monitoring au plus vite et sur tous les éléments qui pourraient poser des problèmes ultérieurement, et prenez le temps de tout instrumentaliser : métriques systèmes, taille des files d’attente, bande passante, traces applicatives, … et aux différents couches de votre écosystème.
Une architecture typique - quel que soit le domaine d’application - se constitue de plusieurs services généralement découplés et interfacés les uns avec les autres. La décomposition des services consiste à séparer chaque service pour répondre à un ou plusieurs problèmes en particulier.
Une évolution possible consiste à faire basculer ces blocs vers des services Cloud, exposés par des plateformes externes. Cette (nouvelle) intégration peut être de deux types :
Les APIs externes nécessitent cependant une attention particulière au niveau de la sécurité. En poussant cette réflexion un peu plus loin, il est facile de considérer chaque API comme externe au reste de l’écosystème ; ceci permet de pousser la sécurité à son paroxysme, en visant un environnement Zero Trust, où chaque action nécessite l’utilisateur à s’authentifier.
Le paradoxe de l’architecture se situe au niveau des performances : d’un côté, nous essayons de construire des systèmes maintenables et faciles à comprendre, et de l’autre côté, nous devons conserver des objectifs de performances adéquats, en faisant rentrer l’application dans les contraintes techniques (CPU, RAM, disques, réseaux, processus et I/O, …).
L’UX ne se limite pas uniquement à l’ergonomie générale d’une application ou à sa facilité d’utilisation, mais touche en fait à tous les domaines auxquels l’utilisateur a accès, en ce compris les APIs, la configuration, la documentation ou les possibilités d’extension (= greffons) pouvant y être adjoints.
Un système proche de la perfection anticipe ce que vous pourriez souhaiter au moment où vous pourriez en avoir besoin. Pour cela, il est nécessaire :
De manière générale, concevez l’UX avant l’implémentation : évitez autant que possible de rendre une configuration complexe (La configuration de Gitea par exemple est bourrée de valeurs par défaut est relativement brain-friendly). Dans tous les cas, documentez systématiquement quelques exemples, accompagnés des valeurs possibles et du sens que vous leur avez donné.
Une macro-architecture voit les systèmes comme des unités - qui peuvent être autant des composants que des services. Chaque fonctionnalité qui ne peut pas être représentée en utilisant l’un de ces blocs de conception (building blocks), par exemple les load balancers, outils ou middlewares, sera implémentée comme un service. Chacun de ces services couvrira certaines caractéristiques ou fonctionnalités qui ne pourront être réutilisées comme des building blocks. Une des pistes de stratégies consistera à choisir entre une architecture orientée services (SOA) ou une architecture orientée ressources (ROA).
Chronologiquement, nous avons vu l’évolution de ces architecture :
Une architecture moderne se compose des éléments suivants :
Il existe plusieurs manières de coordonner les échanges d’informations ; en général, piloter le flux d’informations à partir des désidérata de l’utilisateur est suffisant. Il est également possible d’utiliser des méthodologies (type Choreography, Centralized Middlewares ou services externes), mais celles-ci viennent avec leurs propres désavantages - et notamment la nécessité d’un monitoring intense de chaque ressource, pour suivre chaque avancée, récupération ou notification.
Dans une architecture moderne, la sécurité est un point critique qui doit être pris au sérieux à toutes les étapes de la mise en application, la base de tout ceci étant le renouvellement des certificats et le support SSL/TLS. Il convient dès lors de :
La sécurité est un domaine trop complexe que pour être réimplémentée par n’importe qui.
If you choose to start from scratch, you will end up investing a lot of time and still not getting it right (Page 85)
La gestion des utilisateurs implique de gérer les méthodes d’authentification, les écrans de connexion, la fédération et les imprévus - type récupération de mot de passe. Elle implique également de gérer les traces d’audit, les niveaux d’autorisation et les clés de connexion. De manière générale, il existe quatre grandes catégories d’utilisateurs, chacune ayant son propre IAM :
En termes d’authentification, les anciens modèles envoyaient les mots de passe à destination du serveur, mais il s’agit d’une mauvaise habitude, puisque le serveur pourrait enregistrer et traiter ces informations. Les nouvelles tendances contactent un Identity Provider (Idp), type SAML ou OpenId Conect, au travers d’un échange de jeton :
Anyone can design a security system that you yourself can’t think of a way of breaking. That doesn’t mean it works, it just means that it works against people stupider than you."
Une vidéo de Sam Scott explique pourquoi l’authentification est compliquée.
Les principales manières de gérer les autorisations se font sur les modèles RBAC (Role Based Access Control) ou ReBAC (Relationship Based Access Control) - qui est une évolution du premier, permettant surtout de contrôlersur quels éléments un utilisateur peut effectuer un ensemble d’actions - là où RBAC ne permet que de donner un accès ou des permissions à l’ensemble des éléments d’un même type.
Si RBAC est suffisant pour vous, utilisez-le.
Stratégiquement, s’il existe un service externe qui couvre l’ensemble de vos besoins et qui peut être déployé en une fois, utilisez-le, plutôt que de (re)construire une solution graduellement. Un système présentant une solution naïve n’est généralement pas une bonne solution. Mais d’un autre côté, remplacer un IAM ou CIAM va clairement être ardu.
Le principal risque avec le GDPR, ce sont les employés qui partageraient des données sensibles. Le GDPR exige que les données d’identification (Personal Identifiable Information - PII), telles que les numéros de registre, numéros de cartes d’identité, adresses emails, soient sécurisées. En termes de sécurité, il est conseillé de ne conserver que ce qui est réellement nécessaire (principe de least knowledge) ; il est également conseillé de séparer les données sensibles (de manière chiffrée, si possible) des autres informations.
Une manière (considérée comme bonne…) consiste à accéder aux données grâce à des UUID, puis à les stocker au niveau de l’IAM.
Dans un environnement Zero-Trust, les utilisateurs sont continuellement authentifiés, autorisés et validés, à chaque étape ou transaction qu’ils effectuent. Historiquement, les utilisateurs était automatiquement approuvés dès lors qu’ils se trouvaient dans le périmètre de sécurité de l’institution ; cependant, la surface d’attaque a continuellement augmenté - notamment avec l’introduction des BYOD, IoT et APIs. Le besoin en sécurité apparait de plus en plus évident à plusieurs endroits d’une infrastructure, et avec un minimum de privilèges, afin de limiter le Blast Radius.
Il y a deux manières principales de proposer de la haute-disponibilité :
La réplication est mise en place en conservant des copies distinctes mais synchronisées (et à jour) d’un ensemble de données, derrière un répartiteur de charge. Les répartiteurs hardware sont les plus fiables ; après quoi vient l’IP Hot Swap. Si ni l’un, ni l’autre ne sont disponibles, passez par un package type keepalive sur un environnement Linux.
Un des problèmes lié à la répartition concerne le maintient des sessions : si une session devait être disponible sur l’un des systèmes, il est possible de passer par des sticky sessions - soit au travers d’un stockage en base de données, soit en utilisant le local storage proposé par le navigateur.
La réplication consomme beaucoup de ressources (il est nécessaire d’avoir au moins 2n+1 noeuds) et que le débogage n’est pas le plus facile qui soit, il est intéressant de considérer le fast-recovery, mais il faut pour cela que :
La haute-disponibilité se base également sur les concepts de mise à l’échelle verticale (on augmente la RAM, on ajoute un plus gros CPU, …) ou horizontale (on ajoute de nouvelles instances).
Ecrire des services est relativement facile en utilisant des technologies récentes. En cas de doute, le plus simple consiste à utiliser un modèle type “Un thread = une requête” (en faisant en sorte que chaque requête puisse être exécutée rapidement et en s’assurant qu’il n’y a rien de bloquant sur le chemin d’exécution). Faites également en sorte que chaque requête soit idempotent, éventuellement en échangeant des cycles CPU par de la mémoire, via l’utilisation d’une mémoire cache.
Un service peut être amélioré avec une architecture Event Driven (EDA) ou Staged Event Driven Architecture (SEDA). Ce point nécessite cependant une attention particulière, dans la mesure où la complexité qui est apportée peut ne pas valoir l’investissement. Mais il peut être nécessaire de rendre un service plus complexe qu’il ne l’est, pour allégrer la complexité de l’architecture dans son ensemble.
Un système stable est un système dont le comportement reste prédictible, qui remplit toutes ses spécifications (même sous une charge conséquente) et qui continue à fonctionner sous un mode dégradé si des conditions extraordinaires devaient se présenter : tout le monde préfère un système qui fonctionne moins bien pendant une courte période temps, qu’un système qui pourrait perdre ou corrompre ses données - même avec une probabilité très faible.
Les erreurs connues sont des erreurs qu’il est possible de gérer de manière méthodique.
Pour palier aux erreurs inconnues, les solutions sont également multiples, mais la principale concerne l’Observability, en passant par un Application Performance Manager (APM) : au plus vous apprendrez à propos de vos systèmes, au plus vous réaliserez qu’il y a des choses que vous ne connaissez et ne comprenez pas. Un APM permet de suivre des métriques personnalisées (latences, bande passante, utilisation des ressources, …). A chaque fois que le système dévie d’une ligne de référence (initialisée manuellement), il sera nécessaire de réaliser une analyse.
The ennemy of agility is large and heavy build (page 167)
Business hates surprises. Although there will be (surprises), the key is to communicate them early and explain the sources.
Modéliser vos APIs et formats de message le plus tôt possible, en gardant systématiquement la sécurité en ligne de mire. ↩︎