Autour du codeDevelopper toujours mieux
Posté le

Nameko le framework pour faire des microservices

Nameko est un framework basé sur RabbitMQ et qui va gérer pour nous la communication entre nos services.

On peut distinguer deux types de communication. Le RPC (Remote Procedure Call) c'est le fait qu'un service appelle une méthode définie sur un autre service et attend que celui-ci lui retourne le résultat et le pub/sub un service va émettre un événement et d'autres services vont réagir à cet événement.

Mise en place de RabbitMQ

$ docker run -d --name rabbit -p 15672:15672 -p 5672:5672 rabbitmq:3-management

Création de notre premier service

On va créer un micro-service que l'on va appeler receptionniste, son rôle sera de dire bonjour. Pour créer un service avec Nameko rien de plus simple, il suffit de créer une classe avec un attribut name et d'ajouter le décorateur @rpc aux méthodes que l'on souhaite rendre appelables à distance.

from nameko.rpc import rpc


class Receptionniste:

    name = "receptionniste_service"

    @rpc
    def bonjour(self, name):
        return f'Bonjour {name}'

Lancer son service

Pour lancer son service on va saisir la commande shell suivante:

nameko run receptionniste --broker amqp://guest:guest@localhost

Par défaut Nameko exécute la première classe qu'il trouve dans le module, mais vous pouvez également en spécifier une autre en utilisant la commande

nameko run receptionniste:Voiturier --broker amqp://guest:guest@localhost

Si l'on souhaite immédiatement essayer de faire un appel distant sur notre service, Nameko fourni un shell

$ nameko shell --broker amqp://guest:guest@localhost
Nameko Python 3.6.5 (default, Mar 29 2018, 18:20:46)
[GCC 8.0.1 20180317 (Red Hat 8.0.1-0.19)] shell on linux
Broker: amqp://guest:guest@localhost
>>> n.rpc.receptionniste_service.bonjour('Tom')
'Bonjour Tom'

Faire des appels RPC entre services

Maintenant que l'on a un receptionniste pour dire bonjour, on va ajouter des voiturier pour garer les voitures des clients. la classe RpcProxy va nous permettre de communiquer avec un service du moment que l'on connait son nom.

from nameko.rpc import rpc, RpcProxy

class Receptionniste:

    name = "receptionniste_service"
    voiturier = RpcProxy("voiturier_service")

    @rpc
    def bonjour(self, name):
        self.voiturier.garer_voiture(name)
        return f'Bonjour {name}'

from nameko.rpc import rpc
from random import randint


class Voiturier:

    name = "voiturier_service"

    @rpc
    def garer_voiture(self, name):
        print("j'y vais")
        numero_de_place = randint(1000, 9999)
        return f"La voiture de {name} est garée à la place {numero_de_place}"

On va lancer un receptionniste_service

$ nameko run receptionniste --broker amqp://guest:guest@localhost

Et dans des terminaux différents on va lancer deux services voiturier:

$ nameko run voiturier --broker amqp://guest:guest@localhost

$ nameko run voiturier --broker amqp://guest:guest@localhost

Lorsque l'on fait plusieurs appels à la méthode bonjour du service receptionniste celui-ci va chercher à appeler garer_voiture auprès d'un service voiturier. Dès qu'un voiturier est libre, il prendra l'appel et il sera le seul à l'exécuter

$ nameko shell --broker amqp://guest:guest@localhost
>>> for name in ('Tom', 'Lea', 'Bob', 'Bea', 'Jul'):
...     n.rpc.receptionniste_service.bonjour(name)
...
'Bonjour Tom'
'Bonjour Lea'
'Bonjour Bob'
'Bonjour Bea'
'Bonjour Jul'

$ nameko run voiturier --broker amqp://guest:guest@localhost
starting services: voiturier_service
Connected to amqp://guest:**@127.0.0.1:5672//
J'y vais
J'y vais

$ nameko run voiturier --broker amqp://guest:guest@localhost
starting services: voiturier_service
Connected to amqp://guest:**@127.0.0.1:5672//
J'y vais
J'y vais
J'y vais

