Vous êtes sur la page 1sur 27

Rapport d’expertise

Mastère CTO & Tech Lead

Validation d’expertise de fin d’études

Dussordet Marie-Athénaïs
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

I. Chain of Responsibility et serverless, le couple parfait ? 4


Serverless, fonctionnement et coût 4

Chain of Responsibility pattern, précisions depuis le contexte 8

Postulat : coupler le serverless au Chain Of Responsibility pattern 11

II. Chain of Responsibility/Serverless - El Dorado ? 13


Serverless, entre objet de mode et crainte de l’inconnu 13

Les coûts du serverless sont-ils vraiment avantageux ? 15

Le Chain of Responsibility pattern est-il la bonne solution ? 17

III. Chain of Responsibility et serverless, inspiration ? 19


Adapter le Chain Of Responsibility 19

Le serverless, automatisme ? 21

Proposition d’architecture définitive 22

Bibliographie 25
Sites 25

Ouvrages 25

Annexes 26
Calculs engagement utilisateur 26

Implémentation d’une interface de handler en Golang 27

2
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

“The fundamental assumption underlying all software projects is that software is easy to change. If you violate
this assumption by creating inflexible structures, then you undercut the economic model that the entire industry
is based on.”, Robert C. Martin, The Clean Coder: A Code of Conduct for Professional Programmers1. Robert
C. Martin souligne ici l’importance d’une architecture flexible, adaptable, et maintenable. Le sacerdoce d’un
bon architecte est donc de concevoir un système à la fois complexe et appréhendable, optimisé et
parfaitement maintenable, et évolutif tant en matière de charge que de fonctionnalités.

D’aucuns diraient que certains systèmes sont de facto inflexibles, lents, et atomiques donc indivisibles. Le
sempiternel “cette fonctionnalité est codée ainsi depuis longtemps, on ne pouvait pas faire mieux”. Pourtant,
certains design pattern deviennent peu à peu des anti-patterns (e.g le singleton2) tandis que de nouvelles
architectures sont créées, comme la relativement récente architecture “hexagonale”3 pensée par Alistair
Cockburn en 2005.
Quid du large traitement de données ? Prenons le cas de statistiques d’un jeu de type MMORPG4. Nous
avons un set de données conséquent, souvent très disparate dans sa construction, servant de sources sur
laquelle s’appliqueront bon nombre de calculs et opérations.. Ce système, à première vue, requiert un temps
d’exécution long et coûteux en RAM (Random-Access-Memory) ; c’est le cas dans de nombreuses
entreprises aujourd’hui, notamment celles qui ont pris de l’ampleur et donc ont exponentiellement allongé
le temps de traitement desdites données.
Cette situation est-elle intrinsèquement figée dans cet état immuable ? Ou pourrait-on penser un système
plus efficace ?
La première hypothèse ici serait que l’utilisation du monolithe constamment servi ne serait pas idéale. Nous
pourrions imaginer que le système ne tournerait que lorsque les statistiques auraient besoin d’être calculées,
i.e un système serverless.
La seconde hypothèse serait qu’en cherchant dans les différents design pattern connus à ce jour, nous
trouverions un modèle efficace et adapté à notre situation. Nous proposerons tout d’abord l’implémentation
du Chain Of Responsibility pattern.
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que
l’on pourrait croire fondamentalement immuable ?
Nous verrons d’abord l’implémentation théorique, puis les limites de ce postulat. Nous chercherons enfin à
définir une solution viable et pragmatique.

1
Robert Cecil Martin. The Clean Coder: A Code Of Conduct For Professional Programmers. Prentice Hall, 2011
2
Refactoring Guru, https://refactoring.guru/design-patterns/singleton, 2014
3
Alistair Cockburn, https://alistair.cockburn.us/hexagonal-architecture
4
MMORPG : massively multiplayer online role-playing game

3
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

I. Chain of Responsibility et serverless, le couple parfait ?

1. Serverless, fonctionnement et coût

Le serverless est un modèle d'exécution de cloud computing dans lequel le fournisseur de cloud alloue des
ressources machine à la demande, en prenant soin des serveurs pour le compte de ses clients.
Le terme serverless est néanmoins trompeur en ce qu’il désignerait dans sa traduction la plus littérale
l’absence de serveur, bien que toute instance serverless est elle-même évidemment hébergée sur un serveur.
Cette nouvelle appellation représente néanmoins le fait que le développeur peut être totalement désintéressé
des soucis techniques liés à l’implémentation, la maintenance, la montée en charge inhérente à un serveur.
Nous connaissons à ce jour trois principaux cloud providers: AWS (Amazon Web Service), Google Cloud et
Microsoft Azure. Trois noms faisant partie des fameux GAFAM5, les géants des technologies de
l’information. Nous pourrions également citer Alibaba Cloud, le concurrent chinois des géants occidentaux,
ou encore Oracle ou IBM, non comparables en termes de marché, mais qui figurent néanmoins parmi les
clouds les plus réputés.
Dans le cadre de ce mémo, nous nous consacrerons aux trois grands noms et plus précisément à AWS, leader
incontesté du marché.

La majorité des cloud providers fournissent aujourd’hui un schéma similaire à celui ci-dessous.
L’API Gateway est le point d’entrée du système, nécessaire pour communiquer avec tout élément extérieur.
L’event queue est un système de file d’attente, pour traiter dans l’ordre les demandes envoyées.

