Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Architectures CQRS & ES — Axa BBL décembre 2018

Architectures CQRS & ES — Axa BBL décembre 2018

Arnaud LEMAIRE

November 29, 2018
Tweet

More Decks by Arnaud LEMAIRE

Other Decks in Programming

Transcript

  1. F R O M U S E C A S

    E A R C H I T E C T U R E T O E V E N T S O U R C I N G T H R O U G H C Q R S W I T H S O M E D D D I N I T. T H E B U Z Z W O R D C O N F E R E N C E
  2. @ L I L O B A S E –

    L G O . G R O U P Arnaud LEMAIRE | LGO
  3. VS UPDATE ADDRESS MISTAKE CORRECTION RELOCATION CRUD ARCHITECTURE TASK BASED

    UI STAND FOR USER INTERACTION USER INTENT IS LOST :-(
  4. MoveHub AddMaintenanceOperation TestLighting CommissionHub ENTITIES USE CASES Hub MaintenanceOperation TestPlan

    Commissioning YOU DON’T NEED TO HAVE THE FULL CORRECT LISTS, JUST ENOUGH TO GET STARTED HOW DO WE START ?
  5. U S E C A S E & A G

    G R E G AT E R O O T A N I N T R O D U C T I O N
  6. ENTITIES & VALUE OBJECTS DateTime Money BillingInterval ENTITIES VALUE OBJECTS

    Hub Maintenance Operation Session THE DOMAIN IS EXPRESS THROUGH PLAIN OLD PHP OBJECTS CATEGORIZED BETWEEN ENTITIES & VALUE OBJECTS EQUALITY ON IDENTITY, HAS LIFECYCLE EQUALITY ON VALUES, HAS NO LIFECYCLE
  7. AGGREGATE ROOT HUB MAINTENANCE OPERATIONS PARTS LOCATION ID OPERATIONS CAN

    ONLY BE PERFORMED THROUGH THE ROOT ENFORCE INTEGRITY REFERENCE TO COLLABORATOR WITH ≠ LIFECYCLE AGGREGATE OBJECT’S SHARE A COMMON LIFECYCLE: THEY CAN BE SAVED & FETCHED AS A WHOLE
  8. A G G R E G AT E R O

    O T A R E B U S I N E S S O B J E C T public class Hub implements AggregateRootWithUUID { static public Hub create(
 String productReference, UUID locationId ) { Hub instance = new Hub(UUID.randomUUID()); instance.locationId = locationId; instance.productReference = productReference; return instance; } private Hub(UUID id){ this.id = id; //We control the ID generation } @Override public UUID id() { return id; } }
  9. T H AT C O N TA I N C

    O L L A B O R AT O R S H U B M A I N T E N A N C E O P E R AT I O N M A I N T E N A N C E O P E R AT I O N M A I N T E N A N C E O P E R AT I O N public class MaintenanceOperation { MaintenanceOperation( String description, Type operationType, LocalDateTime dateTime ) { this.description = description; this.operationType = operationType; this.dateTime = dateTime; } }
  10. T H AT P R O T E C T

    C O N S I S T E N C Y public class Hub implements AggregateRootWithUUID { //… public void addMaintenanceOperation(
 String description, 
 MaintenanceOperation.Type type
 ) { maintenanceOperations =
 maintenanceOperations.append( new MaintenanceOperation( description, type, LocalDateTime.now())); } }
  11. A N D C A N H AV E R

    E F E R E N C E T O O T H E R A G G R E G AT E public class Hub implements AggregateRootWithUUID { static public Hub create(
 String productReference, UUID locationId ) { final Hub instance = new Hub(UUID.randomUUID()); instance.locationId = locationId; instance.productReference = productReference; return instance; } private UUID locationId; }
  12. U S E C A S E A R E

    D T O public class NewMaintenanceOperationUseCase implements UseCase<Void> { public NewMaintenanceOperationUseCase( String switchId, String description, String type ) { this.switchId = switchId; this.description = description; this.type = type; } public String switchId; public String description; public String type; }
  13. W I T H A D E D I C

    AT E D H A N D L E R public class NewMaintenanceOperationUseCaseHandler implements UseCaseHandler<Void, NewMaintenanceOperationUseCase> { public NewMaintenanceOperationUseCaseHandler( SwitchRepository repository ) { this.repository = repository; } @Override public void handle(NewMaintenanceOperationUseCase command) { Hub hub = repository.get(UUID.fromString(command.switchId)); Type maintenanceOperationType = Type.valueOf(command.type); hub.addMaintenanceOperation(
 command.description, 
 maintenanceOperationType ); repository.add(aSwitch); } private final SwitchRepository repository; }
  14. C Q R S C O M M A N

    D Q U E RY R E S P O N S I B I L I T Y S E G R E G AT I O N
  15. C H A N G E S O M E

    T H I N G E V E RY T H I N G S TA R T S W I T H U S E C A S E S U S E R I want to … G E T I N F O A B O U T S O M E T H I N G (read) (mutation) C O M M A N D Q U E RY « I need information to make a decision » « I made a decision »
  16. C O M M A N D S & Q

    U E R I E S A R E D T O class RelocateHubCommand implements Command<Void> {
 
 public RelocateHubCommand(
 String hubId, String newLocationId ) {
 this.hubId = hubId;
 this.newLocationId = newLocationId;
 }
 
 @NotEmpty
 public String hubId;
 
 @NotEmpty
 public String newLocationId;
 
 } A command returns no business data to the client (can return a status & an id)
  17. S E N T I N A B U S

    @RestController
 class HubResource {
 
 @Autowired
 public HubResource(CommandBus commandBus) {
 this.commandBus = commandBus;
 }
 
 @PutMapping("/api/hub/location")
 public Void relocate(Request request) {
 Map<String, String> requestBody = Serializer.deserialize(request.body()); 
 return this.commandBus.dispatch( new RelocateHubCommand( requestBody.get("hubId"), requestBody.get("newLocationId") ));
 }
 }
  18. S E N T I N A B U S

    @RestController
 class HubResource {
 
 @Autowired
 public HubResource(CommandBus commandBus) {
 this.commandBus = commandBus;
 }
 
 @PutMapping("/api/hub/location")
 public Void relocate(Request request) {
 Map<String, String> requestBody = Serializer.deserialize(request.body()); 
 return this.commandBus.dispatch( new RelocateHubCommand( requestBody.get("hubId"), requestBody.get("newLocationId") ));
 }
 } Almost no stickiness to a framework
  19. D I S PAT C H E D T O

    A S P E C I F I C H A N D L E R @Component
 public class CommandBus {
 
 @Autowired
 public CommandBus(List<? extends Handler> handlers) {
 this.handlers = handlers.toMap( h -> Tuple.of(h.listenTo(), h) );
 }
 
 public <R, C extends Command<R>> R dispatch(C command) {
 return handlers .get(command.getClass()) .handle(command);
 }
 
 private final Map<Class, Handler> handlers;
 }
  20. D I S PAT C H E D T O

    A S P E C I F I C H A N D L E R @Component
 public class CommandBus {
 
 @Autowired
 public CommandBus(List<? extends Handler> handlers) {
 this.handlers = handlers.toMap( h -> Tuple.of(h.listenTo(), h) );
 }
 
 public <R, C extends Command<R>> R dispatch(C command<R>) {
 return handlers .get(command.getClass()) .handle(command);
 }
 
 private final Map<Class, Handler> handlers;
 }
  21. T H E F U L L P I P

    E L I N E U S E R C O M M A N D Q U E RY Q U E RY H A N D L E R C O M M A N D H A N D L E R one per query one per command Q U E RY B U S C O M M A N D B U S
  22. M I D D L E WA R E @Component

    public class BusValidationMiddleware<T> implements BusMiddleware<T, Message<T>> { @Autowired
 public BusValidationMiddleware( BusMiddleware next, Validator validator ) {
 this.next = next;
 this.validator = validator;
 }
 
 public T handle(Message<T> message) {
 Violation violations = validator.validate(message);
 if(violations.size() > 1)
 throw new ArgumentException(violations.toArray());
 
 return next.handle(message);
 }
 }
  23. C O M P O S E D I N

    T H E B U S U S E R C O M M A N D Q U E RY new BusValidationMiddleware(
 new BusTransactionMiddleware(
 new BusDispatcher( Q U E RY H A N D L E R C O M M A N D H A N D L E R Q U E RY B U S C O M M A N D B U S VA L I D AT I O N C A C H E VA L I D AT I O N U N I T O F W O R K
  24. C O M M A N D H A N

    D L I N G @Component
 public class RelocateHubCommandHandler implements CommandHandler<Void, RelocateHubCommand> {
 @Autowired
 public RelocateHubCommandHandler(HubRepository repo) {
 this.repository = repo;
 }
 
 @Override
 public Void handle(RelocateHubCommand command) {
 Hub hub = repository.getSiteInformation();
 hub.relocateTo(command.newLocationId);
 }
 
 public Class listenTo() {
 return RelocateHubCommand.class;
 }
 }
  25. C O M M A N D H A N

    D L I N G @Component
 public class RelocateHubCommandHandler implements CommandHandler<Void, RelocateHubCommand> {
 @Autowired
 public RelocateHubCommandHandler(HubRepository repo) {
 this.repository = repo;
 }
 
 @Override
 public Void handle(RelocateHubCommand command) {
 Hub hub = repository.get(command.hubId);
 hub.relocateTo(command.newLocationId);
 }
 
 public Class listenTo() {
 return RelocateHubCommand.class;
 }
 }
  26. H A N D L E R S W O

    R K O N A G G R E G AT E public class Hub {
 public Hub(UUID id) { this.id = id; }
 
 public Hub() { this.id = UUID.randomUUID(); }
 
 public UUID id() { return id; } 
 //...
 
 public void relocateTo(String newLocationId) {
 locationId = UUID.fromString(newLocationId);
 }
 
 //...
 
 private UUID locationId;
 private UUID id;
 } We keep under control the aggregate id creation
  27. P E R S I S T E N C

    E A B S T R A C T I O N public interface Repository<TId, TRoot> {
 TRoot get(TId id);
 
 void add(TRoot root);
 
 void delete(TRoot root);
 
 List<TRoot> getAll();
 } The persistence mechanism save snapshot of the application state
  28. P E R S I S T E N C

    E A B S T R A C T I O N ( P S E U D O M A P I N T E R FA C E ) public class HubRepository implements Repository<UUID, Hub> {
 
 public HubRepository(Store store)
 this.store = store;
 
 public Hub get(UUID uuid)
 return store.findById(uuid);
 
 public void add(Hub hub)
 store.upsert(hub);
 
 public void delete(Hub hub)
 store.remove(hub);
 
 public Seq<Hub> getAll()
 return store.findAll();
 } We fetch and save aggregate as a whole
  29. H A N D L E R I N T

    E R N A L S S T O R E D B A G G R E G AT E S T O R E D B fetched flushed by the unit of work middleware domain operations handler(state, command) = state’ saved
  30. T E S T I N G @Test
 public void

    it_relocates_a_hub() {
 store = new InMemoryStore();
 command = new RelocateHubCommand("hubId", "newLocationId");
 handler = new RelocateHubCommandHander( new HubRepository(store) );
 
 handler.handle(command); 
 expectedHub = store.get("hubId");
 assertThat(expectedHub.location()).equals("newLocationId");
 }
  31. C O M M A N D S & Q

    U E R I E S A R E D T O class FindHubByLocationPathQuery implements Query<Void> {
 
 public FindHubByLocationPath(
 String locationPath, ) {
 this.locationPath = locationPath;
 }
 
 @NotEmpty
 public String locationPath;
 
 }
  32. Q U E RY H A N D L I

    N G @Component
 public class FindHubByLocationPathQueryHander extends SqlHandler<Hub, FindHubByLocationPathQuery> {
 
 @Autowired
 public FindHubByLocationPathQueryHander(SqlConnection sql) {
 this.sql = sql;
 }
 
 @Override
 public Installation handle(FetchAllLocationsQuery query) {
 Record hub = sql.prepare( "SELECT * FROM hub JOIN location ON hub.locationId = location.id WHERE location.path = %s" ).execute(query.locationPath);
 return Serializer.deserialize(hub);
 }
 } We receive a direct connection to the datastore
  33. Q U E RY H A N D L I

    N G @Component
 public class FindHubByLocationPathQueryHander extends SqlHandler<Hub, FindHubByLocationPathQuery> {
 
 @Autowired
 public FindHubByLocationPathQueryHander(SqlConnection sql) {
 this.sql = sql;
 }
 
 @Override
 public Installation handle(FetchAllLocationsQuery query) {
 Record hub = sql.prepare( "SELECT * FROM hub JOIN location ON hub.locationId = location.id WHERE location.path = %s" ).execute(query.locationPath);
 return Serializer.deserialize(hub);
 }
 }
  34. T H E S A M E D ATA S

    T O R E C A N B E S H A R E D U S E R C O M M A N D Q U E RY Q U E RY H A N D L E R C O M M A N D H A N D L E R P E R S I S T E N C E while maintaining a complete segregation through the whole application Q U E RY B U S C O M M A N D B U S
  35. TA K E A WAY • Massive simplification of persistence

    mechanisms • Unit testing in a breeze • Enable business use case discovery • Drive task based UI instead of CRUD thinking
  36. D O M A I N E V E N

    T S T I L L N O T E V E N T S O U R C I N G
  37. E V E N T ≠ C O M M

    A N D C O M M A N D E V E N T Intent : « do something » Event : « something happened »
  38. E V E N T A R E A L

    S O D T O class HubMovedEvent implements Event {
 
 public HubMovedEvent(
 String hubId, String oldLocationId, String newLocationId ) {
 this.hubId = hubId; this.oldLocationId = oldLocationId;
 this.newLocationId = newLocationId;
 }
 
 public String hubId;
 
 public String newLocationId; public String oldLocationId;
 }
  39. A N D A L S O S E N

    T I N A B U S eventBus.dispatch( new HubMoved( "hubId", "oldLocationId", "newLocationId" ));
  40. B U T W I T H M U LT

    I P L E H A N D L E R public EventBus(List<? extends EventHandler> handlers) {
 this.handlers = handlers.groupBy( EventHandler::listenTo()
 ));
 }
 
 public <C extends Event> Void dispatch(C event) {
 handlers.get(event.getClass())
 .forEach(h -> h.handle(event));
 }
  41. A N E V E N T I S T

    H E R E S U LT O F A B U S I N E S S O P E R AT I O N public class Hub {
 
 public Event relocateTo(String newLocationId) {
 HubMovedEvent event = new HubMovedEvent(
 this.locationId.toString(),
 newLocationId
 );
 
 this.locationId = UUID.fromString(newLocationId);
 
 return event;
 }
 UUID locationId;
 private UUID id;
 }
  42. R E T U R N E D B Y

    C O M M A N D H A N D L E R S @Component
 public class RelocateHubCommandHandler implements CommandHandler<Void, RelocateHubCommand> {
 @Autowired
 public RelocateHubCommandHandler(HubRepository repo) {
 this.repository = repo;
 }
 
 @Override
 public Void handle(RelocateHubCommand command) {
 Hub hub = repository.get(command.hubId);
 hub.relocateTo(command.newLocationId);
 }
 
 public Class listenTo() {
 return RelocateHubCommand.class;
 }
 }
  43. R E T U R N E D B Y

    C O M M A N D H A N D L E R S @Component
 public class RelocateHubCommandHandler implements CommandHandler<Void, RelocateHubCommand> {
 @Autowired
 public RelocateHubCommandHandler(HubRepository repo) {
 this.repository = repo;
 }
 
 @Override
 public Tuple2<Void, List<Event> handle(RHC command) {
 Hub hub = repository.get(command.hubId);
 return Tuple.of(
 void,
 List.of(hub.relocateTo(command.newLocationId)
 );
 }
 
 public Class listenTo() {
 return RelocateHubCommand.class;
 }
 }
  44. D I S PAT C H E D B Y

    T H E C O M M A N D B U S @Component
 public class CommandBus {
 
 @Autowired
 public CommandBus(List<? extends Handler> handlers) {
 this.handlers = handlers.toMap( h -> Tuple.of(h.listenTo(), h) );
 }
 
 public <R, C extends Command<R>> R dispatch(C command<R>) {
 return handlers .get(command.getClass()) .handle(command);
 }
 
 private final Map<Class, Handler> handlers;
 }
  45. D I S PAT C H E D B Y

    T H E C O M M A N D B U S @Component public class CommandBus { @Autowired public CommandBus(
 Collection<? extends CommandHandler> handlers, EventBus eventBus) { //… this.eventBus = eventBus; } public <R, C extends Command<R>> Try<R> dispatch(C command) { return handlers.get(command.getClass()) .map(h -> execute(h, command)) .getOrElse(() -> Try.failure( new HandlerNotFoundException(command))); } private <R, C extends Command<R>> Try<R> execute(
 CommandHandler<R, C> h, C command) { return Try.of(() -> { Tuple2<R, List<Event>> result = h.handle(command); result._2.forEach(eventBus::publish); return result._1; }); } }
  46. D O M A I N E V E N

    T P R O J E C T I O N
  47. L E T ’ S G O B A C

    K I N T H E C O M M A N D H A N D L E R U S E R C O M M A N D Q U E RY Q U E RY H A N D L E R C O M M A N D H A N D L E R Q U E RY B U S C O M M A N D B U S we are here…
  48. O U R C O M M A N D

    H A N D L E R A G G R E G AT E E V E N T S A G G R E G AT E domain operations handler(state, command) = (state’, [event]) domain event
  49. A N D R E T U R N S

    T H E M public List<Event> handle(RelocateHubCommand command) {
 Hub hub = repository.getHub(command.hubId);
 events.add(hub.relocateTo(command.newLocationId));
 
 return events;
 }
 
 private List<Event> events = List.empty();
  50. T O B E D I S PAT C H

    E D B Y A M I D D L E WA R E C O M M A N D C O M M A N D H A N D L E R C O M M A N D B U S VA L I D AT I O N U N I T O F W O R K E V E N T D I S PAT C H E R S E N D E M A I L L A U N C H C O M M A N D
  51. W E C A N L I S T E

    N E V E N T T O U P D AT E O T H E R D ATA S T O R E public class HubLocationsUpdater implements EventHandler<HubMovedEvent> {
 
 public HubLocationsUpdater(Connection sql) {
 this.sql = sql;
 }
 
 public void handle(HubMovedEvent event) {
 sql.createStatement().executeUpdate(
 "UPDATE hub_location SET location_id = ? WHERE hub_id = ?"
 ).execute(event.newLocationId, event.hubId);
 }
 
 public Class<? extends Event> listenTo() {
 return HubMovedEvent.class;
 }
 }
  52. W E C A N C R E AT E

    A R E A D M O D E L I N T H E S A M E D ATA S T O R E C O M M A N D Q U E RY Q U E RY H A N D L E R C O M M A N D H A N D L E R P R O J E C T I O N U P D AT E R E V E N T D I S PAT C H E R P E R S I S T E N C E W R I T E M O D E L R E A D M O D E L
  53. O R I N D I F F E R

    E N T O N E C O M M A N D Q U E RY Q U E RY H A N D L E R C O M M A N D H A N D L E R P R O J E C T I O N U P D AT E R E V E N T D I S PAT C H E R W R I T E M O D E L R E A D M O D E L This is not event sourcing !
  54. O P E N I N G A W O

    R L D O F P O S S I B L E G E O Q U E RY S E A R C H U P D AT E R D O M A I N E V E N T G I S D B G I S U P D AT E R S Q L U P D AT E R E L A S T I C S E A R C H P O S T G R E S Q L S E A R C H Q U E RY R E L AT I O N A L Q U E RY You can adapt your datastore to your queries need plus you can even add more later…
  55. TA K E A WAY • Enable reactive business system

    • Adapt the datastore to your queries need • Suited even for legacy system
  56. E V E N T S O U R C

    I N G AT L E A S T …
  57. W E A D D S O M E M

    E TA D ATA T O E V E N T S public abstract class Event<ID, ENTITY extends AggregateRoot<ID>> {
 
 public Event(ENTITY entity) {
 aggregateId = entity.id();
 aggregateType = entity.class;
 }
 
 public ID aggregateId;
 public Class<ENTITY> aggregateType; public LocalDateTime eventDateTime = LocalDateTime.now();
 }
  58. W E A D D S O M E M

    E TA D ATA T O E V E N T S public class HubMovedEvent extends Event<UUID, Hub<UUID>> {
 
 public HubMovedEvent(
 Hub hub,
 String oldLocationId,
 String newLocationId
 ) {
 super(hub);
 this.hubId = hub.id().toString();
 this.oldLocationId = oldLocationId;
 this.newLocationId = newLocationId; 
 } 
 public String hubId;
 public String newLocationId;
 public String oldLocationId;
 
 }
  59. D O M A I N E V E N

    T A S S O U R C E O F S TAT E public class HubEventApplier implements EventApplier<Hub> {
 
 public static Hub apply(Hub root, HubMovedEvent event) {
 Hub hub = new Hub(root);
 hub.locationId = UUID.fromString(event.newLocationId);
 
 return hub;
 }
 
 } look like a reducer, no ?
  60. F R O M B U S I N E

    S S O P E R AT I O N A S M U TAT I O N public Event relocateTo(String newLocationId) {
 HubMovedEvent event = new HubMovedEvent(
 this,
 this.locationId.toString(),
 newLocationId
 );
 
 this.locationId = UUID.fromString(newLocationId);
 
 return event;
 }
  61. T O E V E N T S O U

    R C E D B U S I N E S S O P E R AT I O N S public Tuple2<Hub, Event> relocateTo(String newLocationId) {
 HubMovedEvent event = new HubMovedEvent(
 this,
 this.locationId.toString(),
 newLocationId
 );
 
 return Tuple.of(
 HubEventApplier.apply(this, event),
 event
 );
 }
  62. R E A L E V E N T A

    P P L I E R public class HubEventApplier implements EventApplier<Hub> {
 
 static Hub applyHubMovedEvent(Hub h, HubMovedEvent event) {
 Hub hub = new Hub(h);
 hub.locationId = UUID.fromString(event.newLocationId);
 return hub;
 }
 
 public static Hub apply(Hub hub, Event event) {
 return appliers .get(event.getClass()) .apply(hub, event);
 }
 
 static Map<Class, Function> appliers = HashMap.of(
 HubMovedEvent.class, (hub, event) -> applyHubMovedEvent(hub, event)
 );
 }
  63. A N D S AV E D I N E

    V E N T S T O R E C O M M A N D C O M M A N D H A N D L E R E V E N T S T O R E this is event sourcing E V E N T P E R S I S T E N C E C O M M A N D C O M M A N D H A N D L E R D B U N I T O F W O R K flush serialize
  64. W H AT I S A N E V E

    N T S T O R E ?
  65. W H AT I S A N E V E

    N T S T O R E ?
  66. E V E N T S O U R C

    I N G R E B U I L D I N G S TAT E
  67. W E G E T E V E N T

    S F O R A G I V E N A G G R E G AT E public class eventStore {
 public eventStore(Connection sql) {
 this.sql = sql;
 }
 
 public List<Event> get(String type, String id) {
 Seq<Record> records = sql.createStatement(
 "
 SELECT * FROM events 
 WHERE aggr_type = ? AND aggr_id = ? 
 ORDER BY TIMESTAMP "
 ).execute(aggregateType, aggregateId);
 
 return records.map(records -> Serializer.deserialize( record.getValue("payload"), Event.class
 ));
 }
 }
  68. A N D W E A P P LY T

    H E M public HubRepository(EventStore store) {
 
 this.store = store;
 }
 
 public Hub get(UUID uuid) {
 List<Event> events = store.get("HUB", uuid.toString());
 return events.foldLeft(
 new Hub(),
 (hub, event) -> HubEventApplier.apply(hub, event)
 );
 }
  69. TA K E A WAY • No more business memory

    loss • Pure data persistence (no more plop) • You can build new projection from past events
  70. THE BIG PICTURE COMMAND HANDLER BUS ACK/NACK EVENT DISPATCHER REPOSITORY

    PROJECTOR PROJECTOR QUERY HANDLER BUS VIEWMODEL EVENT STORE EVENTS AS A SOURCE OF TRUTH WE CAN CREATE NEW PROJECTION ON DEMAND
  71. H O W W E H A N D L

    E C O M M A N D • classic : 
 handler(state, command) = state’ • with domain event : 
 handler(state, command = (state’, [event]) • with event sourcing :
 handler([event], command) = [event]’
  72. Q & A • How do we snapshot ? •

    How do we manage version ? • How to use multiple aggregate in a handler ?
  73. T O G O F U R T H E

    R • CQRS Documents by Greg Young • The Value of Value by Rich Hickey • What if the user was a function by Andre Staltz
  74. @ L I L O B A S E |

    L G O . G R O U P Thanks !