Intégrer MassTransit à Azure Functions 2.0 via une extension

Arnaud TAMAILLON
Oct 7, 2019 · 6 min read
Image for post
Image for post

Chez Younited, notre architecture micro-services repose très fortement sur du messaging pour assurer la scalabilité et la résilience du système.

Dans ce cadre, nous sommes de gros utilisateurs (et parfois contributeurs) de MassTransit, une librairie qui nous apporte notamment, via son intégration à Azure Service Bus, une gestion automatisée de la topologie des queues, souscriptions et topics, de la résilience de la connexion, et des erreurs de traitements.

Or MassTransit est une librairie pensée pour fonctionner dans le cadre de services exécutés en permanence. Cela semble a priori mal s’intégrer dans une approche serverless, où l’on cherche à ne consommer des ressources que lorsque une charge de travail est disponible pour traitement.

Dans la mesure où les équipes de Younited tendent à utiliser de plus en plus l’approche serverless pour leurs nouveaux développements ou refactorings, il est important pour l’architecture générale du SI de s’assurer que les apports de MassTransit sont conservés à cette occasion.

La proposition d’intégration à Azure Functions de MassTransit

La documentation MassTransit propose un exemple d’intégration à la plateforme Azure Functions. Ci-dessous le code proposé reproduit:

Proposition de la documentation MassTransit

Cette approche souffre de plusieurs écueils:

La présence de l’attribut ServiceBusTrigger met en évidence le fait que MassTransit n’est pas en charge de la connexion au bus. Adieu donc la gestion automatique de la topologie, et la création automatique des topics, souscriptions et queues.

  1. Chaque réception de message nécessite la reconfiguration du pipeline MassTransit, avec le log, la stratégie de retry, le branchement des consommateurs, et toute autre configuration nécessaire. Impossible a priori également d’avoir des fonctions différentes pour plusieurs types de messages sur la même queue. Peu satisfaisant, et peu efficace a priori.

En définitive, si le code parait potentiellement peu efficace, ce sont surtout les différences de comportement avec notre approche standard sur notre SI qui sont gênantes. Elles sont en effet d’ordre à créer des problèmes opérationnels à nos DevOps et à nos équipes de développement, en terme de monitoring, mais aussi de réaction aux incidents qui ne manqueront pas de se produire.

Voyons si on peut faire mieux en développant une extension spécifique pour Azure Functions!

Développer une extension MassTransit pour Azure Functions 2.0

Il faut noter que l’ensemble des triggers de fonctions, même ceux fournis par Microsoft, exploitent les mêmes capacités d’extensions du framework Azure WebJobs SDK 3.x. L’implémentation que nous allons mettre en oeuvre n’y fait pas exception.

Par ailleurs, l’implémentation qui sert de support à cet article est volontairement simpliste, pour mettre en avant les concepts principaux qui régissent le développement de nouveaux triggers. En revanche, il est important de noter que cette simplicité ne rend pas le code impropre à l’usage en production.

L’ensemble du code sur lequel se base cet article se trouve ici:

Créer un attribut pour décrire le trigger

En premier lieu, il faut noter que MassTransit utilise une approche basée sur le code pour la définition de la topologie du bus et de la configuration des différents éléments de celle-ci.

Or, ces possibilités de configuration sont nombreuses! Par ailleurs, l’utilisation d’un attribut restreint de manière assez forte les possibilités, dans la mesure où tout élément référencé dans l’attribut doit être une constante accessible à la compilation.

Dans cette optique, l’attribut portera le nom de la queue à utiliser, ainsi qu’un chaîne de caractères qui identifiera le bus, et qui nous servira à faire le lien avec une configuration que nous fournirons par ailleurs. On va par ailleurs ajouter la capacité de spécifier qu’on veut utiliser la feature de Session de Service Bus.

Attribut du trigger MassTransit

Cet attribut nous permettra ainsi de décrire notre fonction de la sorte:

Utilisation de l’attribut dans une fonction

Créer le BindingProvider pour créer le Binding à partir de l’attribut

Le TriggerBindingProvider a pour rôle de s’assurer que le trigger peut effectivement être mis en place au vu des paramètres, et de créer le TriggerBinding correspondant. Si les conditions ne sont pas réunies, il suffira de retourner null et le SDK prendra le relais sur la gestion d’erreur.

Dans notre cas, on va vouloir accepter que le paramètre ait le type:

  • soit du message reçu (Message)

Enfin, MassTransit faisant usage de types génériques pour enregistrer les consommateurs de messages, nous allons en profiter pour utiliser un TriggerBinding générique, et, au moyen de code réflectif, de fournir le type du message en argument de type générique.