Le dispatcher est le répartiteur chargé de distribuer correctement les différentes demandes. Les workers sont
les fonctions qui sont appelées “à la demande”, le cœur du code écrit par le développeur, le reste étant géré
par le cloud provider et les opérationnels (ops, spécialistes des opérations des technologies de l’information),
qui peuvent être internes à l’entreprise ou un service fourni par le cloud provider.

5
Google, Apple, Facebook, Amazon, Microsoft.

4
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

Ce système présente deux avantages :


- Pay-as-you-go (paiement à la consommation) : les coûts sont réduits au temps d’exécution du code et
à la mémoire allouée à cette exécution.
- Élasticité : le système peut automatiquement gérer sa capacité à encaisser l’afflux de demandes qu’il
reçoit, que ce soit en augmentant ses ressources disponibles (scale up) ou en les réduisant (scale
down). Cela induit un gros avantage compétitif sur des systèmes pensés pour leur extensibilité
(scalability). Non pas dans leur potentiel à décupler les ressources mises à disposition, mais dans leur
capacité à s’adapter à plusieurs situations, notamment les montées en charge, mais aussi la descente.
Un afflux très soudain n’impactera ainsi que très peu des architectures serverless élastiques car des
instances “jetables” peuvent se lancer pour le temps de l’afflux, et être détruites lorsque le flux
redescend.

Les différents cloud providers mettent donc tous à disposition leur système de serverless.

Le coût unitaire d’exécution d’une fonction serverless


est dérisoire. Dans le tableau ci-contre, nous pouvons
voir les tarifs d’AWS, qui proposent à ce jour les prix
les plus compétitifs du marché.

Prenons l’exemple d’une fonction serverless d’emailing


basique. Son temps d’exécution théorique est de 30
ms, nous choisissons de lui allouer 128MB de
mémoire.
Si nous voulions envoyer 1 millions d’emails, nous
aurions à débourser les $0,20 puisque nous dépassons
le million de requêtes, plus les $0.0000000021 par ms
multipliés par le nombre de requêtes et le temps
d'exécution, nous amenant à $0,063. Le coût total de
l’opération théorique est donc de $0,263. 

Un même système hébergé en continu n’aurait à cette


échelle pas été bien plus onéreux, sachant notamment
que nous pouvons louer de plus petits serveurs qui
pour cette “faible” quantité de requêtes auraient été amplement suffisants.
Posons un exercice théorique. Une entreprise de taille moyenne ayant 10 millions d’utilisateurs doit envoyer
50 millions de mails par mois.

5
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

Si le mailing était géré au sein d’une architecture monolithique sur un ensemble de serveurs en disponibilité
permanente, il serait nécessaire de faire varier le nombre d’instances de ces serveurs au gré du volume d’envoi
d’email. Si nous prenons un groupe de serveurs classiques, à un prix relativement dérisoire de $0,1506 par
heure et par instance, et que celui-ci pour gérer l’afflux de nombreux utilisateurs s’exécutait sur 15 instances
différentes, le prix pour l’envoi de 50 millions d’emails par mois reviendrait au coût d’hébergement, soit
$1620.
Dans le cas d’une lambda classique AWS, le coût total serait de $10.315 par mois7.

La différence peut sembler effectivement énorme, il faut néanmoins prendre en compte que le système
monolithique peut gérer plusieurs fonctionnalités, tandis qu’une lambda ne sert l’exécution que d’une seule
fonction.
Cet exemple met néanmoins en exergue les tarifs plus que compétitifs des solutions proposées par les
différents cloud providers. De plus, cela démontre la flexibilité d’un système construit sur une architecture
serverless. En effet, l’afflux d’utilisateurs n’impactera pas le coût des envois de mails car il n’est aucunement lié
au fonctionnement du backend et des autres services. A ce titre, l’envoi de 100 ou un milliard de mails
n’impactera pas le coût de fonctionnement des autres services.

Cela présente aussi un avantage quant à la maintenance de ces services. Dans le cadre d’un serveur
“classique”, tout le serveur doit être redéployé pour changer une fonctionnalité. Dans une architecture
serverless, chaque fonction peut être maintenue et déployée indépendamment des autres. De plus le
déploiement de chaque fonction est géré par le cloud provider, et est facilement utilisable, notamment avec
des systèmes de CD8 aussi communs que Github Actions et Gitlab CI.

En outre, le découpage d’une architecture en plusieurs fonctions serverless permet une souplesse non
négligeable quant au choix des technologies utilisées. L’envoi d’email, pour reprendre l’exemple, pourra se
traiter en Golang, langage développé par Google favorisant le multithreading, atout critique dans l’exécution
de tâches concurrentes. Pour du traitement d’images, l’architecte pourrait opter pour du C ou du Python.
Pour différents crons, on pourrait penser à du Node.js, etc.

Les crons sont notamment un bel exemple de l’implémentation intelligente et utile d’une architecture
serverless. En effet, les crons sont des planificateurs de tâches sur les systèmes d'exploitation de type Unix. Ils
ne tournent qu’à des moments précis et pour un temps défini, et généralement courts. Ils sont l’essence
même de ce que proposent les fonctions serverless.