Émettre et réagir à des évènements

Lorsque l'on communique avec des évènements, on appelle cela du "publish/subscribe" généralement abrégé "pub/sub"

Dans ce modèle de communication le service qui émet l'évènement ne va attendre aucune réponse, des services peuvent écouter cet évènement et réagir à celui-ci sans que le service qui a émit l'évènement soit au courant

Avec Nameko, on utilise la classe EventDispatcher pour émettre des évènements. On va reprendre notre exemple et faire en sorte que les services voituriers publient à chaque fois qu'ils ont garé une voiture.

Pour qu'une méthode soit appelée sur un évènement, on va la décorer avec le décorateur event_handler en lui passant le nom du service et le nom de l'évènement. Dans notre exemple, on va créer deux nouveaux service. Le service gardien qui va surveiller la voiture et le service laveur qui va passer un coup sur la carrosserie

from nameko.rpc import rpc
from nameko.events import EventDispatcher
from random import randint

class Voiturier:

    name = "voiturier_service"

    dispatch = EventDispatcher()

    @rpc
    def garer_voiture(self, name):
        print("J'y vais")
        numero_de_place = randint(1000, 9999)

        self.dispatch('voiture garée', {
            'personne': name,
            'numero_de_place': numero_de_place
        })

        return f"La voiture de {name} est garée à la place {numero_de_place}"

from nameko.events import event_handler

class Laveur:

    name = "laveur_service"

    @event_handler("voiturier_service", "voiture garée")
    def laver_voiture(self, event_data):
        print(f'La voiture de {event_data["personne"]} est propre')

from nameko.events import event_handler

class Gardien:

    name = "gardien_service"

    @event_handler("voiturier_service", "voiture garée")
    def surveiller_voiture(self, event_data):
        print(f'La voiture de {event_data["personne"]} est surveillée')

On a plus qu'à lancer tous ce petit monde:

nameko run voiturier --broker amqp://guest:guest@localhost

nameko run receptionniste --broker amqp://guest:guest@localhost

nameko run gardien --broker amqp://guest:guest@localhost

nameko run laveur --broker amqp://guest:guest@localhost

Et à dire bonjour au réceptionniste:

$ nameko shell --broker amqp://guest:guest@localhost
Nameko Python 3.6.5 (default, Mar 29 2018, 18:20:46)
[GCC 8.0.1 20180317 (Red Hat 8.0.1-0.19)] shell on linux
Broker: amqp://guest:guest@localhost
>>> for name in ('Tom', 'Lea', 'Bob', 'Bea', 'Jul'):
...     n.rpc.receptionniste_service.bonjour(name)
...
'Bonjour Tom'
'Bonjour Lea'
'Bonjour Bob'
'Bonjour Bea'
'Bonjour Jul'

Par défaut la capture d'évènement et de type SERVICE_POOL c'est-à-dire que si j'ai plusieurs fois le même service qui écoute l'évènement, un seul va réagir. Si par exemple j'instancie plusieurs services laveur, un seul ira laver la voiture

Si je souhaite que tous mes services laveur partent laver la voiture je peux changer le type de capture d'évènement comme ceci:

...

@event_handler("voiturier_service", "voiture garée", handler_type=events.BROADCAST)
def laver_voiture(self, event_data):
    print(f'La voiture de {event_data["personne"]} est propre')

Quand utiliser RPC et quand utiliser pub/sub

C'est deux façons différentes de voir les choses. Soit on fait de l'orchestration, un service ordonne à d'autres services ce qu'ils doivent faire et comment ils doivent le faire, soit on fait de la chorégraphie les services s'écoutent les uns les autres et réagissent à des événements.

pub/sub est adapter pour faire de la chorégraphie et RPC pour faire de l'orchestration

L'avantage de la chorégraphie c'est qu'il y a moins de couplage entre vos services, et qu'il est plus simple d'ajouter ou supprimer un service par contre il peut être difficile de suivre le flux d'exécution.

Allez à vos IDE et bon code! smile