Slide 1

Slide 1 text

C Q R S , E V E N T S O U R C I N G & F U N C T I O N A L P R O G R A M M I N G T H E B U Z Z W O R D C O N F E R E N C E

Slide 2

Slide 2 text

@ L I L O B A S E – W W W. A R P I N U M . F R Arnaud LEMAIRE | Arpinum

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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 »

Slide 5

Slide 5 text

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 {
 
 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)

Slide 6

Slide 6 text

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 requestBody = Serializer.deserialize(request.body()); 
 return this.commandBus.dispatch( new RelocateHubCommand( requestBody.get("hubId"), requestBody.get("newLocationId") ));
 }
 }

Slide 7

Slide 7 text

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 requestBody = Serializer.deserialize(request.body()); 
 return this.commandBus.dispatch( new RelocateHubCommand( requestBody.get("hubId"), requestBody.get("newLocationId") ));
 }
 } Almost no stickiness to a framework

Slide 8

Slide 8 text

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 dispatch(C command) {
 return handlers .get(command.getClass()) .handle(command);
 }
 
 private final Map handlers;
 }

Slide 9

Slide 9 text

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 dispatch(C command) {
 return handlers .get(command.getClass()) .handle(command);
 }
 
 private final Map handlers;
 }

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

M I D D L E WA R E @Component public class BusValidationMiddleware implements BusMiddleware> { @Autowired
 public BusValidationMiddleware( BusMiddleware next, Validator validator ) {
 this.next = next;
 this.validator = validator;
 }
 
 public T handle(Message message) {
 Violation violations = validator.validate(message);
 if(violations.size() > 1)
 throw new ArgumentException(violations.toArray());
 
 return next.handle(message);
 }
 }

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

C Q R S T H E C O M M A N D

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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 {
 TRoot get(TId id);
 
 void add(TRoot root);
 
 void delete(TRoot root);
 
 List getAll();
 } The persistence mechanism save snapshot of the application state

Slide 16

Slide 16 text

P E R S I S T E N C E A B S T R A C T I O N public class HubRepository implements Repository {
 
 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 getAll()
 return store.findAll();
 } We fetch and save aggregate as a whole

Slide 17

Slide 17 text

C O M M A N D H A N D L I N G @Component
 public class RelocateHubCommandHandler implements CommandHandler {
 @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;
 }
 }

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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");
 }

Slide 21

Slide 21 text

C Q R S T H E Q U E RY

Slide 22

Slide 22 text

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 {
 
 public FindHubByLocationPath(
 String locationPath, ) {
 this.locationPath = locationPath;
 }
 
 @NotEmpty
 public String locationPath;
 
 }

Slide 23

Slide 23 text

Q U E RY H A N D L I N G @Component
 public class FindHubByLocationPathQueryHander extends SqlHandler {
 
 @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

Slide 24

Slide 24 text

Q U E RY H A N D L I N G @Component
 public class FindHubByLocationPathQueryHander extends SqlHandler {
 
 @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);
 }
 }

Slide 25

Slide 25 text

C Q R S W R A P P I N G U P

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

F U N C T I O N A L P R O G R A M M I N G S TAT E D E F I N I T I O N

Slide 29

Slide 29 text

What is a state ?

Slide 30

Slide 30 text

A VA L U E O V E R T I M E String var; 
 var = "something";
 var = "something else"; something something else time

Slide 31

Slide 31 text

A P P L I C AT I O N S H A R E D S TAT E M A N A G E R T H E D ATA B A S E

Slide 32

Slide 32 text

T H I S L E A D T O P L O P P L A C E O R I E N T E D P R O G R A M M I N G « Where new informations replace old » - R I C H H I C K E Y

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

This is why a debugger is a time machine

Slide 35

Slide 35 text

A N D B U S I N E S S M E M O RY L O S S - « What was the last dropped shopping cart ? » - « We don’t know, the data have already been replaced »

Slide 36

Slide 36 text

T H E S O L U T I O N ? W E R E M O V E T H E T I M E F R O M T H E E Q U AT I O N « VA L U E O R I E N T E D P R O G R A M M I N G » time x value = variable value = constant

Slide 37

Slide 37 text

F U N C T I O N A L PA R A D I G M « Functional programming makes concurrency composable — something that’s virtually impossible with other programming paradigms » - B A R T O S Z M I L E W S K I

Slide 38

Slide 38 text

F U N C T I O N A L P R O G R A M M I N G S TAT E M A N A G E M E N T

Slide 39

Slide 39 text

Y O U C A N C R E AT E N E W VA L U E VA R I A B L E C O N S TA N T

Slide 40

Slide 40 text

Y O U C A N C R E AT E N E W VA L U E VA R I A B L E C O N S TA N T time

Slide 41

Slide 41 text

Y O U C A N C R E AT E N E W VA L U E VA R I A B L E C O N S TA N T but older values still exists time

Slide 42

Slide 42 text

S H O U L D I P L O P ? public class Circle {
 
 public Circle(int radius, int posX, int posY) {
 this.radius = radius;
 this.posX = posX;
 this.posY = posY;
 }
 
 public void move(int posX, int posY) {
 this.posX = posX;
 this.posY = posY;
 }
 
 private int radius;
 private int posX;
 private int posY;
 }

