Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
KATOWICE // Adam Polak - Event Sourcing Buildin...
Search
KarieraIT
April 07, 2018
Programming
63
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
KATOWICE // Adam Polak - Event Sourcing Building Blocks
Presentation during Kariera IT in Katowice 7.04.2018
KarieraIT
April 07, 2018
Other Decks in Programming
See All in Programming
Compiling a real-world CLI tool to native with Static Hermes
tmikov2023
0
120
[Dev Group Paris Octobre 26] Agentforce, SIforce, Claudeforce, Slackbot… on s'y retrouve ?
nabondance
0
110
phperだってパンチカードでコーディングしたい
o0h
PRO
0
340
作って学ぶSpring BootのAuto Configuration / JJUG Night Seminar 2026-10-19
rshindo
0
160
最近”作らせてる” ローカルAIアプリの話
akit37
0
470
R8 の設定見直しでアプリサイズ削減
andpad
0
220
Kotlinの15年とServer-Side Kotlin
n_takehata
1
150
Next.js: The final boss of Web Performance
guaca
0
270
UPDATE をやめる — EF Core でマスタをバージョン管理する
panda728
PRO
0
1.2k
Simple Storage Service(S3) is not simple
iwatsukayura
0
290
IBM Bob でAI駆動開発『QC-Playbook』
ichirokano2
0
110
Optunaを作ったときにやりたかったこと、その後 (2026/10/16, Optuna Meetup #3)
iwiwi
1
440
Featured
See All Featured
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
330
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.6k
Automating Front-end Workflow
addyosmani
1369
210k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
50
10k
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
270
SEO for Brand Visibility & Recognition
aleyda
0
4.8k
WENDY [Excerpt]
tessaabrams
14
40k
Navigating Weather and Climate Data
rabernat
0
560
A Tale of Four Properties
chriscoyier
163
24k
Exploring anti-patterns in Rails
aemeredith
4
540
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.9k
Leading Effective Engineering Teams in the AI Era
addyosmani
9
2.7k
Transcript
EVENT SOURCING BUILDING BLOCKS
EVENT SOURCING - BUILDING BLOCK AGENDA ▸ EVENT SOURCING BASICS
▸ WHAT ABOUT STORE? ▸ HOW TO HANDLE EVENTS? ▸ VERSIONING ▸ PERFORMANCE ▸ QUESTIONS
ADAM POLAK PHP DEVELOPER JS DEVELOPER E2E DEVELOPER
[email protected]
POLAK.ADAM1
LOOKING FOR AWESOME NODE.JS DEVS
WHAT IS EVENT SOURCING?
WHAT IS EVENT SOURCING? ▸ CREATE TASK ▸ FETCH TASKS
▸ UPDATE TASK ▸ REMOVE TASK TASKS MANAGEMENT
CLASSICAL APPROACH
TASK WHAT IS EVENT SOURCING? class Task { constructor(id, title,
description) { this._id = id; this._title = title; this._description = description; } get id() { … } set id(newId) { … } }
TASK UPDATING WHAT IS EVENT SOURCING? task.title(‘new-title’); tasksRepository.persist(task); | id
| title | description | create_date | update_date | | 1 | new-title | some-description | 2018-01-01 | 2018-01-11 | const task = tasksRepository.findById(id);
WHAT IS EVENT SOURCING? ▸ WHERE IS MY HISTORY? ▸
WHAT IF DB SCHEMA CHANGES? MIGRATIONS? ▸ HOW TO RE-TRACE MISTAKES? PROBLEMS
EVERYTHING IS AN EVENT
{ type: ‘TASK_CREATED, aggregateId: 1, version: 1, createDate: ‘2018-01-01 12:23:34’,
payload: { title: ‘some-title’ } } { type: ‘TASK_UPDATED, aggregateId: 1, version: 1, createDate: ‘2018-01-01 12:23:34’, payload: { title: ‘new-title’ } } WHAT ABOUT STORE? EVENTS
WHERE TO EMIT EVENTS?
WHAT IS EVENT SOURCING? AGGREGATE ROOT class Task { constructor(id)
{ this.id = id; this.recentEvents = []; this.aggregateVersion = 0; } static fromEvents(aggregateId, events) { … } updateTitle(title) { const taskUpdatedEvent = taskUpdatedEvent(this.id, ++this.aggregateVersion, { title }); this.recentEvents.push(taskUpdatedEvent); this.apply(taskUpdatedEvent); } apply(event) {…} getRecentEvents() { … } }
REBUILD AGGREGATE FROM EVENTS
REBUILD FROM EVENTS { type: ‘TASK_CREATED, aggregateId: 1, aggregateVersion: 1,
version: 1, createDate: ‘2018-01-01 12:23:34’, payload: { title: ‘some-title’ } } { id: 1, title: ‘some-title’, description: null, aggregateVersion: 1 } { type: ‘TASK_UPDATED, aggregateId: 1, aggregateVersion: 2, version: 1, createDate: ‘2018-01-01 12:23:34’, payload: { title: ‘new-title’ } } { id: 1, title: ‘new-title’, description: null, aggregateVersion: 2 } { type: ‘TASK_UPDATED, aggregateId: 1, aggregateVersion: 3, version: 1, createDate: ‘2018-01-01 12:23:34’, payload: { description: ‘new-description’ } } { id: 1, title: ‘new-title’, description: ‘new-description’, aggregateVersion: 3 } WHAT IS EVENT SOURCING?
WHAT IS EVENT SOURCING? REBUILD FROM EVENTS fromEvents(aggregateId, events) {
const task = new Task(aggregateId); for (let event of events) { task.apply(event); } return task; }
WHAT IS EVENT SOURCING? APPLY EVENTS apply(event) { const handler
= this.handlers.find(handler => handler.type === event.type); if (typeof handler !== 'undefined') { handler.handle.call(this, event); } else { throw new AppException(`Missing handler for event with type ${event.type}`); } } this.handlers = [ taskCreatedHandler, taskUpdatedHandler ];
WHAT IS EVENT SOURCING? HANDLE EVENT const eventTypes = require('../events/types');
module.exports = ({ type: eventTypes.TASK_CREATED, handle: function(event) { this.aggregateVersion = event.payload.aggregateVersion; this.title = event.payload.title; this.description = event.payload.description; this.owner = event.payload.owner } });
HOW TO PERSIST EVENTS?
WHAT ABOUT STORE? TASK UPDATING function patchTask(req, res, next) {
try { const events = await req.eventsStore.getEventsFor(req.params.taskId); const task = Task.fromEvents(req.params.taskId, events); if (req.body.title) { task.updateTitle(req.body.title); } req.eventsStore.push(task.getRecentEvents()); res.status(204).send(); } catch (e) { next(e); } }
RETURN GENERATED EVENTS WHAT IS EVENT SOURCING? getRecentEvents() { const
events = [...this.recentEvents]; this.recentEvents = []; return events; }
PASS EVENTS TO EVENT STORE
WHAT ABOUT STORE? EVENTS STORE class EventStore { constructor(connection, eventsBus,
upcasters) { … } async push(events) { for(let event of events) { await this.collection.insert(event); this.eventsBus.publish(event); } } getEventsFor(aggregateId) { const snapshot = await this.snapshots.findLatestFor(aggregateId); return this.eventsCollection.find({ aggregateId, createDate: { “$gt”: snapshot.createDate } }) .sort({ "payload.createDate": 1 }) .then(events => [snapshot, …events].map(event => this.upcasters.upcast(event))); } }
| id | aggregateId | aggregateVersion | type | createDate
| payload | version | | 10 | 1 | 10 | TASK_UPDATED | 2018-01-02 | { title: ‘new-title’ } | 3 | | 9 | 1 | 9 | TASK_UPDATED | 2018-01-02 | { title: ‘new-title’ } | 3 | | 8 | 1 | 8 | TASK_UPDATED | 2018-01-02 | { title: ‘new-title’ } | 2 | | 7 | 1 | 7 | TASK_UPDATED | 2018-01-02 | { title: ‘new-title’ } | 2 | | 6 | 1 | 6 | TASK_UPDATED | 2018-01-02 | { title: ‘new-title’ } | 2 | | 5 | 1 | 5 | TASK_UPDATED | 2018-01-02 | { title: ‘new-title’ } | 2 | | 4 | 1 | 4 | TASK_UPDATED | 2018-01-02 | { title: ‘new-title’ } | 1 | | 3 | 1 | 3 | TASK_UPDATED | 2018-01-02 | { title: ‘new-title’ } | 1 | | 2 | 1 | 2 | TASK_UPDATED | 2018-01-02 | { title: ‘new-title’ } | 1 | | 1 | 1 | 1 | TASK_CREATED | 2018-01-02 | { title: ‘new-title’ } | 1 | EVENTS STORE WHAT ABOUT STORE?
HOW TO HANDLE EVENTS? ▸ HOW DO WE FILTER TASKS?
▸ HOW DO I FETCH TASK? PROBLEMS
EVENTS BUS class EventStore { constructor(connection, eventsBus, upcasters) { …
} async push(events) { for(let event of events) { await this.collection.insert(event); this.eventsBus.publish(event); } } getEventsFor(aggregateId) { … } } HOW TO HANDLE EVENTS?
EVENTS BUS HOW TO HANDLE EVENTS? { type: ‘TASK_CREATED, aggregateId:
1, aggregateVersion: 1, version: 1, createDate: ‘2018-01-01 12:23:34’, payload: { title: ‘some-title’ } } EVENTS BUS PROJECTORS MAILER HANDLER
EVENTS BUS HOW TO HANDLE EVENTS? class EventBus { constructor()
{ this.subscribers = []; } subscribe(event, handler) { if (typeof this.subscribers[`${event}`] === 'undefined') { this.subscribers[`${event}`] = []; } this.subscribers[`${event}`].push(handler); } publish(event) { if (this.subscribers[`${event.type}`]) { this.subscribers[`${event.type}`].forEach((handler) => handler(event)); } } }
CREATE PROJECTION
HOW TO HANDLE EVENTS? EVENTS PROJECTION const handler = connection
=> event => { if (event.type === eventTypes.TASK_CREATED) { return connection.collection(COLLECTION).insert({ … }); } if (event.type === eventTypes.TASK_UPDATED) { return connection.collection(COLLECTION).update({ … }); } };
HOW TO HANDLE EVENTS? EVENTS PROJECTION | id | title
| description | create_date | update_date | | 1 | new-title | some-description | 2018-01-01 | 2018-01-11 |
REBUILD PROJECTION
HOW TO HANDLE EVENTS? READMODEL REBUILD mongoClient .connect(`mongodb://${process.env.DB_HOST}:${process.env.DB_PORT}/tsh`) .then(async (connection)
=> { const eventStore = new EventStore(connection, new EventBus(), new Upcasters()); const projector = activeTasksProjector.rebuilder(connection); const events = await eventStore.getAll(); projector(events).then(() => process.exit(0)); });
VERSIONING?
EVENTS VERSIONING ▸ REBUILD EVENT STORE (NOT A GOOD IDEA)
▸ UPCAST AT RUNTIME VERSIONING
UPCASTING
EVENTS VERSIONING UPCASTING { type: ‘TASK_CREATED, aggregateId: 1, aggregateVersion: 1,
version: 1, createDate: ‘2018-01-01 12:23:34’, payload: { title: ‘some-title’ } } { type: ‘TASK_CREATED, aggregateId: 1, aggregateVersion: 1, version: 2, createDate: ‘2018-01-01 12:23:34’, payload: { title: ‘some-title’, status: ‘pending’ } }
EVENTS VERSIONING UPCASTING { type: ‘TASK_CREATED, aggregateId: 1, aggregateVersion: 1,
version: 1, createDate: ‘2018-01-01 12:23:34’, payload: { title: ‘some-title’ } } { type: ‘TASK_CREATED, aggregateId: 1, aggregateVersion: 1, version: 2, createDate: ‘2018-01-01 12:23:34’, payload: { title: ‘some-title’, status: ‘pending’ } } UPCASTER
EVENTS VERSIONING UPCASTER class EventStore { constructor(connection, eventsBus, upcasters) {
… } async push(events) { for(let event of events) { await this.collection.insert(event); this.eventsBus.publish(event); } } getEventsFor(aggregateId) { const snapshot = await this.snapshots.findLatestFor(aggregateId); return this.eventsCollection.find({ aggregateId, createDate: { “$gt”: snapshot.createDate } }) .sort({ "payload.createDate": 1 }) .then(events => events.map(event => this.upcasters.upcast(event))); } }
EVENTS VERSIONING UPCASTER const eventTypes = require('../task/events/types'); module.exports = {
canUpcast: (event) => event.version === 1 && event.type === eventTypes.TASK_CREATED, upcast: (event) => ({ ...event, version: 2, payload: { ...event.payload, status: 'pending' } }) };
PERFORMANCE
MORE EVENTS = PERFORMANCE LOSS
SNAPSHOTS
PERFORMANCE SNAPSHOTS { type: ‘TASK_CREATED, aggregateId: 1, aggregateVersion: 1, version:
1, createDate: ‘2018-01-01 12:23:34’, payload: { title: ‘some-title’ } } { type: ‘TASK_UPDATED, aggregateId: 1, aggregateVersion: 2, version: 1, createDate: ‘2018-01-01 12:23:34’, payload: { description: ‘new-description’ } } { type: ‘TASK_SNAPSHOT, aggregateId: 1, aggregateVersion: 2, version: 1, createDate: ‘2018-01-01 12:23:34’, payload: { title: ‘some-title’, description: ‘new-description’ } }
GET SNAPSHOT
APPLY NEWER EVENTS
class EventStore { async push(events) { for(let event of events)
{ (…) if (event.aggregateVersion % SNAPSHOT_COUNTER === 0) { await this.snapshots.createForAggregate(event.aggregateId); } } } getEventsFor(aggregateId) { const snapshot = await this.snapshots.findLatestFor(aggregateId); return this.eventsCollection.find({ aggregateId, createDate: { “$gt”: snapshot.createDate } }) .sort({ "payload.createDate": 1 }) .then(events => [snapshot, …events].map(event => this.upcasters.upcast(event))); } } SNAPSHOTS PERFORMANCE
QUESTIONS ?
THANK YOU