Le serverless offre la possibilité non négligeable de fonctionner en asynchrone, soit la synchronisation des
opérations par l'utilisation d'impulsions envoyées lorsque l'opération précédente est terminée plutôt qu'à
intervalles réguliers. L’asynchrone permet de libérer l’utilisateur du temps d’attente quand ce dernier n’est
pas strictement nécessaire. Prenons comme exemple l’envoi d’un email de confirmation dans le cas d’une

6
Moyenne réalisée sur les tarifs des différents fournisseurs de serveurs dédiés, prenant en compte la puissance nécessaire pour héberger
entièrement un code monolithique.
7
Les calculs sont effectués hors coûts annexes (SMTP, templating, etc.)
8
Continuous deployment

6
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

création de compte. Dans un fonctionnement synchrone, nous devrions attendre sur la page de création que
le mail soit envoyé pour charger la page. A l’inverse, dans le cadre d’une exécution asynchrone, nous pouvons
afficher un message de prise en compte de la confirmation et de l’envoi d’email, et l’utilisateur le recevra
ensuite. L’utilisateur n’ayant pas expressément besoin de l’envoi de son email sur le coup, l’asynchrone est ici
souvent préféré.

7
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

2. Chain of Responsibility pattern, précisions depuis le contexte

Le Chain of Responsibility (CoR) pattern est un modèle de conception comportementale (behavioral design
pattern) qui permet de transmettre des demandes le long d'une chaîne de gestionnaires (handlers). À la
réception d'une demande, chaque gestionnaire décide soit de traiter la demande, soit de la transmettre au
gestionnaire suivant de la chaîne. Le pattern fait partie de Design Patterns: Elements of Reusable
Object-Oriented Software9 du Gang Of Four10, ouvrage de référence sur les design patterns.

Dans un soucis de clarté, nous reprendrons l’exemple de Refactoring Guru11 pour transposer le modèle dans
un problème non lié aux technologies du Web.

Après avoir acheté un nouveau hardware, l’utilisateur appelle le service après-vente (SAV) pour savoir
comment le faire fonctionner sur un OS Linux. Il aura d’abord affaire à un répondeur automatique qui le
dirigera vers le support technique général, qui l’enverra vers le technicien plus spécialisé qui se chargera
d’expliquer la procédure à suivre. Grâce à cette organisation, le technicien spécialisé n’aura jamais à traiter
avec des clients qui ne posent pas des questions trop avancées, et l’utilisateur voulant accéder seulement aux
conditions de retour du produit n’aura qu’à consulter le répondeur automatique.

Dans des enjeux de développement, le CoR pattern tente de résoudre le code “spaghetti”, en permettant de
découper les différents acteurs en modules séparés et autonomes ; chacun responsable de traiter ou non une
information.
Prenons de nouveau l’exemple de Refactoring Guru, une entreprise qui gère un système d’authentification
utilisateur. Au lancement du projet, l’authentification est basique et gère simplement la connexion, viennent
ensuite de nouvelles contraintes, telles que l'autorisation, la gestion des rôles, la validation, le cache, etc.
Nous nous trouvons donc en face d’un code rapidement illisible et trop complexe.

9
Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software,
Addison Wesley, 1994
10
Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides
11
Refactoring Guru, https://refactoring.guru/design-patterns/chain-of-responsibility

8
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

Nous plaçons des handlers en chaîne, qui géreront chacun une partie spécifique des validations (cf. le
schéma ci-dessus). Non seulement cela améliore la lisibilité du code, mais cela permet également de
déterminer quel handler est le plus susceptible de renvoyer un erreur, et donc de le placer en amont dans la
chaîne (principe de fail-fast).

Dans le cas d’un MMORPG, les données sont disparates et nécessitent de nombreux traitements qui
diffèrent selon le jeu de données de départ et le résultat attendu. Par exemple, si nous souhaitons savoir quel
“type de personnage” est le plus adapté pour telle situation, nous devrons prendre en compte toutes les
statistiques de dégâts de tous les personnages et réaliser des moyennes. Nous pourrions également avoir
besoin de statistiques sur les zones les plus occupées, les serveurs, l’argent dépensé sur le jeu (en prenant en
compte les devises), etc.

La première option serait de traiter les différents cas selon un arbre décisionnel au sein d’une même fonction,
l’ensemble produisant un code proportionnellement illisible. Dans un souci de maintenabilité, nous ne
choisirons pas cette option.
Nous opterons plutôt pour la solution proposée par le CoR pattern ; nous séparerons les différents
traitements dans différents handlers. Selon le type de données fournies en entrée, nous exécuterons le
handler de statistique correspondant pour renvoyer le résultat attendu.

Schéma de l’architecture Chain Of responsibility proposée dans le cas d’un monolithe

9
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

Cette architecture, dans le contexte d’un code monolithique, permet de séparer les différents traitements de
données en des entités clairement définies. Le code devient maintenable, car il est facilement lisible et la
logique exécutée sur la donnée est agnostique des autres handlers.
Nous pouvons déjà constater que si une statistique est plus demandée qu’une autre, nous avons la possibilité
de placer son handler au début de la chaîne, et donc optimiser le temps de traitement de la requête. Nous
pourrions également appliquer la même logique sur des données sensibles au temps (time sensitive).

10
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

3. Postulat : coupler le serverless au Chain Of Responsibility pattern

