Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

Event-sourcing avancé avec Scala

287 vues

Publié le

Par Jonathan Winandy & Valentin Kasas.

Publié dans : Technologie
  • Soyez le premier à commenter

  • Soyez le premier à aimer ceci

Event-sourcing avancé avec Scala

  1. 1. Event-sourcing avancé avec Scala PSUG - Jonathan Winandy & Valentin Kasas 1
  2. 2. Agenda ● Eventsourcing : qu’es aquò ? ● Patterns et anti-patterns ● Streaming et métadonnées ● Typage et sérialisation 2
  3. 3. Qui sommes-nous ? Jonathan Winandy @ahoy_jon ● Data engineer / Hardware destroyer ● Eventsourcer since before it was cool ● Scala/Java/Clojure ● CYM ● hipster Valentin Kasas @ValentinKasas ● Web engineer / homme-orchestre ● Eventsourcer since 2013 ● Scala :: scala :: HNil ● Freelance ● dandy 3
  4. 4. Eventsourcing : définitions 4
  5. 5. EventSourcing : définition ● Une idée maîtresse : prendre comme source de vérité la succession des événements qui surviennent dans un système ● Plutôt que s’appuyer sur un représentation muable du présent, on s’appuie sur l’enregistrement des événements passés et immuables. 5
  6. 6. RDBMS vs EventSourcing Source http://docs.geteventstore.com/introduction/img/transactional-model-with-delete.png VS Source https://martin.kleppmann.com/2015/03/insideout-06.png 6
  7. 7. RDBMS vs EventSourcing 7
  8. 8. EventSourcing, au niveau des API 1/2 trait Stream[M[_]] { val name: String def append(event: Event, events: Event*): M[WriteCompleted] def read(position: Int): M[Event] } case class Event(evenType: String, id: UUID, body: Content, metadata: Content) case class WriteCompleted(start: Int, end: Int) 8
  9. 9. EventSourcing, au niveau des API 2/2 trait Stream[M[_]] { def readPage(fromPosition: Int, pageSize: Int, readDirection: ReadDirection = Forward): M[Page] def subscribeTo(observer: SubscriptionObserver): Closeable def subscribeToFrom(observer: SubscriptionObserver, position: Int): Closeable } case class Page(events: List[Event], fromPosition: Int, nextPosition: Int, readDirection: ReadDirection) trait SubscriptionObserver { def onLiveProcessingStart(subscription: Closeable) def onEvent(event: Event, subscription: Closeable) def onError(e: Throwable) def onClose() } 9
  10. 10. Modèle basique 95 96 97 98 append append append read(95) read(96) read(97) 10
  11. 11. Invariant(s) ∀p ∈ ℕ, et pour une définition de ‘=‘ x := read(p) y := read(p) ‘x ≠ ⊥’ ⇒ x = y Plus simplement : La cacheabilité est infinie. Une fois qu’un élément est disponible à une position (p), cet élément sera identique (=) pour toujours. 11
  12. 12. EventSourcing : les avantages Conserver l’intégralité de l’historique d’un système offre de nombreux avantages : ● Plus proche de la réalité (perception), ● Auditabilité, ● Observabilité de la nouveauté, ● Simplicité de consommation, ● Réinterprêtation de l’historique (BI for “Free”). ● Facilite (beaucoup) CQRS Decision Apply commande état event Read Model Read Model Read Model 12
  13. 13. EventSourcing : les inconvénients L’eventsourcing présente aussi quelques inconvénients : ● Consommation d’espace disque plus importante (et en constante augmentation), ● Démarrage potentiellement plus long (il faut relire tout l’historique), ● Difficile de changer le schéma des événements (<= immuabilité). ● Découpage en streams, et opérations sur plusieurs streams. ● Multiplication des états (read models, Monoid[Event]). ● Gestion asynchrone. 13
  14. 14. Patterns et anti-patterns 14
  15. 15. Patterns ● Algébre d’événement (List[Event] à la place de A * List[Event]), ● Génération d’id non coordonnée => UUID, ● Quantification de la donnée, ● Capture des réglès métier complexes, et décisions peu reproductibles. ● Corrélation et causation ● Sagas 15
  16. 16. Anti-Patterns ● CRUD Sourcing ● Command Sourcing ● Make-My-Sum-Type Grow 16
  17. 17. Streaming, métadonnées et metastreams 17
  18. 18. Pourquoi ajouter des métadonnées ? ● Les events contiennent la donnée métier, mais on a aussi besoin de données techniques pour : ○ Expliciter le contexte d’écriture (instance, saga, commande, position dans la commande). ○ Périniser la donnée (schéma à l’écriture). ○ Tracer la provenance (de qui, de la part de). ○ Séparer la donnée entre différent environnement partageant la même infrastructure. 18
  19. 19. Quels types de métadonnées ? ● Métadonnées des événements ○ Corrélation & causation ○ Informations de type (schéma, on y reviendra) ○ etc... ● Méta-streams (ou streams de méta-events) d’informations techniques sur le système ○ Instances ○ Schémas 19
  20. 20. Types et sérialisation 20
  21. 21. Pourquoi/comment typer les streams ? ● Sans typage, pas d’albèbre, pas moyen d’écrire ○ state = events.foldLeft(initial)(_ apply _) ● Problème : comment conserver le type des “vieux” events ? ● Quand le schéma des events change, on doit pouvoir ○ Upcaster les anciens events dans le nouveaux schéma (pour pouvoir démarrer une nouvelle instance sur les anciennes données) ○ Downcaster les nouveaux events (pour que les anciennes instances continuent de fonctionner pendant la mise à jour) 21
  22. 22. API typée : définition case class Event[E](event:E, id:UUID, metadata:Metadata) trait TypedStream[M[_], E] { implicit val coproductEvidence: Generic[E] implicit val serializableEvidence:AkiraSerializable[E] val name: String @@ StreamId def append(event: Event[E], events: Event[E]*): M[WriteCompleted] def read(position: Int): M[Event[E]] } 22
  23. 23. API typée : usage sealed trait MQTTEvent case class Disconnected(clientID: String) extends MQTTEvent case class Published(clientID: String, payload: Seq[Byte], topicName: String) extends MQTTEvent val mqttStream:TypedStream[Id,MQTTEvent] = ___ mqttStream.append(___) 23
  24. 24. Comment sérialiser les événements ? L’EventSourcing nécessite une bonne sérialisation, car les événements vont être écrits et consommés par : ● Beaucoup de programmes (dans des langages différents). ● Beaucoup d’intermédiaires. ● Pendant plusieurs années. Une bonne sérialisation est : ● Extensible. ● Auto descriptible. ● Peut être lue et écrite de manière générique et indépendante. 24
  25. 25. Comment sérialiser les messages ? Pour l’instant, nous utilisons Avro 1.8.x avec l’encodeur JSON, mais c’est un détail d’implémentation. 25
  26. 26. ● Conserver les schémas (passés et actuels) de tous les types d’événements ○ Dans les métadonnées de chaque événement, placer une référence vers le schéma dudit événement ○ Dans un métastream dédié, stocker une représentation générique de chaque schéma (sous la forme d’événements SchemaCreated ● Il faut aussi conserver le schéma des métadonnées ● Au démarrage, on utilise le méta stream des schémas pour initialiser le Schéma Registry ● Problème de l’oeuf et de la poule ○ quel schéma pour les événements du stream schémas ? ○ sur quel stream les enregistre-t-on ? Schema registry 26
  27. 27. Séquence de démarrage schemas meta stream-xyz SchemaCreated <Metadata> hash : “ab12ce9” Schema : { “typ.. SchemaCreated <InstanceUp> hash : “56f3e05” Schema : { “typ.. InstanceUp data : nodeXYZ meta : 56f3e05 SchemaCreated <SmthHappened> hash : “76b4ed0” Schema : { “typ.. SmthHappened data : {‘d’:”123”} meta : 76b4ed0 SmthHappened data : {‘d’:”abc”} meta : 76b4ed0 SchemaCreated <SchemaCreated> hash : “2d163b9” Schema : { “typ.. Application démarrée 27
  28. 28. Séquence de démarrage ● Au démarrage, on initialise le Schema Registry ● Comme le méta stream schémas est vide on enregistre les schémas “par defaut” : ○ Le schéma de SchemaCreated ○ Le schéma des métadonnées ● On veut ensuite enregistrer le démarrage d’une nouvelle instance (InstanceUp) ○ Le schéma d’InstanceUp n’existe pas, il faut l’enregistrer ○ On émet un InstanceUp dans le méta schéma ● L’application est démarrée ● Avant d’enregistrer un nouvel event, on vérifie que son schéma est présent dans le registry, et on l’enregistre si besoin 28
  29. 29. Evolution des types A + BA A + B + C auto auto BA f: A → B Record with aliased fieldsRecord auto wideningnarrowing A × BA × B × C A auto auto projectionadd field 29
  30. 30. Evolution des types A × BA × B × C A auto auto projectionadd field A × B × Czero auto A × B × (C + Czero ) auto auto auto add field with default value auto Czero <: C A @@ B auto A auto A as logical type A @@ B Type level Serialization level A @@ B <: A 30
  31. 31. Evolution des types dans le temps A1 A2 A3 autoauto auto × A4 ? f B1 stream-xyz : A1 + B1 A2 + B1 A3 + B1 autoauto A4 ? + B1 A’4 + A3 + B1 Gérer la compatibilité entre A3 et A4 . Poser A4 à coté de A3 . Deux solutions : auto 31
  32. 32. Conclusion ● L’EventSourcing c’est bon, mangez-en ! ● Les principaux problèmes pénibles ont des solutions ● Coming soon : Akira (https://github.com/vil1/akira) 32
  33. 33. MerciPour les questions, demandez Sam 33

×