Slide 43

Slide 43 text

O R S H O U L D I N O T ? public class Circle {
 
 public Circle(int radius, int posX, int posY) {
 this.radius = radius;
 this.posX = posX;
 this.posY = posY;
 }
 
 public Circle move(int posX, int posY) {
 return new Circle(radius, posX, posY);
 }
 
 private int radius;
 private int posX;
 private int posY;
 }

Slide 44

Slide 44 text

I F I N E E D S TAT E O R C H E S T R AT I O N ? const state = [{
 id: 3,
 story: {
 author: "Arnaud LEMAIRE",
 text: "Reducer FTW"
 },
 readyForPublishing: false
 }];
 
 const publishStory = (storyId) => ({
 storyId
 });

Slide 45

Slide 45 text

I F I N E E D S TAT E O R C H E S T R AT I O N ? const publishStoryReducer = (state, action) => {
 const [storyToPublish, restOfStories] = state.partitionBy( i => i.id === action.storyId );
 
 return [
 ...restOfStories,
 {
 ...storyToPublish[0],
 readyForPublishing: true
 }
 ];
 };

Slide 46

Slide 46 text

F U N C T I O N A L C O R E / I M P E R AT I V E S H E L L B U S I N E S S D O M A I N ( F U N C T I O N A L ) IMPERATIVE SHELL

Slide 47

Slide 47 text

TA K E A WAY • Avoid a whole category of bugs by removing state • Enable concurrency composabilitty • Push state management to the boundaries

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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 »

Slide 50

Slide 50 text

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;
 }

Slide 51

Slide 51 text

A N D A L S O S E N T I N A B U S eventBus.dispatch( new HubMoved( "hubId", "oldLocationId", "newLocationId" ));

Slide 52

Slide 52 text

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.toMap( h -> Tuple.of(h.listenTo(), h) );
 }
 
 public Void dispatch(C event) {
 handlers.filter(t -> t._1 == event.getClass())
 .forEach(h -> h.handle(event));
 }

Slide 53

Slide 53 text

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> {
 
 public Event(ENTITY entity) {
 aggregateId = entity.id();
 aggregateType = entity.class;
 }
 
 public ID aggregateId;
 public Class aggregateType; public LocalDateTime eventDateTime = LocalDateTime.now();
 }

Slide 54

Slide 54 text

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> {
 
 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;
 
 }

Slide 55

Slide 55 text

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,
 this.locationId.toString(),
 newLocationId
 );
 
 this.locationId = UUID.fromString(newLocationId);
 
 return event;
 }
 UUID locationId;
 private UUID id;
 }

Slide 56

Slide 56 text

D O M A I N E V E N T P R O J E C T I O N

Slide 57

Slide 57 text

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…

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

A N D R E T U R N S T H E M public List handle(RelocateHubCommand command) {
 Hub hub = repository.getHub(command.hubId);
 events.add(hub.relocateTo(command.newLocationId));
 
 return events;
 }
 
 private List events = List.empty();

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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 {
 
 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;
 }
 }

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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 !

Slide 64

Slide 64 text

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…

Slide 65

Slide 65 text

TA K E A WAY • Enable reactive business system • Adapt the datastore to your queries need • Suited even for legacy system

Slide 66

Slide 66 text

E V E N T S O U R C I N G AT L E A S T …

Slide 67

Slide 67 text

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 {
 
 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 ?

Slide 68

Slide 68 text

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;
 }

Slide 69

Slide 69 text

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 relocateTo(String newLocationId) {
 HubMovedEvent event = new HubMovedEvent(
 this,
 this.locationId.toString(),
 newLocationId
 );
 
 return Tuple.of(
 HubEventApplier.apply(this, event),
 event
 );
 }

Slide 70

Slide 70 text

R E A L E V E N T A P P L I E R public class HubEventApplier implements EventApplier {
 
 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 appliers = HashMap.of(
 HubMovedEvent.class, (hub, event) -> applyHubMovedEvent(hub, event)
 );
 }

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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 get(String type, String id) {
 Seq records = sql.createStatement(
 "SELECT * FROM events WHERE aggr_type = ? AND aggr_id = ?"
 ).execute(aggregateType, aggregateId);
 
 return records.map(records -> Serializer.deserialize( record.getValue("payload"), Event.class
 ));
 }
 }

Slide 76

Slide 76 text

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 events = store.get("HUB", uuid.toString());
 return events.foldLeft(
 new Hub(),
 (hub, event) -> HubEventApplier.apply(hub, event)
 );
 }

Slide 77

Slide 77 text

TA K E A WAY • No more business memory loss • Pure data persistence (no more plop) • You can build new projection from past events

Slide 78

Slide 78 text

F O L D E R H I E R A R C H Y

Slide 79

Slide 79 text

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]’

Slide 80

Slide 80 text

Q & A • How do we snapshot ? • How do we manage version ? • How to use multiple aggregate in a handler ?

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

@ L I L O B A S E | A R P I N U M . F R Thanks !