L’architecture serverless et le Chain of Responsibility pattern ont comme point commun la séparation
réfléchie de logiques de code en plusieurs petites unités agnostiques.
Nous pouvons donc facilement penser un système qui, plutôt qu’être monolithique, reprend les principes
du design CoR pattern en séparant chaque handler dans une fonction serverless.

Schéma de l’architecture Chain Of responsibility proposée dans le cas d’une architecture serverless

Chaque handler peut être optimisé individuellement (RAM, CPU, nombre d’instances), selon son
utilisation théorique dans un premier temps, puis selon son utilisation réelle. Notamment en ajoutant un
Load Balancer, qui couplé à un système automatique de mise à l’échelle, permettra de gérer la création et la
suppression des différentes instances proportionnellement à la charge de travail. Le Load Balancer est un
service proposé par tous les cloud providers, et est géré partiellement par le développeur mais son
provisionnement est pris en charge par le provider.

Nous pouvons aussi implémenter un système de file d’attente (queue12) pour traiter intelligemment le flux de
requêtes statistiques. Un système de queues est un service entièrement géré par l'hébergeur qui permet de
conduire l'exécution, la distribution et la livraison d'un grand nombre de tâches distribuées. Un processus
peut être effectué de manière asynchrone, sans être un élément bloquant d’une demande d'utilisateur.
L'exécution asynchrone est un moyen bien établi de réduire la latence des requêtes et de rendre une
application plus réactive. Un système de queue permet d'organiser et de contrôler ces demandes avec des
fonctionnalités telles que la planification, la déduplication, les politiques de nouvelle tentative configurables
et la redirection de version.

Afin d’optimiser de nouveau notre système, nous choisirons d’écrire les handlers en Golang, un langage
permettant de travailler en multithreading, et donc de traiter des calculs en parallèle et rapidement. Ce
langage est également supporté par les trois principaux cloud providers13.

12
Amazon SQS, Google TaskQueues, Azure Service Bus Queues
13
A noter que Azure Functions ne le supporte qu’avec la fonction “custom handlers”,
https://docs.microsoft.com/en-us/azure/azure-functions/supported-languages#language-support-details

11
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

Schéma de l’architecture Chain Of responsibility proposée avec une architecture serverless

Si nous reprenons l’optimisation pensée pour le pattern CoR monolithique, nous pouvons faire de même
dans l'exécution des fonctions serverless, optimisant cette fois-ci le prix d’exécution, puisque les fonctions en
bout de route seront peu appelées.
Nous pourrons également implémenter un système de logs fait sur mesure pour cette architecture, par
exemple Elastic Logstash14, solution leader du marché.

Schéma de l’architecture Chain Of responsibility proposée avec une architecture serverless avec un système de logs.

Ce système est optimisé, en comparaison d’un système monolithique, mais nous pouvons nous demander si
ce système est implémentable dans la réalité d’une entreprise.

14
Elastic Logstash, https://www.elastic.co/logstash/

12
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

II. Chain of Responsibility/Serverless - El Dorado ?

1. Serverless, entre objet de mode et crainte de l’inconnu

Le serverless est sur toutes les lèvres depuis quelques années. Le terme serverless est apparu très récemment,
compte tenu de l’histoire du web (Le world-wide-web ayant été disponible au grand public en 1991).
La première référence est publiée en 2012 dans un article de Ken Fromm15 qui loue les avantages du serverless
avec l’émergence du cloud. Le serverless a d’abord été adopté par les entreprises de la Silicon Valley, puis par le
monde des startups à l’international.
Aujourd’hui encore, le serverless est considéré comme un objet de mode, testé par les jeunes entreprises. Un
concept souvent vu comme surestimé et surutilisé.
Nous pouvons souligner que si la solution serverless paraît optimisée dans certains cas, elle n’est jamais la
panacée. Comme pour toutes les architectures créées en informatique, le serverless n’est jamais une solution
parfaite.

Tout d’abord, le choix d’un cloud provider est souvent critique. D’aucuns pourraient penser que cela repose
surtout sur les différences de prix et de configurations proposées. En effet, les cloud provider ont tous de
nombreux tarifs, offres et types de machines, et cela potentiellement paraît intimidant de premier abord.
Nous disposons néanmoins de nombreuses documentations de plus en plus orientées vers des développeurs
qui ne sont pas formés sur les ops. Également, des systèmes de serverless restent simples à implémenter,
comme Google Cloud Run, qui peut se lancer à partir d’un simple dockerfile ou d’un yaml. Nous pouvons
aussi mentionner Terraform, outil infrastructure as code16, qui fournit à l’utilisateur un langage déclaratif de
configuration pour les infrastructures cloud.

Nous pouvons soulever le fait qu’une architecture serverless ne peut que rarement composer l’intégralité
d’un projet logiciel. Il y aura souvent le besoin, dans notre cas par exemple, de mettre en place un système
d’authentification séparé et d’une ou plusieurs bases de données pour les statistiques recueillies.
L’optimisation de coût peut donc être relative si le système principal est déjà en place.

De plus, le choix d’un cloud provider revient à se restreindre à une architecture dans les services proposés par
le prestataire. Bien sûr, si le système est pensé à l’avance, il est possible d’implémenter une solution
agnostique, mais cela requiert une expertise plus poussée pour les développeurs qui les mettront en place.
En théorie, il est plus simple de déployer et maintenir un code fonctionnant en serverless. Cela demande une
réelle organisation en amont et en pratique ; par exemple, si le développeur doit modifier la structure de la
base de données depuis une des fonctions serverless, il devra modifier toutes les fonctions qui utilisent cette
partie de la base de données. Cela requiert des développeurs expérimentés et une organisation de projet
stricte.

