Décembre 2016

Volume 31, numéro 13

Cet article a fait l'objet d'une traduction automatique.

À la pointe - Réécrire un système CRUD avec des événements et CQRS

Par Dino Esposito

Dino EspositoLe monde de classique création, lecture, mise à jour, les systèmes de suppression (CRUD) basé sur une base de données relationnelle et complété avec des segments de logique métier, parfois enfouie dans les procédures stockées et de mises en cage parfois dans les composants de boîte noire. Au cœur de ces boîtes noires sont les quatre opérations de CRUD : création de nouvelles entités, lecture, mise à jour et de suppression. À un niveau suffisamment élevé d’abstraction, c’est tout, n’importe quel système d’est, dans une certaine mesure, CRUD. Les entités peuvent être parfois très complexe et prendre plus la forme d’un agrégat.

Dans la conception (DDD), un agrégat est un cluster professionnelles d’entités avec un objet racine. Par conséquent, création, mise à jour ou suppression d’une entité même peut être soumise à plusieurs règles d’entreprise complexes. Même lire l’état d’un agrégat est généralement problématique, principalement pour des raisons de l’expérience Utilisateur. Le modèle qui convient pour modifier l’état du système n’est pas nécessairement le même modèle fonctionne pour présenter des données aux utilisateurs dans tous les cas d’usage.

Le niveau d’abstraction de CRUD être redirigé vers le plus élevé entraîne directement séparant ce qui modifie l’état d’un système à partir de ce que retourne simplement une ou plusieurs vues de celui-ci. Ceci est l’essence brute de commande et des requêtes répartition (CQRS), à savoir la séparation claire des responsabilités de commande et de la requête.

Toutefois, il est plus à ce que les développeurs et architectes logiciels doivent prendre en compte. L’état du système est modifié à la pile de commande et, concrètement qu’où agrégats sont créés ; C’est également où les mêmes fonctions d’agrégation sont mis à jour et supprimées ultérieurement. Et c’est précisément le point de repenser.

Conserver l’historique est essentielle pour presque n’importe quel système logiciel. Comme logiciel est écrite pour prendre en charge d’entreprise en cours, l’apprentissage du passé est essentiel pour deux raisons : pour éviter de manquer une seule chose qui se sont produits et pour améliorer les services aux clients et aux employés.

Dans le 2016 mai (msdn.com/magazine/mt703431) et juin 2016 (msdn.com/magazine/mt707524) consacre cette rubrique, j’ai présenté des façons d’étendre le CRUD classique pour un historique CRUD. Dans mon 2016 août (msdn.com/magazine/mt767692) et octobre 2016 (msdn.com/magazine/mt742866) des colonnes, au lieu de cela, j’ai présenté un modèle d’événement-commande-Saga (ECS) et une infrastructure mémento FX (bit.ly/2dt6PVD) en tant que blocs de construction d’une nouvelle façon d’exprimer la logique métier qui répond aux besoins quotidiens.

Dans cette colonne et la suivante, j’aborde les deux avantages susmentionnés de conserver l’historique dans un système en réécrivant une application de démonstration de réservation (le même que j’utilisais dans les colonnes de mai et juin) avec la CQRS et événements d’approvisionnement.

Perspectives

Mon exemple d’application est un système de réservation interne pour les salles de réunion. Le cas d’usage principal est un utilisateur connecté qui fait défiler un calendrier et un ou plusieurs emplacements sur une pièce donnée de la documentation. Le système gère les entités, telles que la salle, RoomConfiguration et prise en compte et, comme vous pouvez l’imaginer, Conceptuellement, l’ensemble de l’application est sur l’ajout et la modification des configurations et les salles (autrement dit, lorsque l’espace est ouvert pour la réservation et la longueur d’emplacements uniques) et l’ajout, mise à jour, d’annulation de réservations. Figure 1 offre un aperçu des actions que les utilisateurs du système sont en mesure d’effectuer et comment ils amèneront conçues dans un système CQRS en fonction du modèle ECS.

Actions de l’utilisateur et la conception du système
Figure 1 Actions de l’utilisateur et la conception du système

Un utilisateur peut entrer une nouvelle réservation, déplacer et annuler et même vérifier dans la salle pour que le système sache que l’espace réservé est réellement utilisé. Le flux de travail derrière chaque action est gérée dans une saga et la saga est une classe définie dans la pile de commande. Une classe saga comprend des méthodes de gestionnaire, chacun traitant une commande ou un événement. Placer une réservation (ou en déplaçant une réservation existante) est une question de l’envoi d’une commande vers la pile de la commande. En règle générale, en exécutant un push d’une commande peut être aussi simple que d’appeler directement la méthode correspondante de la saga ou peut accéder via les services d’un bus.

