Archive

Archive for février 2014

Retours d’expérience sur CQRS

février 5, 2014 Laisser un commentaire

Nous sommes actuellement sur la phase finale de réalisation d’un « extranet » qui interagit fréquemment avec une base de données distante. Cette interaction étant forte (l’application était d’ailleurs une appli Oracle Forms, mais à refondre sous DotNet); il fallait au départ trouver le bon « pattern » qui devra s’adapter à de telle situation, tout en facilitant par ailleurs la vie des développeurs. On était donc parti sur le pattern CQRS (Commands and Queries Responsibility Segregation). Pour plus d’informations, vous pouvez googler (:-P) ou par exemple visiter ce billet de Martin Fowler.

Ce qui va suivre va juste récapituler brièvement nos retours d’expérience sur ce pattern :

CQRS est plus ou moins facile à mettre en place

Et oui, vous suivez habituellement un tutoriel (comme celui-ci), et la suite n’est qu’adaptation selon vos besoins fonctionnels. En résumé : il s’agit :

– d’écrire le code « moteur » pour les commandes : on crée en ce sens un objet CommandInvoker qui possède une méthode Invoke() (ou Execute(), c’est selon le feeling), et qui va donc pouvoir exécuter une commande en appelant cette méthode Invoke().

– optionnellement, de décrire des interfaces pour les commandes et pour les « queries ». Et pourquoi pas écrire des classes abstraites correspondantes.

– d’écrire au fur et à mesure les objets Command et les objets Query. Rappelons qu’un objet Command consisterait à exécuter des traitements pour modifier des données (insérer, modifier, supprimer), tandis qu’un objet Query aura pour rôle de lire des données (filtrer, trier, etc.)

– et enfin, de coder les Handlers des objets Command. A chaque objet Command, on peut avoir un ou plusieurs codes de traitements qu ‘on appelle « CommandHandlers ».

=> pour notre application, on a même pu concevoir un générateur de codes qui crachent les objets Command/Query/CommandHandler, en pointant sur la base de données, et moyennant d’éventuelles configurations…

CQRS se marie bien avec une architecture basée sur de l’IoC

En partant du principe que chaque objet Command ou Query n’est qu’une implémentation d’un interface ICommand ou IQuery; on peut bien s’amuser à faire abstraction de ces objets au code utilisateur. Et la création des instances concrètes se feront pendant l’exécution, grâce à un moteur d’IoC (entre autres Castle Windsor). C’est exactement comme ce que font les adeptes des architectures orientées « Service », dans lesquelles on fait abstraction des contrats de services, en exposant au code utilisateur des interfaces.

Parfois, choisir entre Command ou Query n’est pas si simple…

Par définition : un objet Command doit exécuter des traitements pour modifier les données, tandis qu’un objet Query fait le travail inverse : lire les données en préparant la présentation de celles-ci. Or, souvent, on peut tomber sur des cas où l’on veut exécuter un traitement (assez complexe d’ailleurs), et que ce traitement doit ramener ensuite un ensemble de résultats, ou carrément des données. En construisant un objet Command : l’objet qui en résulte est plus ou moins « dénormalisé » car il faut créer donc une propriété du Command, qui aura pour essence de véhiculer les données en retour des traitements. Par contre, en choisissant d’implémenter un Query : d’une part, l’objet « SearchModel » (qui regroupe les critères de requêtes par exemple) devient rapidement compliqué, et d’autre part; un objet Query ne doit pas être utilisé pour des traitements qui provoqueront des « charges » importantes, car c’est l’une des principales raisons de la séparation des commands et des queries, donc du pattern CQRS en lui-même.

Comment ordonnancer les « Handlers » pour un Command qui peut en avoir plusieurs ?

L’un des points forts du pattern CQRS, et surtout de l’utilisation des Command, est le fait de pouvoir rattacher des « objets d’exécutions » (handler) à chaque objet Command.

Par exemple : vous voulez une suppression en cascade qui s’effectue au niveau « métier » : vous avez donc un objet Command (DeleteCommand) qui est sensé provoquer cette suppression. Cet objet contient les paramètres qui vont restreindre sur le (ou les) enregistrement(s) à supprimer. Supposons ensuite que l’objet métier (entre autres un POCO par exemple) à supprimer possède un ou plusieurs autres objets métiers enfants. L’idée serait de rattacher des handlers pour l’objet DeleteCommand, de telle sorte que : un Handler va supprimer l’objet parent, alors que les autres handlers auront la charge de supprimer les objets enfants. Mais ensuite, nous avons besoin de spécifier que les suppressions des objets enfants doivent se faire AVANT celle de l’objet parent, sinon on aura un souci d’intégrité au niveau de la base de données. Comment établir cet ordonnancement dans l’exécution des handlers ? A cela, il n’y a pas de réponse unique, et ce n’est pas bien décrit dans les différents tutos ou introductions à CQRS… Dans notre cas, on a mis une convention de nommage sur les handlers (avec un suffixe numérique), et l’ordonnancement se fait donc en triant selon ce suffixe. On peut aussi rajouter une propriété « Order » dans chaque objet Command ou dans la classe abstraite dont toutes les Command vont hériter.

On a une sensation de redondance entre un objet Command, un modèle de vue et un objet POCO

Dans un formulaire classique : l’interface est mappé à un modèle de vue. En cliquant sur un bouton de traitement (enregistrer par exemple) : ce modèle de vue est envoyé vers le serveur d’application (ou vers le code côté serveur) qui contient donc un objet Command spécifique au traitement demandé. Ce command va contenir à peu près les mêmes informations contenues dans le modèle de vue. Ensuite, avant de s’exécuter au niveau de la base : le Handler de l’objet Command va transformer les informations contenues dans cet objet Command pour les véhiculer vers un modèle POCO… Et enfin, c’est le modèle POCO qui va être manipulé par une couche d’accès aux données afin de lancer par exemple des requêtes SQL. Visiblement, la plupart des informations véhiculées sont les mêmes, mais elles passent par trois objets différents !

Conclusion

Comme tout autre pattern d’architecture; le CQRS s’adapte à certaines situations, mais pas à toutes. A mon avis (et cela a peut-être été dit X fois), il faut le privilégier : si on veut vraiment séparer les domaines d’exécution des commands et des queries. Ce qui suppose que les interactions vers la base de données (écriture) sont beaucoup plus intenses que celles partant de la base de données (lecture).