15
Why the future of software and apps is serverless, Ken Fromm, Readwrite.cm,
https://readwrite.com/2012/10/15/why-the-future-of-software-and-apps-is-serverless/
16
processus de gestion et d'approvisionnement des centres de données informatiques via des fichiers de définition lisibles par machine,
plutôt qu'une configuration matérielle physique ou des outils de configuration interactifs.

13
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

Aussi, l’implémentation d’une architecture serverless signifie une perte de maîtrise, notamment de son
lancement. Comme le cloud provider gère une grande partie des processus de déploiement, d’arrêt et
d’élasticité, le client a moins la main sur les systèmes qu’il crée. Le debugging peut devenir plus complexe dans
le cas d’un développeur non formé sur le système, et l’entreprise est dépendante du cloud provider.

Le serverless est une implémentation qui demande des équipes ayant de l’expérience et de l’organisation, ce
qui peut potentiellement amener des coûts supplémentaires pour l’entreprise.

14
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

2. Les coûts du serverless sont-ils vraiment avantageux ?

Nous pouvons dorénavant prendre en compte que si les équipes dans l’entreprise n’ont pas les formations
et/ou l’expérience nécessaire, l’implémentation d’une architecture serverless engage des frais de recrutement
ou de mise à niveau des équipes.
Nous avons aussi remarqué que l’implémentation du serverless implique d’autres systèmes mis en place
puisqu’elle n’est pas universelle. Il convient donc de déterminer si une implémentation de ces architectures
(microservices, monolithes) et leur coût actuel ou supposé est réellement plus avantageux en y ajoutant une
architecture serverless.
Dans le cas d’une implémentation from scratch comme ici, les coûts seront plus avantageux puisque nous
pensons toute l’architecture autour d’une architecture serverless centrale, et que la fonctionnalité n’existe pas
précédemment dans l’entreprise. Dans le cas d’une refactorisation d’une service préexistant, il conviendra
d’évaluer au mieux les coûts actuels du service et de les comparer au nouveau service mis en place.
Il nous aussi prendre en compte le temps et le coût de démarrage (boot) d’une fonction serverless. En effet, si
un de nos handlers est appelé très régulièrement, ce que nous pouvons savoir en monitorant les instances
créées, il conviendra de repenser sa durée de vie (lifetime). Par exemple, nous pourrions décider que la
machine virtuelle bootée traitera x messages depuis la queue avant de s’éteindre. Il faudra calculer très
exactement, par handler, quel sera le pull de message depuis la queue. Un handler pourra donc traiter 50
messages avant de s’éteindre tandis qu’un autre, que l’on démarre que très rarement, n’en traitera que 6. Cela
évitera les boots constants, coûteux en temps et en énergie.
Cette optimisation demande évidemment une équipe de développeurs formés, mais aussi un ops, ou tout du
moins un devops, métier relativement récent qui combine les qualités intrinsèques à un développeur et des
connaissances en gestion de cloud et d’architectures serveur.
Les économies réalisées avec cette architecture serverless sont, sur le long terme, incontestables, mais elles sont
inéluctablement accompagnées de dépenses en amont en termes de ressources humaines (RH) et de temps
de développement. Pour mener à bien cette implémentation, il faudra évidemment un architecte en amont,
que ce soit un freelance en conseil, ou un élément préexistant de l’équipe. Il nous faudra compter également
un lead developer17 (ou CTO18 si l’entreprise a peu d’effectifs) et un chef de projet pour définir les objectifs
techniques et métiers pour mettre en place un service concret. Enfin, nous devrons disposer d’une équipe de
développeurs majoritairement seniors, bien qu’un junior puisse apprendre à implémenter ce système, il
n’aura en général ni le recul, ni l’expérience nécessaire pour gérer les différents cas complexes qui peuvent se
poser (e.g. l’implémentation d’interfaces pour écrire les fonctions serverless afin d’éviter de trop devenir
dépendant de son cloud provider). Le strict minimum de développeurs backend sur le projet devrait être un
senior confirmé et un junior expérimenté.

17
Chargé d’une équipe de développeurs sur un projet/produit précis
18
Chief Technical Officer, Directeur technique de l’entreprise, à la tête de toutes les équipes techniques

15
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