Pour conserver l’historique, vous devez suivre au moins tous les effets d’entreprise de commandes traitées. Dans certains cas, vous pouvez également suivre les commandes d’origine. Une commande est un objet de transfert de données transportant certaines des données d’entrée. Un effet de professionnels de l’exécution d’une commande via une saga est un événement. Un événement est un objet de transfert de données pour poursuivre les données qui décrit en détail l’événement. Les événements sont enregistrés dans un magasin de données spécifique. Il n’existe aucune des contraintes strictes sur la technologie de stockage à utiliser pour les événements. Il peut être un système de gestion simple de la base de données relationnelle (SGBDR) ou il peut être un magasin de données NoSQL. (Reportez-vous à la colonne d’octobre pour la configuration de MementoFX et RavenDB et bus).

Coordination des commandes et les requêtes

Supposons qu’un utilisateur place une commande de réserver un emplacement sur une pièce donnée. En cas d’ASP.NET MVC, le contrôleur obtienne les données publiées et place une commande sur le bus. Le bus est configuré pour reconnaître les quelques sagas et chaque saga déclare les commandes (et/ou des événements) s’intéresse à gérer. Par conséquent, le bus distribue le message à la saga. L’entrée de la saga est les données brutes tapés par les utilisateurs dans les écrans de l’interface Utilisateur. Le Gestionnaire de la saga est responsable de la réception de transformer des données dans une instance d’un agrégat qui est cohérent avec la logique métier.

Supposons que l’utilisateur clique pour livre, comme indiqué dans Figure 2. La méthode de contrôleur déclenchée par le bouton reçoit l’ID de l’espace, le jour et l’heure et le nom d’utilisateur. Le gestionnaire saga doit faire de ces évaluations une mesure d’agrégation réservation pour gérer la logique métier attendu. Logique métier raisonnablement pour résoudre les problèmes dans la zone des autorisations, les priorités, les coûts et même ordinaire d’accès concurrentiel. Toutefois, au minimum la méthode saga doit créer un agrégat de réservation et l’enregistrer.

La réservation d’une salle de réunion dans le système de l’exemple
Figure 2 la réservation d’une salle de réunion dans le système de l’exemple

À première vue, l’extrait de code dans Figure 3 ne diffère pas un CRUD simple, à l’exception de son utilisation d’une fabrique et la propriété Repository en attente. L’effet combiné de fabrique et de référentiel écrit dans les magasins d’événement configuré tous les événements déclenchés dans l’implémentation de la classe de réservation.

Figure 3 Structure d’une classe Saga

public class ReservationSaga : Saga,
  IAmStartedBy<MakeReservationCommand>,
  IHandleMessages<ChangeReservationCommand>,
  IHandleMessages<CancelReservationCommand>
{
   ...
  public void Handle(MakeReservationCommand msg)
  {
    var slots = CalculateActualNumberOfSlots(msg);
    var booking = Booking.Factory.New(
      msg.FullName, msg.When, msg.Hour, msg.Mins, slots);
    Repository.Save(booking);
  }
}

Au final, le référentiel n’enregistre pas un enregistrement avec l’état actuel d’une classe de réservation, où les propriétés sont d’une certaine façon mappée aux colonnes. Il enregistre simplement les événements commerciaux dans le magasin et en fin de compte à ce stade, vous savez exactement ce qui est arrivé à votre réservation (lorsqu’elle a été créée et comment), mais n’avez pas toutes les informations classiques prêtes à afficher à l’utilisateur. Vous savez ce qui est arrivé, mais vous n’avez rien prêt à afficher. Le code source de la fabrique est indiqué dans Figure 4.

Figure 4 Code de Source de la fabrique

public static class Factory
{
  public static Booking New(string name, DateTime when,
    int hour, int mins, int length)
  {
    var created = new NewBookingCreatedEvent(
      Guid.NewGuid(), name.Capitalize(), when,
      hour, mins, length);
    // Tell the aggregate to log the "received" event
    var booking = new Booking();
    booking.RaiseEvent(created);
    return booking;
  }
}

