26-07-2019, 02:40 PM
Je vais parler un peu d'implémentation !
Dans l'architecture CQRS/ES choisie, on raisonne en commandes et en événements.
Pour envoyer une unité exploiter une parcelle de ressources, on envoie une commande UnitStartsExploitingDeposit à notre aggregate Game.
Grâce à un module qu'on appelle un routeur, on définit que les commandes UnitStartsExploitingDeposit doivent être transmises à un aggegate Game qui contient l'état d'une partie.
Le système fera donc tourner autant d'aggregate Game qu'il y a de partie, et il les identifiera grâce à la clé "game_id" de chaque commande (cela fait partie des informations que l'on doit donner au routeur).
En soit, une commande n'est qu'un "value objet" contenant quelques clés. Dans le cas de la commande UnitStartsExploitingDeposit, on a trois clés : game_id, unit_id et deposit_id.
Très concrètement, un aggegate est un module qui implémente une fonction de décision (execute) et une fonction de mutation (apply) :
- la fonction de décision execute reçoit l'état de l'aggregate et une action et retourne soit une erreur, soit une liste d'événements (potentiellement aucun) à émettre ;
- la fonction de mutation apply reçoit pour chaque événement émis l'état de l'aggregate et un événement et retourne le nouvel état de l'aggregate.
Ici, la fonction de décision execute est appelée et effectue quelques vérifications métier grâce aux arguments qui lui sont fournis, à savoir (l'état de l'aggregate (l'état de la partie, donc) et la commande UnitStartsExploitingDeposit :
- est-ce que l'unité choisie a bien accès au gisement demandé ?
- est-ce que ce type d'unité est capable d'exploiter ce type de gisement ?
- est-ce qu'elle n'est pas déjà en train d'exploiter ce gisement ?
Si tout va bien, la fonction retourne un seul événement UnitStartedExploitingDeposit, avec quelques informations (unit_id, deposit_id, timestamp).
Cet événement est alors ajouté de manière permanente et immuable dans l'historique de notre application.
Ensuite, la fonction de mutation apply est exécutée : elle reçoit à son tour l'état de l'aggregate ainsi que l'événement UnitStartedExploitingDeposit. On modifie l'état de la partie.
Cette architecture (l'event sourcing) est complexe mais apporte de nombreux avantages dans le cas d'un jeu :
- en terme de compréhension : il est facile de visualiser les transitions entre un état et un autre puisque ça ne passe que par les fonctions de mutation ;
- de tracabilité : on sait exactement ce qui se passe (et s'est déjà produit) dans l'application, on peut même ajouter des fonctionnalités a posteriori (analyse statistiques, trophées, etc.) ;
- de débogage : là aussi, si un bug survient, on peut remonter l'historique des événements pour savoir où ça a cloché, il suffit de voir quel élément de code a émis l'événement foireux (ou n'a pas émis l'événement attendu) ;
En plus de l'event sourcing, CQRS (dont on parlera plus tard) apporte également son lot d'avantages et d'inconvénients. Un autre article traitera des avantages et inconvénients de l'architecture CQRS/ES.
L'état de la partie, c'est une structure de données qui contient tout ce dont on a besoin pour la faire évoluer et prendre les décisions à la réception des commandes suivantes.
Dans le cas de l'aggregate Game de Seelies, il s'agit d'une map avec quelques clés, j'y reviendrai un autre jour.
Dans l'architecture CQRS/ES choisie, on raisonne en commandes et en événements.
Pour envoyer une unité exploiter une parcelle de ressources, on envoie une commande UnitStartsExploitingDeposit à notre aggregate Game.
Grâce à un module qu'on appelle un routeur, on définit que les commandes UnitStartsExploitingDeposit doivent être transmises à un aggegate Game qui contient l'état d'une partie.
Le système fera donc tourner autant d'aggregate Game qu'il y a de partie, et il les identifiera grâce à la clé "game_id" de chaque commande (cela fait partie des informations que l'on doit donner au routeur).
En soit, une commande n'est qu'un "value objet" contenant quelques clés. Dans le cas de la commande UnitStartsExploitingDeposit, on a trois clés : game_id, unit_id et deposit_id.
Très concrètement, un aggegate est un module qui implémente une fonction de décision (execute) et une fonction de mutation (apply) :
- la fonction de décision execute reçoit l'état de l'aggregate et une action et retourne soit une erreur, soit une liste d'événements (potentiellement aucun) à émettre ;
- la fonction de mutation apply reçoit pour chaque événement émis l'état de l'aggregate et un événement et retourne le nouvel état de l'aggregate.
Ici, la fonction de décision execute est appelée et effectue quelques vérifications métier grâce aux arguments qui lui sont fournis, à savoir (l'état de l'aggregate (l'état de la partie, donc) et la commande UnitStartsExploitingDeposit :
- est-ce que l'unité choisie a bien accès au gisement demandé ?
- est-ce que ce type d'unité est capable d'exploiter ce type de gisement ?
- est-ce qu'elle n'est pas déjà en train d'exploiter ce gisement ?
Si tout va bien, la fonction retourne un seul événement UnitStartedExploitingDeposit, avec quelques informations (unit_id, deposit_id, timestamp).
Cet événement est alors ajouté de manière permanente et immuable dans l'historique de notre application.
Ensuite, la fonction de mutation apply est exécutée : elle reçoit à son tour l'état de l'aggregate ainsi que l'événement UnitStartedExploitingDeposit. On modifie l'état de la partie.
Cette architecture (l'event sourcing) est complexe mais apporte de nombreux avantages dans le cas d'un jeu :
- en terme de compréhension : il est facile de visualiser les transitions entre un état et un autre puisque ça ne passe que par les fonctions de mutation ;
- de tracabilité : on sait exactement ce qui se passe (et s'est déjà produit) dans l'application, on peut même ajouter des fonctionnalités a posteriori (analyse statistiques, trophées, etc.) ;
- de débogage : là aussi, si un bug survient, on peut remonter l'historique des événements pour savoir où ça a cloché, il suffit de voir quel élément de code a émis l'événement foireux (ou n'a pas émis l'événement attendu) ;
En plus de l'event sourcing, CQRS (dont on parlera plus tard) apporte également son lot d'avantages et d'inconvénients. Un autre article traitera des avantages et inconvénients de l'architecture CQRS/ES.
L'état de la partie, c'est une structure de données qui contient tout ce dont on a besoin pour la faire évoluer et prendre les décisions à la réception des commandes suivantes.
Dans le cas de l'aggregate Game de Seelies, il s'agit d'une map avec quelques clés, j'y reviendrai un autre jour.