Zehreen Soogun / zehreen@extension-interactive.com
Jihad Sahebdin / jihad@extension-interactive.com
S y m f o n y 3
31/03/2017
C Q R S e t E v e n t s o u r c i n g
2
Who Am I
SOOGUN Bibi Zehreen
¬ Chef de Projet Technique
¬ Extension Interactive
Je suis #Zehreen
Find me on :-
https://twitter.com/bzehreens
https://mu.linkedin.com/in/bibi-zehreen-soogun-53115190
3
Speakers / Présentation intervenants
Zehreen
¬ CQRS
¬ ES
Jihad
¬ Cas réel
¬ MEP CQRS et ES dans un projet SF 3.
4
1. CQRS
1. Introduction
2. Avantages
3. Désavantages
4. Utilisation
2. Event Sourcing
3. Architecture
4. Conclusions
5
CQRS / Introduction
Command
Query
Responsibility
Segregation
Pioneer de CQRS
¬ Greg Young
Origine de CQRS
¬ Bertrand Meyer - CQS
6
CQRS / Command
Contexte boutique : Je valide ma commande
Contexte Symfony : Je lance une commande via console
¬ php bin/console c:c
7
CQRS / Command
Nom d’une tâche
¬ Temps impératif
Données requises pour effectuer la tâche
a.k.a
¬ Mutators
¬ Modifiers
Contrast avec CRUD
¬ Point de données
openCustomerAccountCommand
8
CQRS / Query
Types de requêtes classiques
¬ Insert
¬ Select
¬ Update
¬ Delete
ReadOnly
¬ Récupération des données
Constraste avec CRUD
¬ TOUTES les opérations VS Lecture seule
9
CQRS & CQS
Similarités
¬ Utilisation commandes (modification) et requêtes (récupération)
 Différence