Poste Expérience Salaire moyen (en milliers d'€)

CTO/ Lead Dev au moins 3 ans 75-85

Chef de projet au moins 3 ans 45-55

Senior developer au moins 5 ans 65-75

Junior developer 1 ou 2 ans 35-50

Total 220-265
Coût total RH estimé

Nous constatons que le coût total estimé en RH oscille entre 330 et 400 mille euros19.

L’implémentation d’une telle architecture en serverless est donc un investissement non négligeable. Même si
sur la longueur, les dépenses de cette fonctionnalité seront moindres et optimisées, il faut noter que cela
requiert une anticipation RH.

Les pièges du serverless et de la solution moins coûteuse miracle sont donc :


- Une implémentation non réfléchie
- Des équipes qui n’ont pas le niveau de maintenir ou d’implémenter une telle solution
- Les coûts induits des autres architectures qui doivent tourner en parallèle

19
Calculs réalisés avec les données de www.glassdoor.fr, sur les salaires moyens sur Paris, en prenant en compte la récente augmentation des
salaires dûe notamment à un manque de développeurs sur le marché et les charges patronales et sociales.
La moyenne haute a très souvent été retenue, puisque nous cherchons des équipes expérimentées.

16
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

3. Le Chain of Responsibility pattern est-il la bonne solution ?

Les différents handlers dont nous aurons besoin dépendent du résultat attendu et des données en entrée.
Dans notre cas pratique, nous avons des données de jeu, qui peuvent varier d’une donnée utilisateur pure
(nom, âge, etc.) à des coordonnées de mouvement du personnage joué, aux dégâts infligés sur un telle ou
telle aventure. Les données sont multiples, non singulièrement typées, et il en va de même pour les possibles
résultats.
Prenons comme postulat que nous souhaitons :

- Pour les utilisateurs:


- Une moyenne d'âge des utilisateurs
- Un graphique de répartition des joueurs à travers le monde
- Leur taux d’engagement, que nous calculerons en prenant en compte le nombre moyen de
sessions, la fréquence et la longueur de connexion ainsi que le nombre d’achats et leur
fréquence.
- Pour les combats:
- Une moyenne des dégâts par combat et par patch20 par type de personnage jouable
- Une moyenne des soins apportés par les personnages de type soin (heal)
- Les mouvements de chaque personnage dans chaque groupe de joueur qui a participé à un
combat afin de déterminer les stratégies adoptées par les différentes équipes.

Nous représentons dans le schéma ci-dessous les handlers, et donc des fonctions serverless associées. Il n’est
en rien exhaustif sur la quantité totale de handlers que nous mettrions en place dans le cas de statistiques sur
un MMORPG, il a vocation à démontrer le fonctionnement d’une infirme partie des statistiques, définie
ci-dessus.
Dans notre modèle actuel, le schéma concret est linéaire. Pour accéder à la statistique `DamageOnEncounter`
nous devrons passer par les handlers `GetUsersAge`, `GetUsersLocation` et `GetEngagement` avant de
pouvoir renvoyer la donnée. Nous pouvons bien sûr optimiser le placement des différents handlers dans la
chaîne s’il est clair qu’une donnée statistique est plus fréquemment requêtée.

Nous pouvons également faire en sorte que les fonctions les plus utilisées soient optimisées pour traiter un
nombre de requêtes conséquent avant de s’éteindre. L’implémentation très canonique ici peut amener de la
perte du puissance de processing, bien que plusieurs systèmes comme celui-ci puissent tourner en
concurrence.

Le pattern CoR apporte cependant une nuance à ses propres directives. Dans le cas où la donnée est amenée
à devoir être traitée dans un ordre qui peut changer, nous pouvons “fournir des setters pour un champ de
référence à l'intérieur des handlers [...] (pour être) en mesure d'insérer, de supprimer ou de réorganiser les
handlers de manière dynamique”21.
20
Version du jeu, chaque nouveau patch présente des mises à jour (combats, équipement, zone, etc.)
21
Citation traduite depuis Refactoring Guru sur le Chain Of responsibility pattern,
https://refactoring.guru/design-patterns/chain-of-responsibility

17
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

Avec l’introduction d’un champ de référence à l’intérieur de chaque handler, nous pouvons donc changer
dynamiquement l’ordre d’exécution des handlers, et ne plus avoir à parcourir toute la chaîne de handlers
statistiques pour n’en récupérer qu’une.
Nous conservons un système automatisé dans le traitement des requêtes par rapport à la donnée qu’il reçoit
en évitant une montée en flèche des ressources utilisées et du coût total de l’infrastructure.

Schéma des handlers, Chain Of Responsibility pattern classique

18
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

III. Chain of Responsibility et serverless, inspiration ?

1. Adapter le Chain Of Responsibility

L’introduction d’un champ de référence afin de permettre un ordre flexible de l’exécution des handlers est
une étape importante, et applicable aisément.

Cependant, nous constatons très clairement des “familles” dans lesquelles nous pourrions regrouper nos
handlers. Notamment, nous pouvons aisément les séparer en “données utilisateurs” et “statistiques de jeu”,
comme fait naturellement pour présenter le postulat de nos besoins.

Nous pourrions alors séparer notre schéma linéaire en deux architectures de handlers chaînés distincts. En
introduisant en amont un dispatcher qui pointera vers deux systèmes, et deux implémentations de COR.

Schéma des handlers, Chain of Responsibility pattern découplé

Dans le schéma ci-dessus, nous constatons déjà une plus grande lisibilité, d’autant que nous travaillons
actuellement avec un petit nombre de handlers. Nous pouvons aussi constater que l’implémentation d’un
champ de référence paraît bien moins critique.

En regroupant les types de handler, nous pouvons également penser à améliorer les performances d’une
famille entière de handler, en imaginant tout d’abord une priorisation dans le système de queue. Par exemple,

19
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

l’implémentation d’un groupe de handlers de statistiques sensibles au temps (time-sensitive data) implique
une priorité dans la queue et dans le dispatcher en dessous. Cette chaîne aura également de meilleures
puissances de processing, ainsi qu’un nombre d’instances maximum plus élevé.

Si le regroupement en plusieurs grandes familles de handlers est efficace, nous pouvons également nous
demander si un plus fin découpage pourrait améliorer le système actuel. En effet, le handler
`GetEngagement`, quand on connaît la complexité de calcul d’un taux d’engagement (déterminée en partie
p.12), gère de nombreux calculs que nous pouvons découper en plusieurs handlers, d’autant plus que les
résultats de ces handlers pourraient être utiles à d’autres calculs.

Chaque handler ci-contre produit un indice (de 0 à 100)22,


moyenne de tous les utilisateurs du jeu. Par exemple, un
utilisateur ayant moins d’un an d’ancienneté et
comptabilisant plus de cent sessions aura un indice de cent
dans le handler `NumberOfSessions`, tandis qu’un utilisateur
ayant trois ans d’ancienneté comptabilisant le même nombre
de session aura un indice plus faible de trente-trois. La
moyenne des indices de tous les utilisateurs donne le taux
d’engagement des utilisateurs quant aux nombres de
connexions, et donc au nombre de sessions.

Les autres handlers fonctionnement similairement. Pourtant,


a-t-on ici une autre implémentation du pattern CoR ?

Même si l’idée d’une séparation méthodique et chaînée est


inspirée du principe fondamental incarné par le CoR, ici
nous choisissons de partager les données traitées d’un handler
à l’autre, et non de passer d’un handler à l’autre jusqu’à
exécuter la fonction requise.

Ici, nous séparons la logique de code pour qu’elle soit non seulement plus lisible, mais également qu’elle soit
réutilisable. Si nous souhaitions connaître l’engagement en termes de sessions et non d’habitudes d’achats,
nous pouvons seulement utiliser les trois premiers handlers.

L’architecture conserve le pattern CoR et s’en inspire pour découper certains handlers.

22
Cf. annexe sur le détail du calcul de l’engagement pensé dans le cadre de ce document.

20
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

2. Le serverless, automatisme ?

En considérant les inconvénients du serverless, notamment le recrutement systématique de ressources


humaines expérimentées, nous ne pouvons penser un système géré seulement en serverless.

Premièrement, le dispatcher nouvellement introduit dans l’architecture va être appelé à chaque fois que le
service est utilisé. Nous pouvons donc le penser comme un microservice, qui tourne constamment.

Aussi, toutes les données traitées, surtout celles qui ne sont pas ou peu sensibles au temps, doivent être
conservées. Les données étant disparates et en larges volumes, nous opterons pour une base de données non
relationnelle. Chaque handler stockera ses résultats dans le système de gestion de base de données (SGBD).

Ajouter un système de stockage des données traitées permet d’améliorer grandement le processus asynchrone
et de limiter les erreurs. En effet, si un des handlers renvoie une erreur et ne peut plus fonctionner, la donnée
déjà traitée ne sera pas perdue mais stockée en attente de traitement. De plus, un SGBD donne la possibilité
d’implémenter un système de cache plus sophistiqué, avec un temps d’expiration sur mesure pour chaque
handler et son type de statistique déjà traitée. Si nous prenons par exemple un combat dans un patch qui
n’est pas le plus récent, les données demandées ici n’ont que rarement à être mises à jour. Le cache aura donc
un temps d’expiration plus long, et le handler traitant cette requête pourra renvoyer cette donnée sans passer
par la logique complexe de calculs, avec un temps d'exécution moindre, et donc un coût réduit. Si nous
prenons une donnée utilisateur comme l’engagement, que nous avons séparé en plusieurs handlers
disparates, nous savons que ces données sont calculées en asynchrone, et les différents résultats pourront être
découpés sur un pull d’utilisateurs et stockés en database puis caché quand toutes les données sur la période
ont été traitées. L’engagement des utilisateurs sur une période n’est pas voué à changer car il n’y aura jamais
de nouveaux éléments à prendre en compte. De plus, ce genre de statistiques utilisateurs, une fois
entièrement traitée, pourra être transférée sur des logiciels tiers de marketing et/ou de communication, e.g
un outil de Customer Relationship Management (CRM). Nous pourrions noter par ailleurs que les
principaux cloud providers proposent des processus de CRM clé-en-main, des solutions pouvant être
intéressantes pour les entreprises qui ne possèdent pas de grand département marketing.

21
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

3. Proposition d’architecture définitive

Nous proposerons une architecture inspirée de toutes les remarques que nous avons pu développer dans ce
document.

Pour produire une architecture définitive, nous devons choisir un cloud provider. Microsoft Azure, en retard
dans le marché des leaders du cloud, ne sera pas compris dans la comparaison. Google Cloud et AWS sont les
leaders du marché. Ils proposent tous deux des services similaires, à des prix compétitifs.

Notre décision se fera sur un point essentiel : le serverless est plus développé sur AWS, et il reste au cœur de
notre architecture. Nous choisirons donc de développer notre architecture sur AWS, en gardant en tête que
chaque fonction serverless gardera une indépendance de l'infrastructure si nous voulions un jour changer de
cloud provider. Tous les autres services utilisés dans notre système seront fournis par AWS.

Le point d’entrée de l’architecture sera un AWS API Gateway qui gère toutes les tâches impliquées dans
l'acceptation et le traitement jusqu'à des centaines de milliers d'appels d'API simultanés, y compris la gestion
du trafic, la prise en charge CORS, l'autorisation et le contrôle d'accès, la limitation, la surveillance et la
gestion des versions d'API.

Les queues seront gérées par AWS SQS, un service de mise en file d'attente des messages entièrement géré qui
permet de découpler et de faire évoluer les microservices, les systèmes distribués et les applications serverless.

Le dispatcher utilisera AWS Load Balancer, un elastic load balancer qui distribue automatiquement le trafic
entrant sur plusieurs cibles, telles que les instances EC2, les conteneurs et les adresses IP, dans une ou
plusieurs zones de disponibilité. Il surveille la santé de ses cibles enregistrées et achemine le trafic
uniquement vers les cibles saines. Elastic Load Balancing adapte l'équilibreur de charge à mesure que le trafic
entrant change au fil du temps. Il peut s'adapter automatiquement à la grande majorité des charges de
travail.

Pour le cache, nous choisissons AWS Elasticache, un service de mise en cache en mémoire entièrement géré
prenant en charge des cas d'utilisation flexibles et en temps réel. Il peut être utilisé pour la mise en cache, qui
accélère les performances des applications et des bases de données, ou comme magasin de données principal
pour les cas d'utilisation qui ne nécessitent pas de durabilité, comme les magasins de session, les classements
de jeux, le streaming et l'analyse.

Pour la gestion de nos données non relationnelles, nous utiliserons AWS DynamoDB, une base de données
NoSQL entièrement gérée, sans serveur et à valeur clé, conçue pour exécuter des applications hautes
performances à n'importe quelle échelle. DynamoDB offre une sécurité intégrée, des sauvegardes continues,
une réplication multirégionale automatisée, une mise en cache en mémoire et des outils d'exportation de
données.

La proposition finale est une proposition théorique de l’architecture sur AWS. Elle ne prend pas en compte
les différentes mesures de sécurité (Bastion, IAM, etc.) que l’équipe devra mettre en place pour le projet.

22
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

23
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un
système que l’on pourrait croire fondamentalement immuable ?

Le système proposé s’éloigne énormément d’une architecture monolithique, pourtant encore largement
canon.
Nous avons opté pour le très plébiscité serverless, qui, d’apparence aisé à implémenter, implique des
investissements onéreux dans le temps et dans les ressources mises en place. Cette architecture, bien que
capable de fonctionner en autonomie, est souvent couplée à d’autres services. Elle n’est en rien la solution
universelle que d’aucuns encensent. Elle est un outil de séparation de logique pure de notre cas métier et
technique.
Le pattern choisi pour traiter l’exécution des requêtes statistiques est un choix que nous avons justifié.
Néanmoins, nous avons choisi d’utiliser sa variante dynamique, en ce que cela nous permet une meilleure
optimisation de notre système. De plus, nous avons opté pour plusieurs implémentations de ce pattern, dont
une n’est que très vaguement inspirée du principe de séparation logique.
Nous arrivons à une architecture structurée et cohérente, mais elle ne reste qu’une proposition parmi tant
d’autres, et elle est irrévocablement amenée à s’adapter, et est pensée pour faciliter les changements non
radicaux.
Une architecture n’est jamais fondamentalement immuable, il n’est néanmoins pas toujours possible de
calquer les différents design patterns sur de nouvelles technologies plébiscitées. Chaque architecture, chaque
problème et chaque situation est vouée à être repensée, notamment dans un domaine qui est en constante
évolution. Les architectures d’aujourd’hui ne font que proposer des versions adaptées de patterns
pré-existants. Pour citer Erich Gamma, membre de l’informel Gang of Four et co-auteur de la référence
Design Patterns: Elements of Reusable Object-Oriented Software : “A design that doesn’t take change into
account risks major redesign in the future”.

24
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

Bibliographie

Sites :

- Refactoring Guru : https://refactoring.guru/


- Alistair Cockburn : https://alistair.cockburn.us/hexagonal-architecture
- Glassdoor : https://www.glassdoor.fr/
- Amazon Web Service : https://aws.amazon.com
- Google Cloud : https://cloud.google.com/
- Azure Cloud : https://docs.microsoft.com/en-us/azure/
- “Why the Future of App Is Serverless”:
https://readwrite.com/2012/10/15/why-the-future-of-software-and-apps-is-serverless/
- Serverless showdown: AWS Lambda vs Azure Functions vs Google Cloud Functions,
https://acloudguru.com/blog/engineering/serverless-showdown-aws-lambda-vs-azure-functions-vs-
google-cloud-functions?utm_source=medium_blog&utm_medium=redirect&utm_campaign=me
dium_blog
- Lucid Chart : https://lucid.app/lucidchart (confection des schémas)

Ouvrages:

- Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of
Reusable Object-Oriented Software, Addison Wesley, 1994
- Robert Cecil Martin. The Clean Coder: A Code Of Conduct For Professional Programmers. Prentice
Hall. 2011
- Robert Cecil Martin, Clean Architecture: A Craftsman's Guide to Software Structure and Design.
Prentice Hall. 2017

25
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

26
Est-il aujourd’hui possible d’optimiser le traitement d’un large set de données en repensant un système que l’on pourrait croire
fondamentalement immuable ?

Exemple de code d’interface implémentable en Golang pour la gestion des handlers

27

Vous aimerez peut-être aussi