Aucune propriété de l’instance nouvellement créée de la classe de réservation n’est couvertes dans la fabrique, mais une classe d’événements est créée et remplie avec les données réelles à stocker dans l’instance, y compris le nom du client et l’ID unique qui suivra définitivement la réservation dans tout le système en majuscules. L’événement est passé à la méthode RaiseEvent, partie de l’infrastructure MementoFX, car il est la classe de base de tous les agrégats. RaiseEvent ajoute l’événement à une liste interne qui passe par le biais du référentiel lorsque l’instance de l’agrégat « enregistrer ». J’ai utilisé le terme « enregistrer », car c’est ce qui se trouve, mais le placer entre guillemets pour souligner qu’il est un autre type d’action dans un CRUD classique. Le référentiel enregistre l’événement qu’une réservation a été créée avec les données spécifiées. Plus précisément, le référentiel enregistre tous les événements enregistrés dans une instance de l’agrégat pendant l’exécution d’un workflow métier, à savoir une méthode de gestionnaire saga, comme illustré dans Figure 5.

L’enregistrement des événements et état de l’enregistrement
Figure 5 l’enregistrement des événements et état de l’enregistrement

Mais les événements commerciaux résultant d’une commande de suivi n’est pas suffisante.

Dénormalisation des événements à la pile de requête

Si vous examinez CRUD à travers l’objectif de conserver l’historique des données, vous consultez que créer et de lire les entités n’affectent pas l’historique, mais ne peut pas de même pour la mise à jour et suppression. Un récepteur d’événements est en mode append-only et mises à jour et suppressions sont uniquement des événements liés aux agrégats mêmes. Avoir une liste d’événements pour un regroupement donné, cependant, vous indique tout sur l’historique, à l’exception de l’état actuel. Et l’état actuel est juste ce qu’il faut présenter aux utilisateurs.

Voici où denormalizers tenir dans. Un denormalizer est une classe générée comme une collection de gestionnaires d’événements, comme ces enregistré dans le récepteur d’événements. Vous enregistrez un denormalizer avec le bus et le bus distribue des événements chaque fois qu’il obtient un. L’effet net est qu’un denormalizer écrit pour écouter les événements d’une réservation a la possibilité de réagir chaque fois qu’un est déclenché.

Un denormalizer obtienne les données dans l’événement et est tout ce que vous avez besoin pour faire, par exemple, en conservant une base de données relationnelle de requête simple synchronisé avec les événements enregistrés. La base de données relationnelle (ou une banque NoSQL ou un cache, si ce n’est plus facile ou plus avantageux d’utiliser) appartient à la pile de requête et ses API n’est pas un accès à la liste d’événements stockée. De plus, vous pouvez avoir plusieurs denormalizers création d’affichages ad hoc des événements bruts. (J’explorerai plus en profondeur cet aspect dans mon prochain article.) Dans Figure 1, le calendrier à partir de laquelle un utilisateur sélectionne un emplacement est rempli à partir d’une base de données relationnel simple qui est conservé par l’action d’un denormalizer synchronisé avec des événements. Consultez la page Figure 6 pour le code de la classe denormalizer.

Structure de la figure 6 d’une classe Denormalizer

public class BookingDenormalizer :
  IHandleMessages<NewBookingCreatedEvent>,
  IHandleMessages<BookingMovedEvent>,
  IHandleMessages<BookingCanceledEvent>
{
  public void Handle(NewBookingCreatedEvent message)
  {
    var item = new BookingSummary()
    {
      DisplayName = message.FullName,
      BookingId = message.BookingId,
      Day = message.When,
      StartHour = message.Hour,
      StartMins = message.Mins,
      NumberOfSlots = message.Length
    };
    using (var context = new MfxbiDatabase())
    {
      context.BookingSummaries.Add(item);
      context.SaveChanges();
    }  }
  ...
}

Pour Figure 5, denormalizers fournissent un CRUD relationnelle uniquement à des fins de lecture. La sortie de denormalizers est souvent appelée « modèle de lecture ». Entités dans le modèle de lecture ne correspondent généralement pas les agrégats utilisés pour générer des événements comme ils sont principalement pilotés par les besoins de l’interface Utilisateur.

Mises à jour et suppressions

Supposons que l’utilisateur souhaite déplacer d’un emplacement réservé précédemment. Une commande est placée avec tous les détails du nouvel emplacement et une méthode saga prend en charge de l’écriture d’un événement transféré pour la prise en compte donné. La saga a besoin de récupérer l’agrégat et a besoin dans l’état mis à jour. Si denormalizers venez de créer une copie de l’état de l’agrégat (par conséquent, le modèle de lecture presque coïncide avec le modèle de domaine) relationnelle, vous pouvez obtenir l’état de mise à jour à partir de là. Sinon, vous créez une nouvelle copie de l’agrégat et exécuter tous les événements enregistrés. À la fin de la relecture, l’agrégat est dans l’état la plus à jour. Relecture d’événements n’est pas une tâche à effectuer directement. Dans MementoFX, vous obtenez un agrégat avec une ligne de code dans un gestionnaire saga mis à jour :

