Avant de passer aux relations avec les développeurs, je suis passé d’architecte logiciel à architecte de solutions il y a longtemps. C’est un changement de carrière assez courant. Le problème dans cette situation est double :
- Vous connaissez parfaitement les bibliothèques de logiciels.
- Vous ne connaissez pas bien les composants de l’infrastructure.
Il semble logique que les personnes dans cette situation essaient de résoudre les problèmes avec les solutions qui leur sont le plus familières. Cependant, cela ne signifie pas que c’est la meilleure approche. C’est un mauvais dans la plupart des cas.
Un exemple concret
Imaginez une application API. Il s’exécute sur la JVM et est écrit dans le style « Réactif » à l’aide du framework Spring Boot.
L’une des exigences est de limiter le nombre d’appels qu’un utilisateur peut passer dans un laps de temps. Dans le monde des API, une telle fonctionnalité de limitation de débit est répandue.
Avec mon chapeau d’architecte logiciel, je chercherai une bibliothèque JVM qui le fera. Parce que j’ai un peu d’expérience, je connais l’excellente bibliothèque Bucket4J :
bibliothèque de limitation de débit Java basée sur l’algorithme de seau à jetons
– Godet4J
C’est juste une question d’intégrer la bibliothèque dans mon code:
val beans = beans {
bean {
val props = ref<BucketProperties>() //1
BucketFactory().create( //2
props.size,
props.refresh.tokens,
props.refresh.duration
)
}
bean {
coRouter {
val handler = HelloHandler(ref()) //3
GET("/hello") { handler.hello(it) }
GET("/hello/{who}") { handler.helloWho(it) }
}
}
}
class HelloHandler(private val bucket: Bucket) { //3
private suspend fun rateLimit( //4
req: ServerRequest,
f: suspend (ServerRequest) -> ServerResponse
) = if (bucket.tryConsume(1))
f.invoke(req)
else
ServerResponse.status(429).buildAndAwait()
suspend fun hello(req: ServerRequest) = rateLimit(req) { //5
ServerResponse.ok().bodyValueAndAwait("Hello World!")
}
}
- Obtenir les propriétés de configuration d’un
@ConfigurationProperties
-classe annotée. - Créez un compartiment correctement configuré.
- Passez le seau au gestionnaire.
- Créez un wrapper de limitation de débit réutilisable basé sur le compartiment.
- Enveloppez l’appel.
À ce stade, le bucket concerne l’ensemble de l’application. Si nous voulons un bucket dédié par utilisateur, conformément aux exigences, nous devons :
- Apportez Spring Security pour authentifier les utilisateurs (ou écrivez notre propre mécanisme d’authentification).
- Créez un compartiment par utilisateur.
- Stockez le compartiment côté serveur et liez-le à la session utilisateur.
Bien que ce soit parfaitement acceptable, c’est beaucoup d’efforts pour une fonctionnalité que l’on peut implémenter moins cher ailleurs.
Le cas d’or pour les passerelles API
« Une place pour chaque chose, chaque chose à sa place. »
Cette citation est associée à Samuel Smiles, Mme Isabella Beeton et Benjamin Franklin.
Dans tous les cas, les fonctionnalités transversales n’appartiennent pas à l’application mais aux composants de l’infrastructure. Notre fonctionnalité est une API, c’est donc un cas d’utilisation parfait pour une API Gateway. Nous pouvons simplifier le code en supprimant Bucket4J et en configurant une API Gateway devant l’application.
Voici comment procéder avec Apache APISIX.
consumers:
- username: joe
plugins:
key-auth: #1
key: joe
- username: jane
plugins:
key-auth: #1
key: jane
routes:
- uri: /hello*
upstream:
type: roundrobin
nodes:
"resilient-boot:8080": 1
plugins:
limit-req: #2
rate: 1
burst: 0
key: consumer_name #3
rejected_code: 429
key-auth: ~ #1
- Nous utilisons un simple en-tête HTTP pour l’authentification à des fins de démonstration. Les applications du monde réel utiliseraient OAuth2.0 ou OpenID Connect, mais le principe est le même.
- Plugin de limitation de débit.
- Configurez un compartiment par consommateur.
Discussion : Qu’est-ce qui appartient où ?
Avant de répondre à la question, permettez-moi d’abord de faire un détour. Le livre Thinking, Fast and Slow fait valoir que le cerveau a deux « modes »:
La thèse principale du livre est celle d’une dichotomie entre deux modes de pensée : le « système 1 » est rapide, instinctif et émotionnel ; Le « Système 2 » est plus lent, plus délibératif et plus logique.
De plus, le système 2 est beaucoup plus énergivore. Parce que nous sommes paresseux, nous avons tendance à privilégier le système 1 – rapide et instinctif. Ainsi, en tant qu’architectes, nous privilégierons généralement :
- Des solutions que nous connaissons, par exemple des bibliothèques pour d’anciens architectes logiciels
- Règles à appliquer aveuglément : en guise de commentaire secondaire, c’est la principale raison de la mentalité de troupeau dans l’industrie de la technologie, comme les « microservices partout ».
Par conséquent, considérez les conseils suivants comme des lignes directrices et non comme des règles. Maintenant que cela a été dit, voici ma position.
Tout d’abord, vous devez déterminer si la fonctionnalité est purement technique. Par exemple, la limitation de débit classique pour empêcher DDoS est purement technique. De telles caractéristiques techniques appartiennent à l’infrastructure : chaque proxy inverse digne de ce nom a ce type de limitation de débit.
Plus une fonctionnalité est métier, plus elle doit être proche de l’application. Notre cas d’utilisation est légèrement lié à l’entreprise car la limitation du débit est par utilisateur. Pourtant, la passerelle API fournit la fonctionnalité prête à l’emploi.
Ensuite, connaissez les composants de votre infrastructure. Il est impossible de connaître tous les composants, mais vous devez avoir une connaissance passagère des éléments disponibles dans votre organisation. Si vous utilisez un fournisseur de cloud, obtenez une carte de tous les services qu’il propose.
Concernant l’impossibilité de connaître tous les composants, parlez-en à vos SysAdmins. Mon expérience m’a montré que la plupart des organisations doivent utiliser efficacement leurs SysAdmins. Ces derniers aimeraient être davantage impliqués dans la conception globale de l’architecture du système, mais ils sont rarement sollicités. La plupart des SysAdmins aiment partager leurs connaissances !
Il faut aussi penser à la configuration. Si vous devez configurer chaque composant de bibliothèque sur chaque instance, c’est un énorme drapeau rouge : préférez un composant d’infrastructure. Certaines bibliothèques offrent une solution de configuration centralisée, par exemple, Spring Cloud Config. Évaluez soigneusement la complexité supplémentaire d’un tel composant et son taux de défaillance par rapport aux autres composants d’infrastructure dédiés.
Les organisations influencent le choix beaucoup. Le même problème dans deux contextes organisationnels différents peut aboutir à deux solutions opposées. La familiarité avec une solution l’emporte généralement sur le meilleur ajustement des autres solutions.
Enfin, comme je l’ai mentionné dans l’introduction, votre expérience influencera vos choix : les anciens architectes logiciels préfèrent les solutions centrées sur les applications, et les anciens administrateurs système les solutions d’infrastructure. Il faut veiller à limiter son parti pris envers sa solution préférée, qui pourrait ne pas être la mieux adaptée dans un contexte différent.
Conclusion
Dans cet article, j’ai pris l’exemple de la limitation du débit par utilisateur pour montrer comment on peut l’implémenter dans une bibliothèque et un composant d’infrastructure. Ensuite, j’ai généralisé cet exemple et donné quelques lignes directrices. J’espère qu’ils vous aideront à faire de meilleurs choix concernant l’emplacement d’une fonctionnalité dans votre système.
Le code source complet de cet article est disponible sur GitHub.