¬ Write Model
¬ Read Model
10
CQRS / Avantages
Performance
¬ Plus de lecture que d’écriture
Evolution
11
CQRS / Désavantages
Complexité
Réduction de productivité
12
CQRS / Utilisation
Environnement collaborative
Données en simultanés
Applications haute performance
Système complèxe
¬ Bounded Context
BankSystem
Pas nécessaire d’appliquer CQRS dans
l’intégralité d’un système
13
CQRS / A éviter
Système simple
Système statique
Système non-collaboratif
Système avec zéro logique métier
14
CQRS / Faire notre choix
Découpler Read et Write / Coûteux
Réfléchir en terme de DDD et non CRUD
15
1. CQRS
1. Introduction
2. Avantages
3. Désavantages
4. Utilisation
2. Event Sourcing
3. Architecture
4. Conclusions
16
Event Sourcing
Evènements
¬ a.k.a Domain Event
CustomerAccountOpened
Pas de suppréssion
17
CQRS + Event Sourcing
18
1. Architecture
1. Aperçu
2. Mise en place
3. Bonnes pratiques
4. Sécurité
5. CORS
19
Aperçu
Application de type API/UI
Enregistrement dans 2 bases de données
¬ Write model
¬ Read model
La UI envoie des requêtes sur l’API et récupère les
données depuis Firebase
UI
API Firebase
20
Le Domain Model
Système de gestion:
Une business unit emploie des salaries qui vont travailler
sur des contrats liés à des clients
Business Units
Salariés Contrats
Clients
21
1. Architecture
1. Aperçu
2. Mise en place
3. Bonnes pratiques
4. Sécurité
5. CORS
22
Architecture
23
Bounded context
Le Domain Model s’organise en morceaux
Chaque morceau correspond à:
¬ un métier
¬ Un composant métier
¬ Une problématique métier qu’il propose de solutionner
Morceau = Bounded Context
Exemple:
¬ Un context RH qui s’occupe de gérer les employés
¬ Un context Contrat qui s’occupe de gérer ses clients et ses
ressources
24
Bounded context / Composants autonomes
Un bounded context peut être composé d’un ou de
plusieurs composants autonomes.
Les composants apportent une solution aux
problématiques identifiés dans un bounded context.
Exemple:
¬ On va s’occuper de la gestion des business units dans le
composant Business Units management
25
Bounded context / Composants autonomes
3 bounded contexts:
¬ Business units
¬ Human Resource
¬ Contracts
Business Unit
Human
Resource
Contract
26
Bounded context / Composants autonomes
Fonctionnement
¬ Un composant reçoit des messages en provenance du
MessageBus:
- Soit par l’intermédiaire de ses CommandHandlers s’il s’agit de
commandes
- Soit par l’intermédiaire de ses ProcessManagers s’il s’agit
d’évènements
27
Bounded context / Structure
28
Commands
Commands:
¬ Les ordres données au Domain Model
¬ Sources diverses:
- Application web
- Application mobile
- Ligne de commandes
¬ Format json
¬ Construction d’un objet Command à partir des données
Domain
Model
Application
Web
Application
mobile
Ligne de
commande
29
Commands
Exemple:
¬ La classe HireEmployeeCommand
class HireEmployeeCommand
{
public $employeeId;
public $employerId;
public $employeeFirstName;
public $employeeLastName;
public $employeeCivility;
public $employeeJobTitle;
public $employeeEmail;
public $requestedBy;
}
30
Commands
Controller:
¬ HRManagementController
- hireEmployeeAction
use pathtoCommandingCommandsHireEmployeeCommand;
class HRManagementController extends Controller
{
/**
* @Route("/hire-employee", name="hire_employee")
*/
public function hireEmployeeAction(Request $request)
{
$commandBus = $this->get('command_bus');
$hireEmployeeCommand = new HireEmployeeCommand();
//Populate command data
$commandBus->handle($hireEmployeeCommand);
return new Response("Employee hired");
}
}
31
SimpleBus bundle
http://simplebus.github.io/SymfonyBridge/
Auteur: Matthias Noback
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
...
new SimpleBusSymfonyBridgeSimpleBusEventBusBundle()
)
...
}
...
}
32
Command Handlers
HireEmployeeCommandHandler
¬ Handle
Service
hire_employee_command_handler:
class: pathtoCommandingCommandHandlersHireEmployeeCommandHandler
tags:
- {
name: command_handler,
handles: pathtoCommandingCommandsHireEmployeeCommand
}
namespace pathtoCommandingCommandHandlers;
class HireEmployeeCommandHandler
{
public function handle(HireEmployeeCommand $command)
{
33
Command Handlers
Reconstruction de l’agrégat à partir de son historique
Enregistrement des évènements
public function handle(HireEmployeeCommand $command)
{
$employeeId = $command->employeeId;
$history = $this->eventStore->getHistoryFor($employeeId);
$employeeAggregate = employeeAggregate::reconstituteFrom($employeeId, $history);
34
Command Handlers
On passe les données à l’agrégat
$employeeAggregate->hireEmployee(
$command->employeeId,
$command->employerId,
$command->employeeFirstName,
$command->employeeLastName,
$command->employeeCivility,
$command->employeeJobTitle,
$command->employeeEmail,
$command->requestedBy,
$command->requestedOn
);
35
Command Handlers
Enregistrement des évènements
Dispatch de l’évènement
foreach ($events as $event) {
$this->eventBus->handle($event);
}
$events = $this->eventStore->save($employeeAggregate);
36
Agrégats
Contient la logique du Domain model
Validation sémantique des commandes
Reçoit les commandes du MessageBus
Emet un ou plusieurs évènements relatant l’opération
qu’il vient d’executer
¬ employeeHiredEvent
37
Agrégats
Validation des commandes
¬ Un agrégat peut réfuser d’éxécuter une commande
- Exemple:
› Par exemple, si HRManagementAggregate reçoit la commande
HireEmployeeCommand pour une BU donnée, si la BU n’existe pas, il doit
renvoyer une exception
class HRManagementAggregate
{
public function hireEmployee($employeeId, $employerId, $employeeFirstName, $employeeLastName)
{
$this->isBusinessUnitIdValid($employerId);
$this->recordThat(new EmployeeHiredEvent($employeeId, $employerId, $employeeFirstName,
$employeeLastName));
}
private function isBusinessUnitIdValid($businessUnit)
{
if (!$businessUnit instanceof BusinessUnit) {
throw new BusinessUnitIdIsNotValidException('This business Unit is not valid');
}
return;
}
}
38
Domain Events
Domain Events
¬ Les évènements métiers produits par les aggrégats
¬ Exemple:
- L’évènement EmployeeHiredEvent émis par l’agrégat
HRManagementAggregate
namespace pathtoAggregatesDomainEvents;
class EmployeeHiredEvent
{
/**
* @Type("string")
*/
private $employeeId;
public function __construct($employeeId)
{
$this->employeeId = $employeeId;
}
public function getEmployeeId()
{
return $this->employeeId;
}
}
39
Bounded context / Récapitulatif
Récapitulatif:
¬ On a donc réussi à construire une commande à partir d’une
requête venant de l’extérieur
¬ Le command handler s’est chargé de le communiquer à
l’agrégat
¬ L’agrégat valide la commande, l’éxécute et génère un
évènement
¬ L’évènement est stocké dans l’event store puis dispatché a
l’event bus
40
Bounded context / Suite
L’évènement peut alors être:
¬ Propagé dans la UI par le biais des denormalizers
¬ Capté par les process managers.
41
Denormalizers
42
Denormalizers
Problème
¬ Données pas exploitable pour la UI
{
"employee_id":"4731ab33-1db8-4570-bc27-e3f4ec0f4feb",
"employer_id":"123a3256-2345-1243-a231-a123c1567b12",
"employee_first_name":"Jihad",
"employee_last_name":"Sahebdin",
"employee_civility":"M",
"employee_job_title":"Developer",
"employee_email":"jihad@sahebdin.com",
"requested_by":"f54d197b-9647-47f8-bfa6-d3acedd54556",
"requested_on":"20170222 18:40:41 +0400"
}
43
Firebase
https://console.firebase.google.com
44
Firebase
Avantages
¬ L’authentification est gérée ‘in-built’ par Google
¬ Base de données en temps réel
¬ Firebase et Angular JS
¬ Permet de déployer une application (UI) par ligne de commande
- > firebase deploy
Inconvenients
¬ On fait confiance à Google concernant la securité des données
45
Firebase
Utilisation dans symfony
¬ Bundle
- https://github.com/ktamas77/firebase-php
¬ Utilisation
const DEFAULT_URL = 'https://kidsplace.firebaseio.com/';
const DEFAULT_TOKEN = 'MqL0c8tKCtheLSYcygYNtGhU8Z2hULOFs9OKPdEp';
const DEFAULT_PATH = '/firebase/example';
$firebase = new FirebaseFirebaseLib(DEFAULT_URL, DEFAULT_TOKEN);
// --- storing an array ---
$test = array(
"foo" => "bar",
"i_love" => "lamp",
"id" => 42
);
$dateTime = new DateTime();
$firebase->set(DEFAULT_PATH . '/' . $dateTime->format('c'), $test);
46
Firebase
Utilisation dans symfony
¬ Evènement EmployeeHiredEvent
class UpdateEmployeesListDenormalizer
{
public function notify(EmployeeHiredEvent $event)
{
$path = '/users/list/'.$event->getEmployeeId();
$firebase->set($path.'/buId', $event->getEmployerId());
$firebase->set($path.'/id', $event->getEmployeeId());
$firebase->set($path.'/civility', $event->getEmployeeCivility());
$firebase->set($path.'/email', $event->getEmployeeEmail());
$firebase->set($path.'/firstname', $event->getEmployeeFirstName());
$firebase->set($path.'/lastname', $event->getEmployeeLastName());
$firebase->set($path.'/job_title', $event->getEmployeeJobTitle());
}
}
47
Firebase
Utilisation dans symfony
¬ Evènement EmployeeHiredEvent
¬ Service
- Un denormalizer va souscrire à l’évènement
update_employees_list_denormalizer:
class: pathtoDenormalizersUpdateEmployeesListDenormalizer
tags:
- { name: event_subscriber,
subscribes_to: pathtoAggregatesDomainEventsEmployeeHiredEvent,
method: notify }
48
Process Managers
a.k.a Saga
Ecoute des évènements et dispatch de nouvelles
commandes
Utile pour coordonner les actions entre les bounded
contexts
Exemple
¬ HRManagement et BusinessUnitManagement
¬ Combien de salariés possède une BU?
- Informer l’agrégat BU pour qu’il incrémente/décrémente son
nombre de salariés à chaque fois qu’une personne est embauche
pour cette BU.
- Solution: Création d’un service qui souscrit à l’évènement
EmployeeHiredEvent
49
Process Managers
Exemple (suite)
¬ ChangeNumberOfEmployeesInBusinessUnitCommand
¬ AddEmployeeToBusinessUnitSaga
class changeNumberOfEmployeesInBusinessUnitCommand
{
public $businessUnitId;
}
class AddEmployeeToBusinessUnitSaga
{
public function notify(EmployeeHiredEvent $event)
{
$command = new changeNumberOfEmployeesInBusinessUnitCommand();
$command->businessUnitId = $event->getEmployerId();
$this->commandBus->handle($command);
}
}
50
Conclusion
Complexe à mettre en oeuvre
Difficile à faire en CRUD
¬ Opérations de lecture et d’écriture sont dissosiées
¬ Pas de conflits dans le cas où plusieurs utilisateurs mettent à jour
le même objet
¬ Enregistrement des actions et des données sur une entité
¬ Historique des évènements possible
¬ Restauration du read model à partir du write model
+ Restauration à partir d’un évènement
¬ Séparation de l’implementation plus facile
51
1. Architecture
1. Aperçu
2. Mise en place
3. Bonnes pratiques
4. Sécurité
5. CORS
52
Bonnes pratiques
Le nom des méthodes est important
¬ Le nom doit reflécter au maximum l’objectif ce que
l’utilisateur veut faire
¬ Exemple:
- HireEmployeeCommand
- EmployeeHiredEvent
- EmployeeLastNameChanged
- ContractTransferredToOtherDirectorEvent
- RessourceAddedToContractEvent
- AccountUpdated / AccountModified
Les commandes sont au présent, les évènements sont
au passé
53
Bonnes pratiques
Un évènement ne peut/doit pas être modifié
¬ Pas de setters dans les classes Event
Un évènement est idempotent
¬ Exemple:
- FireEmployeeCommand sur une même personne n fois
- L’état de l’agrégat ne changera pas après la première fois
- L’agrégat de contentera d’ignorer la commande (et ne pas
renvoyer d’exception)
Valider les commandes
54
Bonnes pratiques
Les bounded contexts doivent être identifiés dès le
début
¬ Division du problème initial en sous-problèmes (bounded
contexts)
¬ Division des bounded contexts en composants autonomes
Identifier le langage du domain expert
Personne
Employé
(RH)
Ressource
(Contrat)
Collaborateur
(Business Unit)
55
1. Architecture
1. Aperçu
2. Mise en place
3. Bonnes pratiques
4. Sécurité
5. CORS
56
Sécurité
Restriction sur la méthode
¬ Autoriser uniquement les POST
¬ MethodListener
app.method_listener:
class: AppBundleListenerMethodListener
arguments: ['POST']
tags:
- { name: kernel.event_listener,
event: kernel.controller,
method: onKernelController,
}
57
Sécurité
Restriction sur la méthode (suite)
¬ Autoriser uniquement les POST
¬ onKernelController
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if ($controller[0] instanceof Controller){
$request = $event->getRequest()->getMethod();
if (!in_array($request, $this->method)){
$event->stopPropagation();
throw new MethodNotAllowedHttpException(
array('POST'),
'Method Not Allowed‘
);
}
}
}
58
Sécurité
Authentifier les requêtes grâce aux headers
d’authentificatiom
JWT = Json Web Token
¬ Encodage de la requête et insertion dans le header
¬ Composé de troix parties
- header
- payload
- signature
59
Sécurité / JWT
Header
¬ Type
¬ Algorithme de cryptage
¬ Base64
{
"typ": "JWT",
"alg": "HS256"
}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
60
Sécurité / JWT
Payload
{
"employee_id":"4731ab33-1db8-4570-bc27-e3f4ec0f4feb",
"employer_id":"123a3256-2345-1243-a231-a123c1567b12",
"employee_first_name":"Jihad",
"employee_last_name":"Sahebdin",
"employee_civility":"M",
"employee_job_title":"Developer",
"employee_email":"jihad@sahebdin.com",
"requested_by":"f54d197b-9647-47f8-bfa6-d3acedd54556",
"requested_on":"20170222 18:40:41 +0400"
}
61
Sécurité / JWT
Signature
¬ Header
¬ Payload
¬ Crypter en HMAC256 avec un secret
var encodedString = base64UrlEncode(header) + "." +
base64UrlEncode(payload);
HMACSHA256(encodedString, 'secret');
62
Sécurité
Côté API
¬ TokenListener
¬ Récupération du header
¬ Décryptage du token
app.token.token_listener:
class: AppBundleListenerTokenListener
arguments: ['@app.token_authenticator']
tags:
- { name: kernel.event_listener, event: kernel.request,
method: onKernelRequest }
$header = $request->headers->get('authorization');
try {
$decoded = JWT::decode($token, $secret, array('HS256'));
}
catch (Exception $e) {
return Response::HTTP_FORBIDDEN;
}
63
Sécurité / Nonce
Nonce = Number used ONCE
Insertion dans la requête pour l’encodage
Vérification de l’unicité du nonce avec les nonces déjà
utilisés
64
Sécurité / UUID
UUID = Universally Unique Identifier
Utilisé comme:
¬ identifiant d’une entité
¬ nonce
Version 4
¬ Génère une uuid aléatoire
¬ 123e4567-e89b-12d3-a456-426655440000
65
1. Architecture
1. Aperçu
2. Mise en place
3. Bonnes pratiques
4. Sécurité
5. CORS
66
Sécurité / CORS
Modification de la request
¬ Dans un event listener: onKernelRequest
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if ($request->headers->has("Access-Control-Request-Headers")
&& $request->headers->has("Access-Control-Request-Method"))
{
$response = new Response();
//enable CORS - return the requested methods as allowed
$response->headers->add(
array(
'Access-Control-Allow-Headers' => $request->headers->get("Access-
Control-Request-Headers"),
'Access-Control-Allow-Methods' => $request->headers->get("Access-
Control-Request-Method"),
'Access-Control-Allow-Origin' => '*'));
$event->setResponse($response);
}
67
Un mot sur la UI
Données stockés sur Firebase
¬ Correspondance entre l’arborescence des données et les urls
(pages) de la UI
¬ Utilisation d’un resolve avant le chargement de la page
resolve: {
listEmployees : function(firebaseService) {
return firebaseService.get('/users/list');
},
68
Références
Bundles existants
¬ Broadway
- https://github.com/broadway/broadway
¬ LiteCqrs
- https://github.com/beberlei/litecqrs-php
Auteurs
¬ Gregory Young
- Simple CQRS example: https://github.com/gregoryyoung/m-r
¬ Matthias Noback
- https://php-and-symfony.matthiasnoback.nl/
¬ Benjamin Eberlei
- https://beberlei.de/
¬ Martin Fowler
- https://martinfowler.com/bliki/CQRS.html
¬ Daniel Whittaker
- http://danielwhittaker.me/
extension interactive
14, avenue des Lataniers
72238 Quatre Bornes
Merci

Contenu connexe

PPTX
Symfony2 - Un Framework PHP 5 Performant
PPTX
Modèle de domaine riche dans une application métier complexe un exemple pratique
PPTX
SOLID : les principes à l’origine du succès de Symfony et de vos applications
PDF
Zf2 ce-qui-va-changer
PPTX
De Java à .NET
PPT
Soutenance Zend Framework vs Symfony
PDF
Programmation orientée objet en PHP 5
PDF
Php 2 - Approfondissement MySQL, PDO et MVC
Symfony2 - Un Framework PHP 5 Performant
Modèle de domaine riche dans une application métier complexe un exemple pratique
SOLID : les principes à l’origine du succès de Symfony et de vos applications
Zf2 ce-qui-va-changer
De Java à .NET
Soutenance Zend Framework vs Symfony
Programmation orientée objet en PHP 5
Php 2 - Approfondissement MySQL, PDO et MVC

Tendances (20)

PDF
Function oop - bonnes pratiques ms tech days
PPTX
Introduction à Angular JS
PDF
SQL et MySQL
PPTX
php2 : formulaire-session-PDO
ODP
Patterns et bonnes pratiques autour de JavaScript
PDF
Javascript et JQuery
PPT
Php mysql cours
 
PDF
Python avancé : Interface graphique et programmation évènementielle
PDF
Trucs et astuces PHP et MySQL
PDF
Open close principle, on a dit étendre, pas extends !
PPTX
JavaScript prise en main et fondamentaux
ODP
Formation PHP avancé - Cake PHP
PDF
PHP 5 et la programmation objet
PDF
Formulaires Symfony2 - Cas pratiques et explications
PDF
Bases de PHP - Partie 1
PDF
Cours php & Mysql - 5éme partie
PDF
Héritage et redéfinition de méthode
PDF
Sécurité et Quaité de code PHP
PDF
Cours php & Mysql - 4éme partie
PDF
Corrige tp java
Function oop - bonnes pratiques ms tech days
Introduction à Angular JS
SQL et MySQL
php2 : formulaire-session-PDO
Patterns et bonnes pratiques autour de JavaScript
Javascript et JQuery
Php mysql cours
 
Python avancé : Interface graphique et programmation évènementielle
Trucs et astuces PHP et MySQL
Open close principle, on a dit étendre, pas extends !
JavaScript prise en main et fondamentaux
Formation PHP avancé - Cake PHP
PHP 5 et la programmation objet
Formulaires Symfony2 - Cas pratiques et explications
Bases de PHP - Partie 1
Cours php & Mysql - 5éme partie
Héritage et redéfinition de méthode
Sécurité et Quaité de code PHP
Cours php & Mysql - 4éme partie
Corrige tp java
Publicité

Similaire à Symfony CQRS and _event_sourcing (20)

PPTX
CQRS + Event Sourcing
PPTX
Symfony3 overview
PDF
Mieux Développer en PHP avec Symfony
PDF
Support de cours Spring M.youssfi
PDF
S51 vos projets web services ibm i a l aide de php
PDF
DDD, CQRS et Event Sourcing : quand coder propre n'est plus suffisant
PDF
Design applicatif avec symfony - Zoom sur la clean architecture - Symfony Live
PPTX
ASP.NET MVC, Web API & KnockoutJS
PDF
Architecture jee principe de inversion de controle et injection des dependances
PPTX
Drupal 8, symfony
PPTX
Introduction à Symfony
PDF
Etude des Frameworks PHP
PDF
Correction-examen-TW2.0-SPS2-22-23 (1).pdf
PPTX
[2015] Laravel yet another framework
PPTX
DevFestBdm2019
PDF
Support de cours entrepise java beans ejb m.youssfi
PPTX
Controller_Rest.pptx
PDF
Decouvrir CQRS (sans Event sourcing) par la pratique
PPTX
Retour AFUP du forumphp 2017
PPTX
retour sur confoo2011 et Symfony2
CQRS + Event Sourcing
Symfony3 overview
Mieux Développer en PHP avec Symfony
Support de cours Spring M.youssfi
S51 vos projets web services ibm i a l aide de php
DDD, CQRS et Event Sourcing : quand coder propre n'est plus suffisant
Design applicatif avec symfony - Zoom sur la clean architecture - Symfony Live
ASP.NET MVC, Web API & KnockoutJS
Architecture jee principe de inversion de controle et injection des dependances
Drupal 8, symfony
Introduction à Symfony
Etude des Frameworks PHP
Correction-examen-TW2.0-SPS2-22-23 (1).pdf
[2015] Laravel yet another framework
DevFestBdm2019
Support de cours entrepise java beans ejb m.youssfi
Controller_Rest.pptx
Decouvrir CQRS (sans Event sourcing) par la pratique
Retour AFUP du forumphp 2017
retour sur confoo2011 et Symfony2
Publicité

Dernier (7)

PDF
Frais et décompte dans SAP S/4HANA Transportation Management, S4TM3 Col26
PDF
IPTV Meilleur - Le Meilleur Abonnement IPTV en France pour 2025
PDF
Architecture logicielle et Modeles de Conception
PPTX
Cours Electrotechnique L2 - Séance 6.pptx
PDF
Analyse technique approfondie pour la gestion des transports dans SAP S/4HANA...
PDF
COURS GCDs Chap 9.pdf tous les éléments.
PPTX
test pour la présentation foire de Chalôns V1
Frais et décompte dans SAP S/4HANA Transportation Management, S4TM3 Col26
IPTV Meilleur - Le Meilleur Abonnement IPTV en France pour 2025
Architecture logicielle et Modeles de Conception
Cours Electrotechnique L2 - Séance 6.pptx
Analyse technique approfondie pour la gestion des transports dans SAP S/4HANA...
COURS GCDs Chap 9.pdf tous les éléments.
test pour la présentation foire de Chalôns V1

Symfony CQRS and _event_sourcing

  • 1. Zehreen Soogun / zehreen@extension-interactive.com Jihad Sahebdin / jihad@extension-interactive.com S y m f o n y 3 31/03/2017 C Q R S e t E v e n t s o u r c i n g
  • 2. 2 Who Am I SOOGUN Bibi Zehreen ¬ Chef de Projet Technique ¬ Extension Interactive Je suis #Zehreen Find me on :- https://twitter.com/bzehreens https://mu.linkedin.com/in/bibi-zehreen-soogun-53115190
  • 3. 3 Speakers / Présentation intervenants Zehreen ¬ CQRS ¬ ES Jihad ¬ Cas réel ¬ MEP CQRS et ES dans un projet SF 3.
  • 4. 4 1. CQRS 1. Introduction 2. Avantages 3. Désavantages 4. Utilisation 2. Event Sourcing 3. Architecture 4. Conclusions
  • 5. 5 CQRS / Introduction Command Query Responsibility Segregation Pioneer de CQRS ¬ Greg Young Origine de CQRS ¬ Bertrand Meyer - CQS
  • 6. 6 CQRS / Command Contexte boutique : Je valide ma commande Contexte Symfony : Je lance une commande via console ¬ php bin/console c:c
  • 7. 7 CQRS / Command Nom d’une tâche ¬ Temps impératif Données requises pour effectuer la tâche a.k.a ¬ Mutators ¬ Modifiers Contrast avec CRUD ¬ Point de données openCustomerAccountCommand
  • 8. 8 CQRS / Query Types de requêtes classiques ¬ Insert ¬ Select ¬ Update ¬ Delete ReadOnly ¬ Récupération des données Constraste avec CRUD ¬ TOUTES les opérations VS Lecture seule
  • 9. 9 CQRS & CQS Similarités ¬ Utilisation commandes (modification) et requêtes (récupération)  Différence ¬ Write Model ¬ Read Model
  • 10. 10 CQRS / Avantages Performance ¬ Plus de lecture que d’écriture Evolution
  • 12. 12 CQRS / Utilisation Environnement collaborative Données en simultanés Applications haute performance Système complèxe ¬ Bounded Context BankSystem Pas nécessaire d’appliquer CQRS dans l’intégralité d’un système
  • 13. 13 CQRS / A éviter Système simple Système statique Système non-collaboratif Système avec zéro logique métier
  • 14. 14 CQRS / Faire notre choix Découpler Read et Write / Coûteux Réfléchir en terme de DDD et non CRUD
  • 15. 15 1. CQRS 1. Introduction 2. Avantages 3. Désavantages 4. Utilisation 2. Event Sourcing 3. Architecture 4. Conclusions
  • 16. 16 Event Sourcing Evènements ¬ a.k.a Domain Event CustomerAccountOpened Pas de suppréssion
  • 17. 17 CQRS + Event Sourcing
  • 18. 18 1. Architecture 1. Aperçu 2. Mise en place 3. Bonnes pratiques 4. Sécurité 5. CORS
  • 19. 19 Aperçu Application de type API/UI Enregistrement dans 2 bases de données ¬ Write model ¬ Read model La UI envoie des requêtes sur l’API et récupère les données depuis Firebase UI API Firebase
  • 20. 20 Le Domain Model Système de gestion: Une business unit emploie des salaries qui vont travailler sur des contrats liés à des clients Business Units Salariés Contrats Clients
  • 21. 21 1. Architecture 1. Aperçu 2. Mise en place 3. Bonnes pratiques 4. Sécurité 5. CORS
  • 23. 23 Bounded context Le Domain Model s’organise en morceaux Chaque morceau correspond à: ¬ un métier ¬ Un composant métier ¬ Une problématique métier qu’il propose de solutionner Morceau = Bounded Context Exemple: ¬ Un context RH qui s’occupe de gérer les employés ¬ Un context Contrat qui s’occupe de gérer ses clients et ses ressources
  • 24. 24 Bounded context / Composants autonomes Un bounded context peut être composé d’un ou de plusieurs composants autonomes. Les composants apportent une solution aux problématiques identifiés dans un bounded context. Exemple: ¬ On va s’occuper de la gestion des business units dans le composant Business Units management
  • 25. 25 Bounded context / Composants autonomes 3 bounded contexts: ¬ Business units ¬ Human Resource ¬ Contracts Business Unit Human Resource Contract
  • 26. 26 Bounded context / Composants autonomes Fonctionnement ¬ Un composant reçoit des messages en provenance du MessageBus: - Soit par l’intermédiaire de ses CommandHandlers s’il s’agit de commandes - Soit par l’intermédiaire de ses ProcessManagers s’il s’agit d’évènements
  • 27. 27 Bounded context / Structure
  • 28. 28 Commands Commands: ¬ Les ordres données au Domain Model ¬ Sources diverses: - Application web - Application mobile - Ligne de commandes ¬ Format json ¬ Construction d’un objet Command à partir des données Domain Model Application Web Application mobile Ligne de commande
  • 29. 29 Commands Exemple: ¬ La classe HireEmployeeCommand class HireEmployeeCommand { public $employeeId; public $employerId; public $employeeFirstName; public $employeeLastName; public $employeeCivility; public $employeeJobTitle; public $employeeEmail; public $requestedBy; }
  • 30. 30 Commands Controller: ¬ HRManagementController - hireEmployeeAction use pathtoCommandingCommandsHireEmployeeCommand; class HRManagementController extends Controller { /** * @Route("/hire-employee", name="hire_employee") */ public function hireEmployeeAction(Request $request) { $commandBus = $this->get('command_bus'); $hireEmployeeCommand = new HireEmployeeCommand(); //Populate command data $commandBus->handle($hireEmployeeCommand); return new Response("Employee hired"); } }
  • 31. 31 SimpleBus bundle http://simplebus.github.io/SymfonyBridge/ Auteur: Matthias Noback class AppKernel extends Kernel { public function registerBundles() { $bundles = array( ... new SimpleBusSymfonyBridgeSimpleBusEventBusBundle() ) ... } ... }
  • 32. 32 Command Handlers HireEmployeeCommandHandler ¬ Handle Service hire_employee_command_handler: class: pathtoCommandingCommandHandlersHireEmployeeCommandHandler tags: - { name: command_handler, handles: pathtoCommandingCommandsHireEmployeeCommand } namespace pathtoCommandingCommandHandlers; class HireEmployeeCommandHandler { public function handle(HireEmployeeCommand $command) {
  • 33. 33 Command Handlers Reconstruction de l’agrégat à partir de son historique Enregistrement des évènements public function handle(HireEmployeeCommand $command) { $employeeId = $command->employeeId; $history = $this->eventStore->getHistoryFor($employeeId); $employeeAggregate = employeeAggregate::reconstituteFrom($employeeId, $history);
  • 34. 34 Command Handlers On passe les données à l’agrégat $employeeAggregate->hireEmployee( $command->employeeId, $command->employerId, $command->employeeFirstName, $command->employeeLastName, $command->employeeCivility, $command->employeeJobTitle, $command->employeeEmail, $command->requestedBy, $command->requestedOn );
  • 35. 35 Command Handlers Enregistrement des évènements Dispatch de l’évènement foreach ($events as $event) { $this->eventBus->handle($event); } $events = $this->eventStore->save($employeeAggregate);
  • 36. 36 Agrégats Contient la logique du Domain model Validation sémantique des commandes Reçoit les commandes du MessageBus Emet un ou plusieurs évènements relatant l’opération qu’il vient d’executer ¬ employeeHiredEvent
  • 37. 37 Agrégats Validation des commandes ¬ Un agrégat peut réfuser d’éxécuter une commande - Exemple: › Par exemple, si HRManagementAggregate reçoit la commande HireEmployeeCommand pour une BU donnée, si la BU n’existe pas, il doit renvoyer une exception class HRManagementAggregate { public function hireEmployee($employeeId, $employerId, $employeeFirstName, $employeeLastName) { $this->isBusinessUnitIdValid($employerId); $this->recordThat(new EmployeeHiredEvent($employeeId, $employerId, $employeeFirstName, $employeeLastName)); } private function isBusinessUnitIdValid($businessUnit) { if (!$businessUnit instanceof BusinessUnit) { throw new BusinessUnitIdIsNotValidException('This business Unit is not valid'); } return; } }
  • 38. 38 Domain Events Domain Events ¬ Les évènements métiers produits par les aggrégats ¬ Exemple: - L’évènement EmployeeHiredEvent émis par l’agrégat HRManagementAggregate namespace pathtoAggregatesDomainEvents; class EmployeeHiredEvent { /** * @Type("string") */ private $employeeId; public function __construct($employeeId) { $this->employeeId = $employeeId; } public function getEmployeeId() { return $this->employeeId; } }
  • 39. 39 Bounded context / Récapitulatif Récapitulatif: ¬ On a donc réussi à construire une commande à partir d’une requête venant de l’extérieur ¬ Le command handler s’est chargé de le communiquer à l’agrégat ¬ L’agrégat valide la commande, l’éxécute et génère un évènement ¬ L’évènement est stocké dans l’event store puis dispatché a l’event bus
  • 40. 40 Bounded context / Suite L’évènement peut alors être: ¬ Propagé dans la UI par le biais des denormalizers ¬ Capté par les process managers.
  • 42. 42 Denormalizers Problème ¬ Données pas exploitable pour la UI { "employee_id":"4731ab33-1db8-4570-bc27-e3f4ec0f4feb", "employer_id":"123a3256-2345-1243-a231-a123c1567b12", "employee_first_name":"Jihad", "employee_last_name":"Sahebdin", "employee_civility":"M", "employee_job_title":"Developer", "employee_email":"jihad@sahebdin.com", "requested_by":"f54d197b-9647-47f8-bfa6-d3acedd54556", "requested_on":"20170222 18:40:41 +0400" }
  • 44. 44 Firebase Avantages ¬ L’authentification est gérée ‘in-built’ par Google ¬ Base de données en temps réel ¬ Firebase et Angular JS ¬ Permet de déployer une application (UI) par ligne de commande - > firebase deploy Inconvenients ¬ On fait confiance à Google concernant la securité des données
  • 45. 45 Firebase Utilisation dans symfony ¬ Bundle - https://github.com/ktamas77/firebase-php ¬ Utilisation const DEFAULT_URL = 'https://kidsplace.firebaseio.com/'; const DEFAULT_TOKEN = 'MqL0c8tKCtheLSYcygYNtGhU8Z2hULOFs9OKPdEp'; const DEFAULT_PATH = '/firebase/example'; $firebase = new FirebaseFirebaseLib(DEFAULT_URL, DEFAULT_TOKEN); // --- storing an array --- $test = array( "foo" => "bar", "i_love" => "lamp", "id" => 42 ); $dateTime = new DateTime(); $firebase->set(DEFAULT_PATH . '/' . $dateTime->format('c'), $test);
  • 46. 46 Firebase Utilisation dans symfony ¬ Evènement EmployeeHiredEvent class UpdateEmployeesListDenormalizer { public function notify(EmployeeHiredEvent $event) { $path = '/users/list/'.$event->getEmployeeId(); $firebase->set($path.'/buId', $event->getEmployerId()); $firebase->set($path.'/id', $event->getEmployeeId()); $firebase->set($path.'/civility', $event->getEmployeeCivility()); $firebase->set($path.'/email', $event->getEmployeeEmail()); $firebase->set($path.'/firstname', $event->getEmployeeFirstName()); $firebase->set($path.'/lastname', $event->getEmployeeLastName()); $firebase->set($path.'/job_title', $event->getEmployeeJobTitle()); } }
  • 47. 47 Firebase Utilisation dans symfony ¬ Evènement EmployeeHiredEvent ¬ Service - Un denormalizer va souscrire à l’évènement update_employees_list_denormalizer: class: pathtoDenormalizersUpdateEmployeesListDenormalizer tags: - { name: event_subscriber, subscribes_to: pathtoAggregatesDomainEventsEmployeeHiredEvent, method: notify }
  • 48. 48 Process Managers a.k.a Saga Ecoute des évènements et dispatch de nouvelles commandes Utile pour coordonner les actions entre les bounded contexts Exemple ¬ HRManagement et BusinessUnitManagement ¬ Combien de salariés possède une BU? - Informer l’agrégat BU pour qu’il incrémente/décrémente son nombre de salariés à chaque fois qu’une personne est embauche pour cette BU. - Solution: Création d’un service qui souscrit à l’évènement EmployeeHiredEvent
  • 49. 49 Process Managers Exemple (suite) ¬ ChangeNumberOfEmployeesInBusinessUnitCommand ¬ AddEmployeeToBusinessUnitSaga class changeNumberOfEmployeesInBusinessUnitCommand { public $businessUnitId; } class AddEmployeeToBusinessUnitSaga { public function notify(EmployeeHiredEvent $event) { $command = new changeNumberOfEmployeesInBusinessUnitCommand(); $command->businessUnitId = $event->getEmployerId(); $this->commandBus->handle($command); } }
  • 50. 50 Conclusion Complexe à mettre en oeuvre Difficile à faire en CRUD ¬ Opérations de lecture et d’écriture sont dissosiées ¬ Pas de conflits dans le cas où plusieurs utilisateurs mettent à jour le même objet ¬ Enregistrement des actions et des données sur une entité ¬ Historique des évènements possible ¬ Restauration du read model à partir du write model + Restauration à partir d’un évènement ¬ Séparation de l’implementation plus facile
  • 51. 51 1. Architecture 1. Aperçu 2. Mise en place 3. Bonnes pratiques 4. Sécurité 5. CORS
  • 52. 52 Bonnes pratiques Le nom des méthodes est important ¬ Le nom doit reflécter au maximum l’objectif ce que l’utilisateur veut faire ¬ Exemple: - HireEmployeeCommand - EmployeeHiredEvent - EmployeeLastNameChanged - ContractTransferredToOtherDirectorEvent - RessourceAddedToContractEvent - AccountUpdated / AccountModified Les commandes sont au présent, les évènements sont au passé
  • 53. 53 Bonnes pratiques Un évènement ne peut/doit pas être modifié ¬ Pas de setters dans les classes Event Un évènement est idempotent ¬ Exemple: - FireEmployeeCommand sur une même personne n fois - L’état de l’agrégat ne changera pas après la première fois - L’agrégat de contentera d’ignorer la commande (et ne pas renvoyer d’exception) Valider les commandes
  • 54. 54 Bonnes pratiques Les bounded contexts doivent être identifiés dès le début ¬ Division du problème initial en sous-problèmes (bounded contexts) ¬ Division des bounded contexts en composants autonomes Identifier le langage du domain expert Personne Employé (RH) Ressource (Contrat) Collaborateur (Business Unit)
  • 55. 55 1. Architecture 1. Aperçu 2. Mise en place 3. Bonnes pratiques 4. Sécurité 5. CORS
  • 56. 56 Sécurité Restriction sur la méthode ¬ Autoriser uniquement les POST ¬ MethodListener app.method_listener: class: AppBundleListenerMethodListener arguments: ['POST'] tags: - { name: kernel.event_listener, event: kernel.controller, method: onKernelController, }
  • 57. 57 Sécurité Restriction sur la méthode (suite) ¬ Autoriser uniquement les POST ¬ onKernelController public function onKernelController(FilterControllerEvent $event) { $controller = $event->getController(); if ($controller[0] instanceof Controller){ $request = $event->getRequest()->getMethod(); if (!in_array($request, $this->method)){ $event->stopPropagation(); throw new MethodNotAllowedHttpException( array('POST'), 'Method Not Allowed‘ ); } } }
  • 58. 58 Sécurité Authentifier les requêtes grâce aux headers d’authentificatiom JWT = Json Web Token ¬ Encodage de la requête et insertion dans le header ¬ Composé de troix parties - header - payload - signature
  • 59. 59 Sécurité / JWT Header ¬ Type ¬ Algorithme de cryptage ¬ Base64 { "typ": "JWT", "alg": "HS256" } eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
  • 61. 61 Sécurité / JWT Signature ¬ Header ¬ Payload ¬ Crypter en HMAC256 avec un secret var encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload); HMACSHA256(encodedString, 'secret');
  • 62. 62 Sécurité Côté API ¬ TokenListener ¬ Récupération du header ¬ Décryptage du token app.token.token_listener: class: AppBundleListenerTokenListener arguments: ['@app.token_authenticator'] tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest } $header = $request->headers->get('authorization'); try { $decoded = JWT::decode($token, $secret, array('HS256')); } catch (Exception $e) { return Response::HTTP_FORBIDDEN; }
  • 63. 63 Sécurité / Nonce Nonce = Number used ONCE Insertion dans la requête pour l’encodage Vérification de l’unicité du nonce avec les nonces déjà utilisés
  • 64. 64 Sécurité / UUID UUID = Universally Unique Identifier Utilisé comme: ¬ identifiant d’une entité ¬ nonce Version 4 ¬ Génère une uuid aléatoire ¬ 123e4567-e89b-12d3-a456-426655440000
  • 65. 65 1. Architecture 1. Aperçu 2. Mise en place 3. Bonnes pratiques 4. Sécurité 5. CORS
  • 66. 66 Sécurité / CORS Modification de la request ¬ Dans un event listener: onKernelRequest public function onKernelRequest(GetResponseEvent $event) { $request = $event->getRequest(); if ($request->headers->has("Access-Control-Request-Headers") && $request->headers->has("Access-Control-Request-Method")) { $response = new Response(); //enable CORS - return the requested methods as allowed $response->headers->add( array( 'Access-Control-Allow-Headers' => $request->headers->get("Access- Control-Request-Headers"), 'Access-Control-Allow-Methods' => $request->headers->get("Access- Control-Request-Method"), 'Access-Control-Allow-Origin' => '*')); $event->setResponse($response); }
  • 67. 67 Un mot sur la UI Données stockés sur Firebase ¬ Correspondance entre l’arborescence des données et les urls (pages) de la UI ¬ Utilisation d’un resolve avant le chargement de la page resolve: { listEmployees : function(firebaseService) { return firebaseService.get('/users/list'); },
  • 68. 68 Références Bundles existants ¬ Broadway - https://github.com/broadway/broadway ¬ LiteCqrs - https://github.com/beberlei/litecqrs-php Auteurs ¬ Gregory Young - Simple CQRS example: https://github.com/gregoryyoung/m-r ¬ Matthias Noback - https://php-and-symfony.matthiasnoback.nl/ ¬ Benjamin Eberlei - https://beberlei.de/ ¬ Martin Fowler - https://martinfowler.com/bliki/CQRS.html ¬ Daniel Whittaker - http://danielwhittaker.me/
  • 69. extension interactive 14, avenue des Lataniers 72238 Quatre Bornes Merci

Notes de l'éditeur

  • #4: Introduction / C’est quoi Avantages / Pourquoi l’utiliser Désavantages / Pourquoi ne pas l’utiliser Utilisation / Quand l’utiliser Case Study par Jihad
  • #6: CQRS C’est un concept intéressant qui a la responsabilité de séparer la commande et la requête. - Pioneer Greg Young - premier à avoir parlé sur le CQRS Origine CQRS applique le principe CQS, dévéloppé par Bertrand Meyer. Une méthode doit être soit une commande ou une requête
  • #7: C’est quoi une commande ? Dans un context boutique, on utilise ce mot quand on valide notre commande, par exemple. Dans le context d’un projet Symfony, on lance des commande, par ex. Pour supprimer le cache on lancer un php bin/console cache:clear
  • #8: Dans le context CQRS, une commande c’est un objet simple avec le nom d’une operation et les données qu’on aura besoin pour effectuer l’opération. A noter que le nom de la commande doit toujours être dans le temps impératif. Ceci montre que l’application peut valider ou refuser la commande. Une commande est aussi connu comme des “mutators” / “modifiers” car cela va modifier l’état d’un système. En comparaison avec l’architecture classqiue, une commande renvoi uniquement les données requises pour traiter la tache tandis que dans le CRUD, on envoie toutes les données de l’objet Ex, openCust….
  • #9: Quand on parle de requête, très souvent on fait référence aux mots comme SELECT, INSERT, UPDATE, DELETE. Dans le context de CQRS, une requête renvoi des résultats et à aucun moment, la requête peut modifier l’état du système. En comparison avec l’architecture classique, une requête fera uniquement la lecture dans la BdD tandis que dans le CRUD, une requête peut faire toutes les operations (sauvegarde / mise à jour / récupération des informations)
  • #10: CQRS applique la notion de CQS en utilisant des objets Command et Query séparés pour modifier et récupérer les données respectivement. CQRS utilise un modèle différent pour mettre à jour les informations et un autre modèle pour lire les informations, ce qu’on appelle le Write Model et le Read Model
  • #11: Voici quelques points forts de ce design pattern : - Performance On gagne plus en performance. En general, on fait plus de lecture dans une BdD que d’écriture. Et souvent des fois, la volumétrie des données renvoyées est conséquente. Du point de vue utilisateur, le temps d’atttente de réponse devrait être minime. Le fait qu’on a dissocié le Read Model permet d’avoir un temps de chargement rapide. Evolution On a plus de flexibilité pour faire évoluer le système
  • #12: Dans le cas ou on applique CQRS sur un domaine qui ne correspond pas, on rajoute plus de compléxité et de risques et conséquemment cela va réduire la productivité.
  • #13: Quand peut-on utiliser CQRS ? CQRS sera plus approprié quand on a un système collaboratif ou les utilisateurs peuvent opérer sur les données simultanément Dans le cas ou on a un gros système qui requiert une haute performance Un système complèxe qu’on peut decomposer en plusieurs morceaux autonomes Jihad reviendra la dessus avec plus de détail Un exemple serait un système bancaire. On a beaucoup de transactions, beaucoup de données qui seront impactées et tous les utilisateurs devraient avoir données synchronisées et non des données obsoletes. Un point à faire ressortir, c’est qu’on peut utiliser CQRS dans une partie de notre système et pas necessaire de l’appliquer sur l’ensemble de l’application.
  • #14: Dans les cas suivants, on evite d’utiliser CQRS
  • #15: Séparer la Read et la Write Model est un processus couteux en terme de prix et de temps. En terme de cout, l’implementation de CQRS dans un projet doit être égale ou inférieure à celui d’un projet classique Il fauut prendre du temps pour sortir d’un projet classique en CRUD pour y aller sur du Domain Driven Design. Reflechir en tant que domaine et non en tant que BdD est une étape très importante pour faire notre choix et pour ensuite l’appliquer Si on arrive à bien identifier le domain, c’est qu’on peut implementer le CQRS dans notre projet
  • #17: Event Sourcing par Greg Young Event Sourcing c’est la notion de rejouer les évènements pour arriver à l’état actuel du système. C’est quoi Un Domaine Event : On a effectué une action dans le passé et donc générer un événement. Ex. custAccOpe On peut voir qu’on a segmenté le Select du CRUD dans le Read Model du CQRS Le Insert et Update du CRUD dans le Write Model du CQRS Mais qu’est ce qui se passe pour le Delete du CRUD ??? C’est simple, on ne fait pas de suppression dans le CQRS, c’est une étape supplementaire qui se rajoute.
  • #26: Pour simplifier, on part donc de 3 bounded context (BU, RH, Contrats) composé chacun d’un seul composant autonome.
  • #28: Commands : les ordres données au Domain Model CommandHandlers: passe les commandes à l’agrégat L’agrégat valide la sémantique de la commande, l’éxécute et génère un évènement Le denormalizer reconstruit les données à partir des données de l’évènement Le process manager lance de nouvelles commandes à partir d’un évènement
  • #31: Puisque la requête arrive par un POST sur une url, on définit donc une action dans un controller. ‘command_bus’ est un service qui va faire le lien (en quelques sortes) entre la commande et son handler.
  • #37: Toute la logique métier du Domain Model doit être traitée au sein des agrégats C’est aux agrégats que les commandHandlers adressent les commandes qu’ils ont reçu du MessageBus
  • #39: !!!: Pas de setters puisqu’un évènement ne peut pas être modifié, ça s’est produit, on ne peut pas revenir en arrière.
  • #61: Encoder en base 64