Ce qui nous donne le code suivant:

Implémentation du BindingProvider

Créer le Binding

Le TriggerBinding a pour responsabilité de créer le Listener qui va être à l’écoute des événements (dans notre cas, des messages présents dans la queue Service Bus), et de binder les données de cet événement aux paramètres de la fonction.

Le code résultant sera le suivant:

Implémentation du Binding

Créer le Listener pour écouter les événements en tâche de fond

Le Listener va configurer MassTransit pour écouter les messages sur Azure Service Bus, et utiliser le ITriggeredFunctionExecutor fourni par le SDK lors de la réception de ceux-ci pour déclencher la fonction configurée.

C’est ici que notre décision prise lors de la création de l’attribut, de fournir uniquement un nom de référence pour la connexion au bus va avoir son pendant. Nous allons en effet utiliser un service IServiceBusHostConfigurationFactory qui sera fourni par le projet qui utilisera le trigger.

Ce service permettra, à partir du nom du bus et du type de message, de configurer la connexion au bus (pour par exemple fournir les informations d’authentification), ou la consommation du message (pour par exemple la stratégie de retry).

Comme l’indique la documentation MassTransit , dans le cas où l’on configurerait plusieurs fonctions pour écouter des messages de types différents sur la même queue, il est important de n’ouvrir qu’un seul endpoint.

Le framework Azure Functions 2.0 fonctionne en deux phases, à savoir la découverte des fonctions et la création des IListener par les Bindings, puis le démarrage de l’ensemble de ces Listener. Nous allons donc profiter de ce fonctionnement pour retourner une seule et même instance pour plusieurs fonctions qui partageraient la même queue.

L’implémentation ci-dessous construit donc un cache de Listener dans un dictionnaire, et empile des actions de configuration à effectuer lors de la création effective de la connexion par MassTransit pour chaque queue, afin de connecter tous les consommateurs nécessaires sur une même queue.

A noter que la configuration de l’utilisation du mode Session est répétée pour chaque consommateur, mais que cela ne pose pas de souci, car on se contente d’activer le comportement dès qu’un trigger l’exige.

Implémentation de la ListenerFactory, qui fournira les Listener au framework de Fonctions

Créer l’infrastructure de l’extension

Il nous reste à faire en sorte que l’attribut soit reconnu par le SDK lorsque l’on référence le nuget d’extension.

Ainsi, nous allons définir une Extension via un IExtensionConfigProvider qui va enregistrer le binding en liant l’attribut au provider que nous avons développé.

L’enregistrement automatique de l’extension sera réalisé grâce à l’utilisation de l’attribut d’assembly WebJobsStartup. Cet attribut permettra au runtime du SDK Azure WebJobs de découvrir la classe qui prendra en charge cet enregistrement et d’exécuter le code correspondant lors de l’initialisation du process.

Utiliser l’extension MassTransit dans une fonction

Maintenant que nous avons à disposition un trigger MassTransit, il s’agit de le mettre en oeuvre sur une fonction.

Nous allons pour cela réutiliser l’attribut WebJobsStartup pour prendre la main sur le démarrage du process dans notre projet de fonction et configurer l’injection de dépendance pour fournir l’implémentation de IServiceBusHostConfigurationFactory.

Conclusion

L’approche choisie, si elle est, comme déjà indiqué simpliste dans son approche, permet d’utiliser l’intégralité des capacités de MassTransit dans le cadre de fonctions.

Bien évidement, la nécessité d’enregistrer un service au démarrage pour fournir la configuration n’est pas des plus élégantes, mais cela permet de ne pas avoir à définir un nombre énorme de structures descriptives de toutes ces options.

L’extension développée a ainsi pu être utilisée en production par nos équipes de développement, garantissant une approche cohérente, et la réutilisation des connaissances, pratiques, et expertise développées sur la librairie MassTransit.

Merci à Anthony Hocquet pour ses tests de l’extension et ses correctifs, les échanges et ses retours précieux!

YounitedTech

Le blog Tech de Younited, où l’on parle de développement…

Arnaud TAMAILLON

Written by

YounitedTech

Le blog Tech de Younited, où l’on parle de développement, d’architecture, de microservices, de cloud, de data… Et de comment on s’organise pour faire tout ça. Ah, et on recrute aussi, on vous a dit ?

Arnaud TAMAILLON

Written by

YounitedTech

Le blog Tech de Younited, où l’on parle de développement, d’architecture, de microservices, de cloud, de data… Et de comment on s’organise pour faire tout ça. Ah, et on recrute aussi, on vous a dit ?

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store