var booking = Repository.GetById<Booking>(message.BookingId);

Vous appliquez ensuite à l’instance de toute logique métier que vous avez besoin. La logique métier génère des événements et les événements sont conservés dans le référentiel :

booking.Move(id, day, hour, mins);
Repository.Save(booking);

Si vous utilisez le modèle de domaine et suivez les principes de conception pilotée par Domaine, la méthode Move contient tous les la logique de domaine et des événements. Sinon, vous exécutez une fonction avec une logique métier et déclencher des événements pour le bus directement. En liant un autre gestionnaire d’événements pour le denormalizer, vous pouvez mettre à jour le modèle de lecture.

L’approche n’est pas différente pour l’annulation d’une réservation. L’événement de l’annulation d’une réservation est un événement métier et doit être suivi. Cela signifie que vous pouvez souhaiter avoir une propriété booléenne dans l’agrégat pour effectuer une suppression logique. Cependant, dans le modèle de lecture, suppression peut être totalement physique selon que votre application va interroger le modèle de lecture pour les réservations annulées. Un effet secondaire intéressant est que vous pouvez toujours régénérer le modèle de lecture en relisant les événements à partir du début ou un point de récupération. Il suffit pour créer un outil qui utilise l’API de magasin d’événements pour lire les événements et appeler directement les denormalizers ad hoc.

À l’aide de l’API de récepteur d’événements

Examinez la sélection de la liste déroulante Figure 2. L’utilisateur souhaite étendre la prise en compte tant que possible à partir de l’heure de début. La logique métier de l’agrégat doit pouvoir quelque chose et pour cela, il doit accéder à la liste des réservations dans l’heure de début au plus tard le même jour. Qui n’est pas un problème dans un CRUD classique, mais MementoFX vous permet d’interroger des événements, ainsi :

var createdEvents = EventStore.Find<NewBookingCreatedEvent>(e =>
  e.ToDateTime() >= date).ToList();

L’extrait de code retourne une liste d’événements NewBookingCreated suivant l’heure donnée. Toutefois, il n’existe aucune garantie que la réservation créée est toujours active et n’a pas été déplacée vers un autre emplacement. Vous devez vraiment obtenir l’état de mise à jour de ces agrégats. L’algorithme est à vous. Par exemple, vous pouvez filtrer les données dans la liste des événements Created les réservations n’est plus actives et puis obtenez l’ID des réservations restantes. Enfin, vous vérifiez l’emplacement par rapport à celui que vous voulez étendre tout en évitant le chevauchement. Dans le code source de cet article, j’ai codé toute cette logique dans un service distinct (domaine) dans la pile de commande.

Pour résumer

À l’aide de la CQRS et événements d’approvisionnement n’est pas limitée aux systèmes particuliers avec des exigences haut de gamme pour la concurrence, l’évolutivité et performances. Avec l’infrastructure de disposition qui vous permet de travailler avec des agrégats et des flux de travail, les systèmes de CRUD aujourd'hui peuvent être réécrit de façon apporte de nombreux avantages. Ces avantages incluent :

  • Conserver l’historique des données
  • Un moyen plus efficace et souple d’implémenter des tâches métier et de modification des tâches afin de refléter les changements métier avec effort limitée et risque de régression
  • Étant donné que les événements sont faits immuables, ils sont très facile à copier et lecture en double et même des modèles peuvent être régénérées par programmation à

Cela signifie que le modèle ECS (ou CQRS/ES tel qu’il est parfois appelée) a un énorme potentiel pour l’évolutivité. Encore plus, le framework MementoFX ici est utile, car elle simplifie les tâches courantes et offre l’abstraction d’agrégation pour la programmation plus facile.

MementoFX exécute un push d’une approche orientée sur la conception pilotée par Domaine, mais vous pouvez utiliser le modèle ECS avec d’autres infrastructures et autres paradigmes telles que le paradigme fonctionnel. Il existe un autre avantage et probablement le plus approprié. Je vais vous parler il dans mon prochain article.


Dino Espositoest l’auteur de « Microsoft .NET :  Conception d’Applications pour l’entreprise » (Microsoft Press, 2014) et « Applications Web modernes avec ASP.NET » (Microsoft Press, 2016). Un développeur technique pour les plateformes .NET et Android chez JetBrains, dans le monde entier manifestations du secteur, Esposito partage sa vision du logiciel à software2cents.wordpress.com et sur Twitter : @despos.

Merci à l'expert technique Microsoft suivant d'avoir relu cet article : Andrea Saltarello