SlideShare a Scribd company logo
1 of 70
Download to read offline
EVENT SOURCE
EVERYTHING!
EVENT SOURCE EVERYTHING!
AGENDA
▸ EVENT SOURCING BASICS
▸ WHAT ABOUT STORE?
▸ HOW TO HANDLE EVENTS?
▸ VERSIONING
▸ PERFORMANCE
▸ QUESTIONS
ADAM POLAK
HEAD OF NODE.JS
ADAM.POLAK@THESOFTWAREHOUSE.PL
POLAK.ADAM1
WHAT IS
EVENT SOURCING?
▸ CREATE EMPLOYEE
▸ ISSUE VACATION REQUEST
▸ APPROVE/DECLINE VACATION REQUEST
HR MANAGEMENT
EVENT SOURCE EVERYTHING!
NON EVENT-BASED
APPROACH
EMPLOYEE
class Employee {
constructor(id, firstName, lastName, vacationRequests) {
this._id = id;
this._firstName = firstName;
this._lastName = lastName;
this._vacationRequests = vacationRequests;
}
issueVacationRequest(from, to, comment) { // do business logic and domain validation here then return vacation req or event}
}
EVENT SOURCE EVERYTHING!
ISSUE VACATION REQUEST
const vacationRequest = employee.issueValidationRequest(‘2018-10-01’, ‘2018-11-01’, ‘Japan, here I come!’);
vacationRequestsRepository.persist(vacationRequest); // map vacation request from domain model to db model
const employee = employeeRepository.findById(id); // map from db model to domain model
EVENT SOURCE EVERYTHING!
| id | employee_id | from | to | comment | create_date | update_date |
| 1 | some-uuid | 2018-10-01 | 2018-11-01 | Japan, here I come! | 2018-01-11 | 2018-01-11 |
IT’S ALL ABOUT
CURRENT STATE
EVENT-BASED
APPROACH
▸ EMPLOYEE EMPLOYED
▸ VACATION REQUEST ISSUED
▸ VACATION REQUEST APPROVED
▸ VACATION REQUEST DECLINED
▸ EMPLOYEE ASSIGNED TO PROJECT
▸ EMPLOYEE DETAILS UPDATED
EVENTS
EVENT SOURCE EVERYTHING!
EVERYTHING IS
AN EVENT
HOW TO DISPLAY
EVENTS?
IT’S CQRS
ON STEROIDS
REBUILD
FROM EVENTSCOMMAND OPERATIONS
WRITE SIDE
EMIT
EVENTS
EVENT STORE
EVENT SOURCE EVERYTHING!
REQUEST
HANDLING
REBUILD
FROM EVENTSCOMMAND OPERATIONS
EMIT
EVENTS
EVENT STORE
EVENT SOURCE EVERYTHING!
ISSUE VACATION REQUEST
router.post('/:id/vacations', celebrate({
body: Joi.object().keys({
from: Joi.date().required(),
to: Joi.date().required(),
comment: Joi.string().allow(null)
})
}), async (req, res, next) => {
const employeeId = req.params.id;
try {
const events = await eventStore.getEventsFor(employeeId);
const employee = Employee.fromEvents(employeeId, events);
employee.issueVacationRequest(req.body.from, req.body.to, req.body.comment);
await eventStore.saveStream(employee.getRecentEvents()).then(events => eventBus.publish(events));
return res.status(204).send();
} catch (error) { next(error);}
});
EVENT SOURCE EVERYTHING!
ISSUE VACATION REQUEST - GET EVENTS FROM STORE
router.post('/:id/vacations', celebrate({
body: Joi.object().keys({
from: Joi.date().required(),
to: Joi.date().required(),
comment: Joi.string().allow(null)
})
}), async (req, res, next) => {
const employeeId = req.params.id;
try {
const employee = Employee.fromEvents(employeeId, events);
employee.issueVacationRequest(req.body.from, req.body.to, req.body.comment);
await eventStore.saveStream(employee.getRecentEvents()).then(events => eventBus.publish(events));
return res.status(204).send();
} catch (error) { next(error);}
});
const events = await eventStore.getEventsFor(employeeId);
EVENT SOURCE EVERYTHING!
EVENT
STORE
▸ SINGLE SOURCE OF TRUTH
▸ USED ONLY ON WRITE SIDE
▸ USED DURING READ MODEL REBUILD
EVENT STORE
EVENT SOURCE EVERYTHING!
{
type: ‘EMPLOYEE_EMPLOYED’,
aggregateId: my-uuid,
version: 1,
createDate: ‘2018-01-01 12:23:34’,
payload: {
firstName: ‘Adam’,
lastName: ‘Polak’,
occupation: ‘Head of Node.js’
}
}
EVENTS
{
type: ‘VACATION_REQUEST_ISSUED’,
aggregateId: my-uuid,
version: 1,
createDate: ‘2018-01-01 13:23:34’,
payload: {
id: request-uuid,
from: ‘2018-10-01’,
to: ‘2018-11-01’,
days: 24
}
}
{
type: ‘EMPLOYEE_ASSIGNED’
aggregateId: my-uuid,
version: 1,
createDate: ‘2018-01-01 14:23:34’,
payload: {
projectId: project-uuid
}
}
EVENT SOURCE EVERYTHING!
▸ EVENT STORE
▸ MONGODB
▸ POSTGRESQL
POSSIBLE SOLUTIONS
EVENT SOURCE EVERYTHING!
class EventStoreRepository {
constructor(db, upcasters) { … }
async saveStream(events) { … }
getAllEvents() { … }
getEventsFor(aggregateId) {
return this.eventsCollection.find({ aggregateId })
.sort({ "payload.createDate": 1 })
.then(events => events.map(event => this.upcasters.upcast(event)));
}
}
REPOSITORY
EVENT SOURCE EVERYTHING!
class EventStoreRepository {
constructor(db, upcasters) { … }
async saveStream(events) { … }
getAllEvents() { … }
}
REPOSITORY
getEventsFor(aggregateId) {
return this.eventsCollection.find({ aggregateId })
.sort({ "payload.createDate": 1 })
.then(events => events.map(event => this.upcasters.upcast(event)));
}
EVENT SOURCE EVERYTHING!
ISSUE VACATION REQUEST - REBUILD AGGREGATE FROM EVENTS
router.post('/:id/vacations', celebrate({
body: Joi.object().keys({
from: Joi.date().required(),
to: Joi.date().required(),
comment: Joi.string().allow(null)
})
}), async (req, res, next) => {
const employeeId = req.params.id;
try {
employee.issueVacationRequest(req.body.from, req.body.to, req.body.comment);
a await eventStore.saveStream(employee.getRecentEvents()).then(events => eventBus.publish(events));
return res.status(204).send();
} catch (error) { next(error);}
});
const events = await eventStore.getEventsFor(employeeId);
const employee = Employee.fromEvents(employeeId, events);
EVENT SOURCE EVERYTHING!
AGGREGATE ROOT
class Employee {
constructor(id) {
this.id = id;
this.recentEvents = [];
}
static fromEvents(aggregateId, events) { … }
issueVacationRequest(from, to, comment) { … }
apply(event) {…}
getRecentEvents() { … }
}
EVENT SOURCE EVERYTHING!
AGGREGATE ROOT - REBUILD FROM EVENTS
class Employee {
constructor(id) {
this.id = id;
this.recentEvents = [];
}
issueVacationRequest(from, to, comment) { … }
apply(event) {…}
getRecentEvents() { … }
}
static fromEvents(aggregateId, events) { … }
EVENT SOURCE EVERYTHING!
AGGREGATE ROOT - REBUILD FROM EVENTS
{
type: ‘EMPLOYEE_EMPLOYED’,
aggregateId: my-uuid,
version: 1,
createDate: ‘2018-01-01 12:23:34’,
payload: {
firstName: ‘Adam’,
lastName: ‘Polak’,
occupation: ‘JS Developer’
}
}
{
type: ‘EMPLOYEE_OCCUPATION_CHANGED’,
aggregateId: my-uuid,
version: 1,
createDate: ‘2018-01-01 13:23:34’,
payload: {
occupation: ‘Head of Node.js’
}
}
{
id: my-uuid,
firstName: ‘Adam’,
lastName: ‘Polak’,
occupation: ‘JS Developer’
}
{
id: my-uuid,
firstName: ‘Adam’,
lastName: ‘Polak’,
occupation: ‘Head of Node.js’
}
EVENT SOURCE EVERYTHING!
static fromEvents(id, events) {
return events.reduce((employee, event) => {
employee.apply(event);
return employee;
}, new Employee(id))
}
AGGREGATE ROOT - REBUILD FROM EVENTS
EVENT SOURCE EVERYTHING!
apply(event) {
if (event.type === ‘EMPLOYEE_EMPLOYED’) {
this.firstName = event.payload.firstName;
this.lastName = event.payload.lastName;
this.occupation = event.payload.occupation;
} else {
throw new AppException(`Missing handler for event with type ${event.type}`);
}
}
AGGREGATE ROOT - APPLY EVENTS
EVENT SOURCE EVERYTHING!
ISSUE VACATION REQUEST - PERFORM ACTION ON DOMAIN MODEL
router.post('/:id/vacations', celebrate({
body: Joi.object().keys({
from: Joi.date().required(),
to: Joi.date().required(),
comment: Joi.string().allow(null)
})
}), async (req, res, next) => {
const employeeId = req.params.id;
try {
await eventStore.saveStream(employee.getRecentEvents()).then(events => eventBus.publish(events));
return res.status(204).send();
} catch (error) { next(error);}
});
const events = await eventStore.getEventsFor(employeeId);
const employee = Employee.fromEvents(employeeId, events);
employee.issueVacationRequest(req.body.from, req.body.to, req.body.comment);
EVENT SOURCE EVERYTHING!
ISSUE VACATION REQUEST
issueVacationRequest(from, to, comment) {
const requestId = uuid.v4();
const days = VacationRequest.calculateDays(from, to);
if (this.vacationDays.availableDays < days) {
throw new Error(`Not enough free days to take vacation for '${days}' days`);
}
const employeeIssuedVacationRequestEvent = employeeIssuedVacationRequest.create( … );
this.recentEvents.push(employeeIssuedVacationRequestEvent);
this.apply(employeeIssuedVacationRequestEvent);
}
EVENT SOURCE EVERYTHING!
ISSUE VACATION REQUEST - EMIT EVENT FROM AGGREGATE
issueVacationRequest(from, to, comment) {
const requestId = uuid.v4();
const days = VacationRequest.calculateDays(from, to);
if (this.vacationDays.availableDays < days) {
throw new Error(`Not enough free days to take vacation for '${days}' days`);
}
}
const employeeIssuedVacationRequestEvent = employeeIssuedVacationRequest.create( … );
this.recentEvents.push(employeeIssuedVacationRequestEvent);
this.apply(employeeIssuedVacationRequestEvent);
EVENT SOURCE EVERYTHING!
ISSUE VACATION REQUEST - SAVE RECENT EVENTS
router.post('/:id/vacations', celebrate({
body: Joi.object().keys({
from: Joi.date().required(),
to: Joi.date().required(),
comment: Joi.string().allow(null)
})
}), async (req, res, next) => {
const employeeId = req.params.id;
try {
return res.status(204).send();
} catch (error) { next(error);}
});
const events = await eventStore.getEventsFor(employeeId);
const employee = Employee.fromEvents(employeeId, events);
employee.issueVacationRequest(req.body.from, req.body.to, req.body.comment);
await eventStore.saveStream(employee.getRecentEvents()).then(events => eventBus.publish(events));
EVENT SOURCE EVERYTHING!
getRecentEvents() {
const events = [...this.recentEvents];
this.recentEvents = [];
return events;
}
AGGREGATE ROOT - GET RECENT EVENTS
EVENT SOURCE EVERYTHING!
PASS EVENTS TO
EVENT STORE
class EventStoreRepository {
constructor(db, upcasters) { … }
async saveStream(events) {
await this.eventsCollection.insert(events);
return events;
}
getAllEvents() { … }
getEventsForm(aggregateId) { … }
}
REPOSITORY - SAVE STREAM
EVENT SOURCE EVERYTHING!
class EventStoreRepository {
constructor(db, upcasters) { … }
async saveStream(events) {
await this.eventsCollection.insert(events);
}
getAllEvents() { … }
getEventsForm(aggregateId) { … }
}
REPOSITORY - SAVE STREAM
EVENT SOURCE EVERYTHING!
// SAVE IN SINGLE “TRANSACTION”!
async saveStream(events) {
await this.eventsCollection.insert(events);
return events;
}
| id | aggregateId | aggregateVersion | type | createDate | payload | version |
| 10 | 1 | 10 | EMPLOYEE_UPDATED | 2018-01-11 | { ………………………………… } | 4 |
| 9 | 1 | 9 | EMPLOYEE_UPDATED | 2018-01-10 | { ………………………………… } | 4 |
| 8 | 1 | 8 | EMPLOYEE_UPDATED | 2018-01-09 | { ………………………………… } | 3 |
| 7 | 1 | 7 | EMPLOYEE_UPDATED | 2018-01-08 | { ………………………………… } | 3 |
| 6 | 1 | 6 | EMPLOYEE_UPDATED | 2018-01-07 | { ………………………………… } | 2 |
| 5 | 1 | 5 | EMPLOYEE_UPDATED | 2018-01-06 | { ………………………………… } | 2 |
| 4 | 1 | 4 | EMPLOYEE_UPDATED | 2018-01-05 | { ………………………………… } | 1 |
| 3 | 1 | 3 | EMPLOYEE_UPDATED | 2018-01-04 | { ………………………………… } | 1 |
| 2 | 1 | 2 | EMPLOYEE_UPDATED | 2018-01-03 | { ………………………………… } | 1 |
| 1 | 1 | 1 | EMPLOYEE_EMPLOYED | 2018-01-02 | { ………………………………… } | 1 |
EVENT STORE - PERSISTED EVENTS
EVENT SOURCE EVERYTHING!
▸ HOW TO PAGINATE?
▸ HOW TO DISPLAY SINGLE EMPLOYEE?
▸ HOW TO DISPLAY FILTER EMPLOYEES?
▸ HOW TO DO BASIC UI OPERATIONS?
EVENT SOURCE EVERYTHING!
PROBLEMS
EVENT
BUS
PROJECTION STORAGE UI
EVENT SOURCE EVERYTHING!
READ SIDE
EVENT
BUS
EVENT SOURCE EVERYTHING!
READ SIDE
EVENT BUS
router.post('/:id/vacations', celebrate({
body: Joi.object().keys({
from: Joi.date().required(),
to: Joi.date().required(),
comment: Joi.string().allow(null)
})
}), async (req, res, next) => {
const employeeId = req.params.id;
try {
return res.status(204).send();
} catch (error) { next(error);}
});
const events = await eventStore.getEventsFor(employeeId);
const employee = Employee.fromEvents(employeeId, events);
employee.issueVacationRequest(req.body.from, req.body.to, req.body.comment);
await eventStore.saveStream(employee.getRecentEvents()).then(events => eventBus.publish(events));
EVENT SOURCE EVERYTHING!
EVENT BUS
EVENT BUS
PROJECTORS
MAILER
HANDLER
EVENT SOURCE EVERYTHING!
{
type: ‘EMPLOYEE_EMPLOYED’,
aggregateId: my-uuid,
version: 1,
createDate: ‘2018-01-01 12:23:34’,
payload: {
firstName: ‘Adam’,
lastName: ‘Polak’,
occupation: ‘Head of Node.js’
}
}
ANY MESSAGING
SYSTEM
EVENTS BUS
class EventBus {
constructor() { this.subscribers = []; }
subscribe(event, handler) {
if (typeof this.subscribers[`${event}`] === 'undefined') {
this.subscribers[`${event}`] = [];
}
this.subscribers[`${event}`].push(handler);
}
publish(events) {
events.forEach(event => {
if (this.subscribers[`${event.type}`]) {
this.subscribers[`${event.type}`].forEach((handler) => handler(event));
}
}
}
}
EVENT SOURCE EVERYTHING!
EVENT
BUS
PROJECTION STORAGE UI
EVENT SOURCE EVERYTHING!
PROJECTOR
const handler = connection => event => {
if (event.type === eventTypes.EMPLOYEE_EMPLOYED) {
return connection.collection(‘employees’).insert({ … });
}
if (event.type === eventTypes.EMPLOYEE_OCCUPATION_CHANGED) {
return connection.collection(‘employees’).update({ … });
}
};
EVENT SOURCE EVERYTHING!
UI CHANGED?
REBUILD PROJECTION
FROM SCRATCH
PROJECTION REBUILD
app.then(async (db) => {
const eventStoreRepository = new EventStoreRepository(db, new Upcasters());
const projector = activeTasksProjector.rebuilder(db);
const events = await eventStoreRepository.getAll();
projector(events).then(() => process.exit(0));
});
EVENT SOURCE EVERYTHING!
▸ IT IS CQRS
▸ REBUILD AGGREGATE FROM PREVIOUS EVENTS
▸ EVERY OPERATION EMITS DOMAIN EVENTS
▸ STORE EVENTS IN EVENT STORE
▸ PUBLISH THEM THROUGH EVENT BUS
▸ BUILD PROJECTIONS FROM PUBLISHED EVENTS
▸ REBUILD PROJECTIONS IF NECESSARY
EVENT SOURCE EVERYTHING!
SUMMARY
CHALLENGES!
VERSIONING
▸ REBUILD EVENT STORE (NOT A GOOD IDEA)
▸ UPCAST AT RUNTIME
EVENT SOURCE EVERYTHING!
VERSIONING
UPCASTING
UPCASTING
{
type: ‘REQUEST_APPROVED’,
aggregateId: 1,
version: 1,
createDate: ‘2018-01-01 12:23:34’,
payload: {
id: ‘some-request-id’
}
}
{
type: ‘REQUEST_APPROVED’,
aggregateId: 1,
version: 2,
createDate: ‘2018-01-01 12:23:34’,
payload: {
id: ‘some-request-id’,
approver: ‘Adam Polak’
}
}
EVENT SOURCE EVERYTHING!
UPCASTING
{
type: ‘REQUEST_APPROVED’,
aggregateId: 1,
version: 1,
createDate: ‘2018-01-01 12:23:34’,
payload: {
id: ‘some-request-id’
}
}
{
type: ‘REQUEST_APPROVED’,
aggregateId: 1,
version: 2,
createDate: ‘2018-01-01 12:23:34’,
payload: {
id: ‘some-request-id’,
approver: ‘Adam Polak’
}
}
EVENT SOURCE EVERYTHING!
UPCASTER
class EventStoreRepository {
constructor(db, upcasters) { … }
async saveStream(events) { … }
getAllEvents() { … }
}
getEventsFor(aggregateId) {
return this.eventsCollection.find({ aggregateId })
.sort({ "payload.createDate": 1 })
.then(events => events.map(event => this.upcasters.upcast(event)));
}
EVENT SOURCE EVERYTHING!
EVENT STORE REPOSITORY
UPCASTER
const eventTypes = require('../employee/events/types');
module.exports = {
canUpcast: (event) => event.version === 1 && event.type === eventTypes.VACATION_REQUEST_APPROVED,
upcast: (event) => ({
...event,
version: 2,
payload: {
...event.payload,
approver: ‘hr-member‘
}
})
};
EVENT SOURCE EVERYTHING!
PERFORMANCE
MORE EVENTS
=
PERFORMANCE LOSS

▸ AVOID LONG LIVING AGGREGATES
▸ SNAPSHOTS
EVENT SOURCE EVERYTHING!
PERFORMANCE IMPROVEMENTS
SNAPSHOTS
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 13:23:34’,
payload: {
description: ‘new-description’
}
}
{
type: ‘TASK_SNAPSHOT,
aggregateId: 1,
aggregateVersion: 2,
version: 1,
createDate: ‘2018-01-01 14:23:34’,
payload: {
title: ‘some-title’,
description: ‘new-description’
}
}
EVENT SOURCE EVERYTHING!
GET SNAPSHOT
APPLY NEWER
EVENTS
QUESTIONS ?
THANK YOU

More Related Content

What's hot

The Open Web and what it means
The Open Web and what it meansThe Open Web and what it means
The Open Web and what it meansRobert Nyman
 
Learning React: Facebook's Javascript Library For Building User Interfaces
Learning React: Facebook's Javascript Library For Building User InterfacesLearning React: Facebook's Javascript Library For Building User Interfaces
Learning React: Facebook's Javascript Library For Building User InterfacesKen Wheeler
 
Promises are so passé - Tim Perry - Codemotion Milan 2016
Promises are so passé - Tim Perry - Codemotion Milan 2016Promises are so passé - Tim Perry - Codemotion Milan 2016
Promises are so passé - Tim Perry - Codemotion Milan 2016Codemotion
 
Introductionandgreetings
IntroductionandgreetingsIntroductionandgreetings
IntroductionandgreetingsPozz ZaRat
 
Introduction to ReactJS and Redux
Introduction to ReactJS and ReduxIntroduction to ReactJS and Redux
Introduction to ReactJS and ReduxBoris Dinkevich
 
My Top 5 APEX JavaScript API's
My Top 5 APEX JavaScript API'sMy Top 5 APEX JavaScript API's
My Top 5 APEX JavaScript API'sRoel Hartman
 
Qualidade de vida: Com Zabbix e API
Qualidade de vida: Com Zabbix e APIQualidade de vida: Com Zabbix e API
Qualidade de vida: Com Zabbix e APILuiz Sales
 
Integrating React.js with PHP projects
Integrating React.js with PHP projectsIntegrating React.js with PHP projects
Integrating React.js with PHP projectsIgnacio Martín
 
2011 a grape odyssey
2011   a grape odyssey2011   a grape odyssey
2011 a grape odysseyMike Hagedorn
 
A Journey with React
A Journey with ReactA Journey with React
A Journey with ReactFITC
 
Intro to computer vision in .net update
Intro to computer vision in .net   updateIntro to computer vision in .net   update
Intro to computer vision in .net updateStephen Lorello
 
Behind the curtain - How Django handles a request
Behind the curtain - How Django handles a requestBehind the curtain - How Django handles a request
Behind the curtain - How Django handles a requestDaniel Hepper
 
JavaScript Promises
JavaScript PromisesJavaScript Promises
JavaScript PromisesTomasz Bak
 
Automated release management with team city & octopusdeploy - NDC 2013
Automated release management with team city & octopusdeploy - NDC 2013Automated release management with team city & octopusdeploy - NDC 2013
Automated release management with team city & octopusdeploy - NDC 2013Kristoffer Deinoff
 

What's hot (20)

The Open Web and what it means
The Open Web and what it meansThe Open Web and what it means
The Open Web and what it means
 
Boost your angular app with web workers
Boost your angular app with web workersBoost your angular app with web workers
Boost your angular app with web workers
 
Promise pattern
Promise patternPromise pattern
Promise pattern
 
Advanced redux
Advanced reduxAdvanced redux
Advanced redux
 
Learning React: Facebook's Javascript Library For Building User Interfaces
Learning React: Facebook's Javascript Library For Building User InterfacesLearning React: Facebook's Javascript Library For Building User Interfaces
Learning React: Facebook's Javascript Library For Building User Interfaces
 
Promises are so passé - Tim Perry - Codemotion Milan 2016
Promises are so passé - Tim Perry - Codemotion Milan 2016Promises are so passé - Tim Perry - Codemotion Milan 2016
Promises are so passé - Tim Perry - Codemotion Milan 2016
 
Reduxing like a pro
Reduxing like a proReduxing like a pro
Reduxing like a pro
 
Framework
FrameworkFramework
Framework
 
Introductionandgreetings
IntroductionandgreetingsIntroductionandgreetings
Introductionandgreetings
 
Introduction to ReactJS and Redux
Introduction to ReactJS and ReduxIntroduction to ReactJS and Redux
Introduction to ReactJS and Redux
 
My Top 5 APEX JavaScript API's
My Top 5 APEX JavaScript API'sMy Top 5 APEX JavaScript API's
My Top 5 APEX JavaScript API's
 
Qualidade de vida: Com Zabbix e API
Qualidade de vida: Com Zabbix e APIQualidade de vida: Com Zabbix e API
Qualidade de vida: Com Zabbix e API
 
Integrating React.js with PHP projects
Integrating React.js with PHP projectsIntegrating React.js with PHP projects
Integrating React.js with PHP projects
 
iOS and Android apps automation
iOS and Android apps automationiOS and Android apps automation
iOS and Android apps automation
 
2011 a grape odyssey
2011   a grape odyssey2011   a grape odyssey
2011 a grape odyssey
 
A Journey with React
A Journey with ReactA Journey with React
A Journey with React
 
Intro to computer vision in .net update
Intro to computer vision in .net   updateIntro to computer vision in .net   update
Intro to computer vision in .net update
 
Behind the curtain - How Django handles a request
Behind the curtain - How Django handles a requestBehind the curtain - How Django handles a request
Behind the curtain - How Django handles a request
 
JavaScript Promises
JavaScript PromisesJavaScript Promises
JavaScript Promises
 
Automated release management with team city & octopusdeploy - NDC 2013
Automated release management with team city & octopusdeploy - NDC 2013Automated release management with team city & octopusdeploy - NDC 2013
Automated release management with team city & octopusdeploy - NDC 2013
 

Similar to Event source everything!

WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015Fernando Daciuk
 
The evolution of redux action creators
The evolution of redux action creatorsThe evolution of redux action creators
The evolution of redux action creatorsGeorge Bukhanov
 
Code moi une RH! (PHP tour 2017)
Code moi une RH! (PHP tour 2017)Code moi une RH! (PHP tour 2017)
Code moi une RH! (PHP tour 2017)Arnaud Langlade
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & RESTHugo Hamon
 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hackingJeroen van Dijk
 
Java script+mvc+with+emberjs
Java script+mvc+with+emberjsJava script+mvc+with+emberjs
Java script+mvc+with+emberjsji guang
 
Make WordPress realtime.
Make WordPress realtime.Make WordPress realtime.
Make WordPress realtime.Josh Hillier
 
Taming forms with React
Taming forms with ReactTaming forms with React
Taming forms with ReactGreeceJS
 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web servicesMichelangelo van Dam
 
第3回Grails/Groovy勉強会名古屋「Grails名古屋座談会」
第3回Grails/Groovy勉強会名古屋「Grails名古屋座談会」第3回Grails/Groovy勉強会名古屋「Grails名古屋座談会」
第3回Grails/Groovy勉強会名古屋「Grails名古屋座談会」Tsuyoshi Yamamoto
 
From Node.js to Design Patterns
From Node.js to Design Patterns From Node.js to Design Patterns
From Node.js to Design Patterns Luciano Mammino
 
learn you some erlang - chap13 to chap14
learn you some erlang - chap13 to chap14learn you some erlang - chap13 to chap14
learn you some erlang - chap13 to chap14경미 김
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony AppsKris Wallsmith
 

Similar to Event source everything! (20)

WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015WordPress Realtime - WordCamp São Paulo 2015
WordPress Realtime - WordCamp São Paulo 2015
 
The evolution of redux action creators
The evolution of redux action creatorsThe evolution of redux action creators
The evolution of redux action creators
 
Code moi une RH! (PHP tour 2017)
Code moi une RH! (PHP tour 2017)Code moi une RH! (PHP tour 2017)
Code moi une RH! (PHP tour 2017)
 
Domain Driven Design
Domain Driven DesignDomain Driven Design
Domain Driven Design
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
 
WordPress REST API hacking
WordPress REST API hackingWordPress REST API hacking
WordPress REST API hacking
 
Java script+mvc+with+emberjs
Java script+mvc+with+emberjsJava script+mvc+with+emberjs
Java script+mvc+with+emberjs
 
Make WordPress realtime.
Make WordPress realtime.Make WordPress realtime.
Make WordPress realtime.
 
Taming forms with React
Taming forms with ReactTaming forms with React
Taming forms with React
 
One tool to rule them all
One tool to rule them allOne tool to rule them all
One tool to rule them all
 
Code me a HR
Code me a HRCode me a HR
Code me a HR
 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web services
 
第3回Grails/Groovy勉強会名古屋「Grails名古屋座談会」
第3回Grails/Groovy勉強会名古屋「Grails名古屋座談会」第3回Grails/Groovy勉強会名古屋「Grails名古屋座談会」
第3回Grails/Groovy勉強会名古屋「Grails名古屋座談会」
 
From Node.js to Design Patterns
From Node.js to Design Patterns From Node.js to Design Patterns
From Node.js to Design Patterns
 
G* on GAE/J 挑戦編
G* on GAE/J 挑戦編G* on GAE/J 挑戦編
G* on GAE/J 挑戦編
 
Bacbkone js
Bacbkone jsBacbkone js
Bacbkone js
 
Pengenalan blaast platform sdk
Pengenalan blaast platform sdkPengenalan blaast platform sdk
Pengenalan blaast platform sdk
 
learn you some erlang - chap13 to chap14
learn you some erlang - chap13 to chap14learn you some erlang - chap13 to chap14
learn you some erlang - chap13 to chap14
 
Side effects-con-redux
Side effects-con-reduxSide effects-con-redux
Side effects-con-redux
 
How Kris Writes Symfony Apps
How Kris Writes Symfony AppsHow Kris Writes Symfony Apps
How Kris Writes Symfony Apps
 

More from The Software House

Jak kraść miliony, czyli o błędach bezpieczeństwa, które mogą spotkać również...
Jak kraść miliony, czyli o błędach bezpieczeństwa, które mogą spotkać również...Jak kraść miliony, czyli o błędach bezpieczeństwa, które mogą spotkać również...
Jak kraść miliony, czyli o błędach bezpieczeństwa, które mogą spotkać również...The Software House
 
Jak efektywnie podejść do certyfikacji w AWS?
Jak efektywnie podejść do certyfikacji w AWS?Jak efektywnie podejść do certyfikacji w AWS?
Jak efektywnie podejść do certyfikacji w AWS?The Software House
 
O co chodzi z tą dostępnością cyfrową?
O co chodzi z tą dostępnością cyfrową?O co chodzi z tą dostępnością cyfrową?
O co chodzi z tą dostępnością cyfrową?The Software House
 
Chat tekstowy z użyciem Amazon Chime
Chat tekstowy z użyciem Amazon ChimeChat tekstowy z użyciem Amazon Chime
Chat tekstowy z użyciem Amazon ChimeThe Software House
 
Jak nie zwariować z architekturą Serverless?
Jak nie zwariować z architekturą Serverless?Jak nie zwariować z architekturą Serverless?
Jak nie zwariować z architekturą Serverless?The Software House
 
Analiza semantyczna artykułów prasowych w 5 sprintów z użyciem AWS
Analiza semantyczna artykułów prasowych w 5 sprintów z użyciem AWSAnaliza semantyczna artykułów prasowych w 5 sprintów z użyciem AWS
Analiza semantyczna artykułów prasowych w 5 sprintów z użyciem AWSThe Software House
 
Feature flags na ratunek projektu w JavaScript
Feature flags na ratunek projektu w JavaScriptFeature flags na ratunek projektu w JavaScript
Feature flags na ratunek projektu w JavaScriptThe Software House
 
Typowanie nominalne w TypeScript
Typowanie nominalne w TypeScriptTypowanie nominalne w TypeScript
Typowanie nominalne w TypeScriptThe Software House
 
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQL
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQLAutomatyzacja tworzenia frontendu z wykorzystaniem GraphQL
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQLThe Software House
 
Serverless Compose vs hurtownia danych
Serverless Compose vs hurtownia danychServerless Compose vs hurtownia danych
Serverless Compose vs hurtownia danychThe Software House
 
Testy API: połączenie z bazą danych czy implementacja w pamięci
Testy API: połączenie z bazą danych czy implementacja w pamięciTesty API: połączenie z bazą danych czy implementacja w pamięci
Testy API: połączenie z bazą danych czy implementacja w pamięciThe Software House
 
Jak skutecznie read model. Case study
Jak skutecznie read model. Case studyJak skutecznie read model. Case study
Jak skutecznie read model. Case studyThe Software House
 
Firestore czyli ognista baza od giganta z Doliny Krzemowej
Firestore czyli ognista baza od giganta z Doliny KrzemowejFirestore czyli ognista baza od giganta z Doliny Krzemowej
Firestore czyli ognista baza od giganta z Doliny KrzemowejThe Software House
 
Jak utrzymać stado Lambd w ryzach
Jak utrzymać stado Lambd w ryzachJak utrzymać stado Lambd w ryzach
Jak utrzymać stado Lambd w ryzachThe Software House
 
O łączeniu Storyblok i Next.js
O łączeniu Storyblok i Next.jsO łączeniu Storyblok i Next.js
O łączeniu Storyblok i Next.jsThe Software House
 
Amazon Step Functions. Sposób na implementację procesów w chmurze
Amazon Step Functions. Sposób na implementację procesów w chmurzeAmazon Step Functions. Sposób na implementację procesów w chmurze
Amazon Step Functions. Sposób na implementację procesów w chmurzeThe Software House
 
Od Figmy do gotowej aplikacji bez linijki kodu
Od Figmy do gotowej aplikacji bez linijki koduOd Figmy do gotowej aplikacji bez linijki kodu
Od Figmy do gotowej aplikacji bez linijki koduThe Software House
 

More from The Software House (20)

Jak kraść miliony, czyli o błędach bezpieczeństwa, które mogą spotkać również...
Jak kraść miliony, czyli o błędach bezpieczeństwa, które mogą spotkać również...Jak kraść miliony, czyli o błędach bezpieczeństwa, które mogą spotkać również...
Jak kraść miliony, czyli o błędach bezpieczeństwa, które mogą spotkać również...
 
Uszanowanko Podsumowanko
Uszanowanko PodsumowankoUszanowanko Podsumowanko
Uszanowanko Podsumowanko
 
Jak efektywnie podejść do certyfikacji w AWS?
Jak efektywnie podejść do certyfikacji w AWS?Jak efektywnie podejść do certyfikacji w AWS?
Jak efektywnie podejść do certyfikacji w AWS?
 
O co chodzi z tą dostępnością cyfrową?
O co chodzi z tą dostępnością cyfrową?O co chodzi z tą dostępnością cyfrową?
O co chodzi z tą dostępnością cyfrową?
 
Chat tekstowy z użyciem Amazon Chime
Chat tekstowy z użyciem Amazon ChimeChat tekstowy z użyciem Amazon Chime
Chat tekstowy z użyciem Amazon Chime
 
Migracje danych serverless
Migracje danych serverlessMigracje danych serverless
Migracje danych serverless
 
Jak nie zwariować z architekturą Serverless?
Jak nie zwariować z architekturą Serverless?Jak nie zwariować z architekturą Serverless?
Jak nie zwariować z architekturą Serverless?
 
Analiza semantyczna artykułów prasowych w 5 sprintów z użyciem AWS
Analiza semantyczna artykułów prasowych w 5 sprintów z użyciem AWSAnaliza semantyczna artykułów prasowych w 5 sprintów z użyciem AWS
Analiza semantyczna artykułów prasowych w 5 sprintów z użyciem AWS
 
Feature flags na ratunek projektu w JavaScript
Feature flags na ratunek projektu w JavaScriptFeature flags na ratunek projektu w JavaScript
Feature flags na ratunek projektu w JavaScript
 
Typowanie nominalne w TypeScript
Typowanie nominalne w TypeScriptTypowanie nominalne w TypeScript
Typowanie nominalne w TypeScript
 
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQL
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQLAutomatyzacja tworzenia frontendu z wykorzystaniem GraphQL
Automatyzacja tworzenia frontendu z wykorzystaniem GraphQL
 
Serverless Compose vs hurtownia danych
Serverless Compose vs hurtownia danychServerless Compose vs hurtownia danych
Serverless Compose vs hurtownia danych
 
Testy API: połączenie z bazą danych czy implementacja w pamięci
Testy API: połączenie z bazą danych czy implementacja w pamięciTesty API: połączenie z bazą danych czy implementacja w pamięci
Testy API: połączenie z bazą danych czy implementacja w pamięci
 
Jak skutecznie read model. Case study
Jak skutecznie read model. Case studyJak skutecznie read model. Case study
Jak skutecznie read model. Case study
 
Firestore czyli ognista baza od giganta z Doliny Krzemowej
Firestore czyli ognista baza od giganta z Doliny KrzemowejFirestore czyli ognista baza od giganta z Doliny Krzemowej
Firestore czyli ognista baza od giganta z Doliny Krzemowej
 
Jak utrzymać stado Lambd w ryzach
Jak utrzymać stado Lambd w ryzachJak utrzymać stado Lambd w ryzach
Jak utrzymać stado Lambd w ryzach
 
Jak poskromić AWS?
Jak poskromić AWS?Jak poskromić AWS?
Jak poskromić AWS?
 
O łączeniu Storyblok i Next.js
O łączeniu Storyblok i Next.jsO łączeniu Storyblok i Next.js
O łączeniu Storyblok i Next.js
 
Amazon Step Functions. Sposób na implementację procesów w chmurze
Amazon Step Functions. Sposób na implementację procesów w chmurzeAmazon Step Functions. Sposób na implementację procesów w chmurze
Amazon Step Functions. Sposób na implementację procesów w chmurze
 
Od Figmy do gotowej aplikacji bez linijki kodu
Od Figmy do gotowej aplikacji bez linijki koduOd Figmy do gotowej aplikacji bez linijki kodu
Od Figmy do gotowej aplikacji bez linijki kodu
 

Recently uploaded

AlbaniaDreamin24 - How to easily use an API with Flows
AlbaniaDreamin24 - How to easily use an API with FlowsAlbaniaDreamin24 - How to easily use an API with Flows
AlbaniaDreamin24 - How to easily use an API with FlowsThierry TROUIN ☁
 
Chennai Call Girls Porur Phone 🍆 8250192130 👅 celebrity escorts service
Chennai Call Girls Porur Phone 🍆 8250192130 👅 celebrity escorts serviceChennai Call Girls Porur Phone 🍆 8250192130 👅 celebrity escorts service
Chennai Call Girls Porur Phone 🍆 8250192130 👅 celebrity escorts servicesonalikaur4
 
Low Rate Call Girls Kolkata Avani 🤌 8250192130 🚀 Vip Call Girls Kolkata
Low Rate Call Girls Kolkata Avani 🤌  8250192130 🚀 Vip Call Girls KolkataLow Rate Call Girls Kolkata Avani 🤌  8250192130 🚀 Vip Call Girls Kolkata
Low Rate Call Girls Kolkata Avani 🤌 8250192130 🚀 Vip Call Girls Kolkataanamikaraghav4
 
Call Girls in Uttam Nagar Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Uttam Nagar Delhi 💯Call Us 🔝8264348440🔝Call Girls in Uttam Nagar Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Uttam Nagar Delhi 💯Call Us 🔝8264348440🔝soniya singh
 
Call Girls In Mumbai Central Mumbai ❤️ 9920874524 👈 Cash on Delivery
Call Girls In Mumbai Central Mumbai ❤️ 9920874524 👈 Cash on DeliveryCall Girls In Mumbai Central Mumbai ❤️ 9920874524 👈 Cash on Delivery
Call Girls In Mumbai Central Mumbai ❤️ 9920874524 👈 Cash on Deliverybabeytanya
 
Sushant Golf City / best call girls in Lucknow | Service-oriented sexy call g...
Sushant Golf City / best call girls in Lucknow | Service-oriented sexy call g...Sushant Golf City / best call girls in Lucknow | Service-oriented sexy call g...
Sushant Golf City / best call girls in Lucknow | Service-oriented sexy call g...akbard9823
 
VIP Kolkata Call Girls Salt Lake 8250192130 Available With Room
VIP Kolkata Call Girls Salt Lake 8250192130 Available With RoomVIP Kolkata Call Girls Salt Lake 8250192130 Available With Room
VIP Kolkata Call Girls Salt Lake 8250192130 Available With Roomgirls4nights
 
Call Girls South Delhi Delhi reach out to us at ☎ 9711199012
Call Girls South Delhi Delhi reach out to us at ☎ 9711199012Call Girls South Delhi Delhi reach out to us at ☎ 9711199012
Call Girls South Delhi Delhi reach out to us at ☎ 9711199012rehmti665
 
VIP Kolkata Call Girl Kestopur 👉 8250192130 Available With Room
VIP Kolkata Call Girl Kestopur 👉 8250192130  Available With RoomVIP Kolkata Call Girl Kestopur 👉 8250192130  Available With Room
VIP Kolkata Call Girl Kestopur 👉 8250192130 Available With Roomdivyansh0kumar0
 
Russian Call girls in Dubai +971563133746 Dubai Call girls
Russian  Call girls in Dubai +971563133746 Dubai  Call girlsRussian  Call girls in Dubai +971563133746 Dubai  Call girls
Russian Call girls in Dubai +971563133746 Dubai Call girlsstephieert
 
FULL ENJOY Call Girls In Mayur Vihar Delhi Contact Us 8377087607
FULL ENJOY Call Girls In Mayur Vihar Delhi Contact Us 8377087607FULL ENJOY Call Girls In Mayur Vihar Delhi Contact Us 8377087607
FULL ENJOY Call Girls In Mayur Vihar Delhi Contact Us 8377087607dollysharma2066
 
Git and Github workshop GDSC MLRITM
Git and Github  workshop GDSC MLRITMGit and Github  workshop GDSC MLRITM
Git and Github workshop GDSC MLRITMgdsc13
 
VIP Kolkata Call Girl Dum Dum 👉 8250192130 Available With Room
VIP Kolkata Call Girl Dum Dum 👉 8250192130  Available With RoomVIP Kolkata Call Girl Dum Dum 👉 8250192130  Available With Room
VIP Kolkata Call Girl Dum Dum 👉 8250192130 Available With Roomdivyansh0kumar0
 
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)Christopher H Felton
 
10.pdfMature Call girls in Dubai +971563133746 Dubai Call girls
10.pdfMature Call girls in Dubai +971563133746 Dubai Call girls10.pdfMature Call girls in Dubai +971563133746 Dubai Call girls
10.pdfMature Call girls in Dubai +971563133746 Dubai Call girlsstephieert
 
Gram Darshan PPT cyber rural in villages of india
Gram Darshan PPT cyber rural  in villages of indiaGram Darshan PPT cyber rural  in villages of india
Gram Darshan PPT cyber rural in villages of indiaimessage0108
 
Complet Documnetation for Smart Assistant Application for Disabled Person
Complet Documnetation   for Smart Assistant Application for Disabled PersonComplet Documnetation   for Smart Assistant Application for Disabled Person
Complet Documnetation for Smart Assistant Application for Disabled Personfurqan222004
 
定制(AUT毕业证书)新西兰奥克兰理工大学毕业证成绩单原版一比一
定制(AUT毕业证书)新西兰奥克兰理工大学毕业证成绩单原版一比一定制(AUT毕业证书)新西兰奥克兰理工大学毕业证成绩单原版一比一
定制(AUT毕业证书)新西兰奥克兰理工大学毕业证成绩单原版一比一Fs
 
Russian Call Girls in Kolkata Samaira 🤌 8250192130 🚀 Vip Call Girls Kolkata
Russian Call Girls in Kolkata Samaira 🤌  8250192130 🚀 Vip Call Girls KolkataRussian Call Girls in Kolkata Samaira 🤌  8250192130 🚀 Vip Call Girls Kolkata
Russian Call Girls in Kolkata Samaira 🤌 8250192130 🚀 Vip Call Girls Kolkataanamikaraghav4
 

Recently uploaded (20)

AlbaniaDreamin24 - How to easily use an API with Flows
AlbaniaDreamin24 - How to easily use an API with FlowsAlbaniaDreamin24 - How to easily use an API with Flows
AlbaniaDreamin24 - How to easily use an API with Flows
 
Chennai Call Girls Porur Phone 🍆 8250192130 👅 celebrity escorts service
Chennai Call Girls Porur Phone 🍆 8250192130 👅 celebrity escorts serviceChennai Call Girls Porur Phone 🍆 8250192130 👅 celebrity escorts service
Chennai Call Girls Porur Phone 🍆 8250192130 👅 celebrity escorts service
 
Low Rate Call Girls Kolkata Avani 🤌 8250192130 🚀 Vip Call Girls Kolkata
Low Rate Call Girls Kolkata Avani 🤌  8250192130 🚀 Vip Call Girls KolkataLow Rate Call Girls Kolkata Avani 🤌  8250192130 🚀 Vip Call Girls Kolkata
Low Rate Call Girls Kolkata Avani 🤌 8250192130 🚀 Vip Call Girls Kolkata
 
Call Girls in Uttam Nagar Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Uttam Nagar Delhi 💯Call Us 🔝8264348440🔝Call Girls in Uttam Nagar Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Uttam Nagar Delhi 💯Call Us 🔝8264348440🔝
 
Call Girls In Mumbai Central Mumbai ❤️ 9920874524 👈 Cash on Delivery
Call Girls In Mumbai Central Mumbai ❤️ 9920874524 👈 Cash on DeliveryCall Girls In Mumbai Central Mumbai ❤️ 9920874524 👈 Cash on Delivery
Call Girls In Mumbai Central Mumbai ❤️ 9920874524 👈 Cash on Delivery
 
Sushant Golf City / best call girls in Lucknow | Service-oriented sexy call g...
Sushant Golf City / best call girls in Lucknow | Service-oriented sexy call g...Sushant Golf City / best call girls in Lucknow | Service-oriented sexy call g...
Sushant Golf City / best call girls in Lucknow | Service-oriented sexy call g...
 
VIP Kolkata Call Girls Salt Lake 8250192130 Available With Room
VIP Kolkata Call Girls Salt Lake 8250192130 Available With RoomVIP Kolkata Call Girls Salt Lake 8250192130 Available With Room
VIP Kolkata Call Girls Salt Lake 8250192130 Available With Room
 
Call Girls South Delhi Delhi reach out to us at ☎ 9711199012
Call Girls South Delhi Delhi reach out to us at ☎ 9711199012Call Girls South Delhi Delhi reach out to us at ☎ 9711199012
Call Girls South Delhi Delhi reach out to us at ☎ 9711199012
 
VIP Kolkata Call Girl Kestopur 👉 8250192130 Available With Room
VIP Kolkata Call Girl Kestopur 👉 8250192130  Available With RoomVIP Kolkata Call Girl Kestopur 👉 8250192130  Available With Room
VIP Kolkata Call Girl Kestopur 👉 8250192130 Available With Room
 
Russian Call girls in Dubai +971563133746 Dubai Call girls
Russian  Call girls in Dubai +971563133746 Dubai  Call girlsRussian  Call girls in Dubai +971563133746 Dubai  Call girls
Russian Call girls in Dubai +971563133746 Dubai Call girls
 
FULL ENJOY Call Girls In Mayur Vihar Delhi Contact Us 8377087607
FULL ENJOY Call Girls In Mayur Vihar Delhi Contact Us 8377087607FULL ENJOY Call Girls In Mayur Vihar Delhi Contact Us 8377087607
FULL ENJOY Call Girls In Mayur Vihar Delhi Contact Us 8377087607
 
Git and Github workshop GDSC MLRITM
Git and Github  workshop GDSC MLRITMGit and Github  workshop GDSC MLRITM
Git and Github workshop GDSC MLRITM
 
VIP Kolkata Call Girl Dum Dum 👉 8250192130 Available With Room
VIP Kolkata Call Girl Dum Dum 👉 8250192130  Available With RoomVIP Kolkata Call Girl Dum Dum 👉 8250192130  Available With Room
VIP Kolkata Call Girl Dum Dum 👉 8250192130 Available With Room
 
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)
A Good Girl's Guide to Murder (A Good Girl's Guide to Murder, #1)
 
10.pdfMature Call girls in Dubai +971563133746 Dubai Call girls
10.pdfMature Call girls in Dubai +971563133746 Dubai Call girls10.pdfMature Call girls in Dubai +971563133746 Dubai Call girls
10.pdfMature Call girls in Dubai +971563133746 Dubai Call girls
 
Gram Darshan PPT cyber rural in villages of india
Gram Darshan PPT cyber rural  in villages of indiaGram Darshan PPT cyber rural  in villages of india
Gram Darshan PPT cyber rural in villages of india
 
Model Call Girl in Jamuna Vihar Delhi reach out to us at 🔝9953056974🔝
Model Call Girl in  Jamuna Vihar Delhi reach out to us at 🔝9953056974🔝Model Call Girl in  Jamuna Vihar Delhi reach out to us at 🔝9953056974🔝
Model Call Girl in Jamuna Vihar Delhi reach out to us at 🔝9953056974🔝
 
Complet Documnetation for Smart Assistant Application for Disabled Person
Complet Documnetation   for Smart Assistant Application for Disabled PersonComplet Documnetation   for Smart Assistant Application for Disabled Person
Complet Documnetation for Smart Assistant Application for Disabled Person
 
定制(AUT毕业证书)新西兰奥克兰理工大学毕业证成绩单原版一比一
定制(AUT毕业证书)新西兰奥克兰理工大学毕业证成绩单原版一比一定制(AUT毕业证书)新西兰奥克兰理工大学毕业证成绩单原版一比一
定制(AUT毕业证书)新西兰奥克兰理工大学毕业证成绩单原版一比一
 
Russian Call Girls in Kolkata Samaira 🤌 8250192130 🚀 Vip Call Girls Kolkata
Russian Call Girls in Kolkata Samaira 🤌  8250192130 🚀 Vip Call Girls KolkataRussian Call Girls in Kolkata Samaira 🤌  8250192130 🚀 Vip Call Girls Kolkata
Russian Call Girls in Kolkata Samaira 🤌 8250192130 🚀 Vip Call Girls Kolkata
 

Event source everything!

  • 2. EVENT SOURCE EVERYTHING! AGENDA ▸ EVENT SOURCING BASICS ▸ WHAT ABOUT STORE? ▸ HOW TO HANDLE EVENTS? ▸ VERSIONING ▸ PERFORMANCE ▸ QUESTIONS
  • 3. ADAM POLAK HEAD OF NODE.JS ADAM.POLAK@THESOFTWAREHOUSE.PL POLAK.ADAM1
  • 5. ▸ CREATE EMPLOYEE ▸ ISSUE VACATION REQUEST ▸ APPROVE/DECLINE VACATION REQUEST HR MANAGEMENT EVENT SOURCE EVERYTHING!
  • 7. EMPLOYEE class Employee { constructor(id, firstName, lastName, vacationRequests) { this._id = id; this._firstName = firstName; this._lastName = lastName; this._vacationRequests = vacationRequests; } issueVacationRequest(from, to, comment) { // do business logic and domain validation here then return vacation req or event} } EVENT SOURCE EVERYTHING!
  • 8. ISSUE VACATION REQUEST const vacationRequest = employee.issueValidationRequest(‘2018-10-01’, ‘2018-11-01’, ‘Japan, here I come!’); vacationRequestsRepository.persist(vacationRequest); // map vacation request from domain model to db model const employee = employeeRepository.findById(id); // map from db model to domain model EVENT SOURCE EVERYTHING! | id | employee_id | from | to | comment | create_date | update_date | | 1 | some-uuid | 2018-10-01 | 2018-11-01 | Japan, here I come! | 2018-01-11 | 2018-01-11 |
  • 11. ▸ EMPLOYEE EMPLOYED ▸ VACATION REQUEST ISSUED ▸ VACATION REQUEST APPROVED ▸ VACATION REQUEST DECLINED ▸ EMPLOYEE ASSIGNED TO PROJECT ▸ EMPLOYEE DETAILS UPDATED EVENTS EVENT SOURCE EVERYTHING!
  • 15. REBUILD FROM EVENTSCOMMAND OPERATIONS WRITE SIDE EMIT EVENTS EVENT STORE EVENT SOURCE EVERYTHING!
  • 18. ISSUE VACATION REQUEST router.post('/:id/vacations', celebrate({ body: Joi.object().keys({ from: Joi.date().required(), to: Joi.date().required(), comment: Joi.string().allow(null) }) }), async (req, res, next) => { const employeeId = req.params.id; try { const events = await eventStore.getEventsFor(employeeId); const employee = Employee.fromEvents(employeeId, events); employee.issueVacationRequest(req.body.from, req.body.to, req.body.comment); await eventStore.saveStream(employee.getRecentEvents()).then(events => eventBus.publish(events)); return res.status(204).send(); } catch (error) { next(error);} }); EVENT SOURCE EVERYTHING!
  • 19. ISSUE VACATION REQUEST - GET EVENTS FROM STORE router.post('/:id/vacations', celebrate({ body: Joi.object().keys({ from: Joi.date().required(), to: Joi.date().required(), comment: Joi.string().allow(null) }) }), async (req, res, next) => { const employeeId = req.params.id; try { const employee = Employee.fromEvents(employeeId, events); employee.issueVacationRequest(req.body.from, req.body.to, req.body.comment); await eventStore.saveStream(employee.getRecentEvents()).then(events => eventBus.publish(events)); return res.status(204).send(); } catch (error) { next(error);} }); const events = await eventStore.getEventsFor(employeeId); EVENT SOURCE EVERYTHING!
  • 21. ▸ SINGLE SOURCE OF TRUTH ▸ USED ONLY ON WRITE SIDE ▸ USED DURING READ MODEL REBUILD EVENT STORE EVENT SOURCE EVERYTHING!
  • 22. { type: ‘EMPLOYEE_EMPLOYED’, aggregateId: my-uuid, version: 1, createDate: ‘2018-01-01 12:23:34’, payload: { firstName: ‘Adam’, lastName: ‘Polak’, occupation: ‘Head of Node.js’ } } EVENTS { type: ‘VACATION_REQUEST_ISSUED’, aggregateId: my-uuid, version: 1, createDate: ‘2018-01-01 13:23:34’, payload: { id: request-uuid, from: ‘2018-10-01’, to: ‘2018-11-01’, days: 24 } } { type: ‘EMPLOYEE_ASSIGNED’ aggregateId: my-uuid, version: 1, createDate: ‘2018-01-01 14:23:34’, payload: { projectId: project-uuid } } EVENT SOURCE EVERYTHING!
  • 23. ▸ EVENT STORE ▸ MONGODB ▸ POSTGRESQL POSSIBLE SOLUTIONS EVENT SOURCE EVERYTHING!
  • 24. class EventStoreRepository { constructor(db, upcasters) { … } async saveStream(events) { … } getAllEvents() { … } getEventsFor(aggregateId) { return this.eventsCollection.find({ aggregateId }) .sort({ "payload.createDate": 1 }) .then(events => events.map(event => this.upcasters.upcast(event))); } } REPOSITORY EVENT SOURCE EVERYTHING!
  • 25. class EventStoreRepository { constructor(db, upcasters) { … } async saveStream(events) { … } getAllEvents() { … } } REPOSITORY getEventsFor(aggregateId) { return this.eventsCollection.find({ aggregateId }) .sort({ "payload.createDate": 1 }) .then(events => events.map(event => this.upcasters.upcast(event))); } EVENT SOURCE EVERYTHING!
  • 26. ISSUE VACATION REQUEST - REBUILD AGGREGATE FROM EVENTS router.post('/:id/vacations', celebrate({ body: Joi.object().keys({ from: Joi.date().required(), to: Joi.date().required(), comment: Joi.string().allow(null) }) }), async (req, res, next) => { const employeeId = req.params.id; try { employee.issueVacationRequest(req.body.from, req.body.to, req.body.comment); a await eventStore.saveStream(employee.getRecentEvents()).then(events => eventBus.publish(events)); return res.status(204).send(); } catch (error) { next(error);} }); const events = await eventStore.getEventsFor(employeeId); const employee = Employee.fromEvents(employeeId, events); EVENT SOURCE EVERYTHING!
  • 27. AGGREGATE ROOT class Employee { constructor(id) { this.id = id; this.recentEvents = []; } static fromEvents(aggregateId, events) { … } issueVacationRequest(from, to, comment) { … } apply(event) {…} getRecentEvents() { … } } EVENT SOURCE EVERYTHING!
  • 28. AGGREGATE ROOT - REBUILD FROM EVENTS class Employee { constructor(id) { this.id = id; this.recentEvents = []; } issueVacationRequest(from, to, comment) { … } apply(event) {…} getRecentEvents() { … } } static fromEvents(aggregateId, events) { … } EVENT SOURCE EVERYTHING!
  • 29. AGGREGATE ROOT - REBUILD FROM EVENTS { type: ‘EMPLOYEE_EMPLOYED’, aggregateId: my-uuid, version: 1, createDate: ‘2018-01-01 12:23:34’, payload: { firstName: ‘Adam’, lastName: ‘Polak’, occupation: ‘JS Developer’ } } { type: ‘EMPLOYEE_OCCUPATION_CHANGED’, aggregateId: my-uuid, version: 1, createDate: ‘2018-01-01 13:23:34’, payload: { occupation: ‘Head of Node.js’ } } { id: my-uuid, firstName: ‘Adam’, lastName: ‘Polak’, occupation: ‘JS Developer’ } { id: my-uuid, firstName: ‘Adam’, lastName: ‘Polak’, occupation: ‘Head of Node.js’ } EVENT SOURCE EVERYTHING!
  • 30. static fromEvents(id, events) { return events.reduce((employee, event) => { employee.apply(event); return employee; }, new Employee(id)) } AGGREGATE ROOT - REBUILD FROM EVENTS EVENT SOURCE EVERYTHING!
  • 31. apply(event) { if (event.type === ‘EMPLOYEE_EMPLOYED’) { this.firstName = event.payload.firstName; this.lastName = event.payload.lastName; this.occupation = event.payload.occupation; } else { throw new AppException(`Missing handler for event with type ${event.type}`); } } AGGREGATE ROOT - APPLY EVENTS EVENT SOURCE EVERYTHING!
  • 32. ISSUE VACATION REQUEST - PERFORM ACTION ON DOMAIN MODEL router.post('/:id/vacations', celebrate({ body: Joi.object().keys({ from: Joi.date().required(), to: Joi.date().required(), comment: Joi.string().allow(null) }) }), async (req, res, next) => { const employeeId = req.params.id; try { await eventStore.saveStream(employee.getRecentEvents()).then(events => eventBus.publish(events)); return res.status(204).send(); } catch (error) { next(error);} }); const events = await eventStore.getEventsFor(employeeId); const employee = Employee.fromEvents(employeeId, events); employee.issueVacationRequest(req.body.from, req.body.to, req.body.comment); EVENT SOURCE EVERYTHING!
  • 33. ISSUE VACATION REQUEST issueVacationRequest(from, to, comment) { const requestId = uuid.v4(); const days = VacationRequest.calculateDays(from, to); if (this.vacationDays.availableDays < days) { throw new Error(`Not enough free days to take vacation for '${days}' days`); } const employeeIssuedVacationRequestEvent = employeeIssuedVacationRequest.create( … ); this.recentEvents.push(employeeIssuedVacationRequestEvent); this.apply(employeeIssuedVacationRequestEvent); } EVENT SOURCE EVERYTHING!
  • 34. ISSUE VACATION REQUEST - EMIT EVENT FROM AGGREGATE issueVacationRequest(from, to, comment) { const requestId = uuid.v4(); const days = VacationRequest.calculateDays(from, to); if (this.vacationDays.availableDays < days) { throw new Error(`Not enough free days to take vacation for '${days}' days`); } } const employeeIssuedVacationRequestEvent = employeeIssuedVacationRequest.create( … ); this.recentEvents.push(employeeIssuedVacationRequestEvent); this.apply(employeeIssuedVacationRequestEvent); EVENT SOURCE EVERYTHING!
  • 35. ISSUE VACATION REQUEST - SAVE RECENT EVENTS router.post('/:id/vacations', celebrate({ body: Joi.object().keys({ from: Joi.date().required(), to: Joi.date().required(), comment: Joi.string().allow(null) }) }), async (req, res, next) => { const employeeId = req.params.id; try { return res.status(204).send(); } catch (error) { next(error);} }); const events = await eventStore.getEventsFor(employeeId); const employee = Employee.fromEvents(employeeId, events); employee.issueVacationRequest(req.body.from, req.body.to, req.body.comment); await eventStore.saveStream(employee.getRecentEvents()).then(events => eventBus.publish(events)); EVENT SOURCE EVERYTHING!
  • 36. getRecentEvents() { const events = [...this.recentEvents]; this.recentEvents = []; return events; } AGGREGATE ROOT - GET RECENT EVENTS EVENT SOURCE EVERYTHING!
  • 38. class EventStoreRepository { constructor(db, upcasters) { … } async saveStream(events) { await this.eventsCollection.insert(events); return events; } getAllEvents() { … } getEventsForm(aggregateId) { … } } REPOSITORY - SAVE STREAM EVENT SOURCE EVERYTHING!
  • 39. class EventStoreRepository { constructor(db, upcasters) { … } async saveStream(events) { await this.eventsCollection.insert(events); } getAllEvents() { … } getEventsForm(aggregateId) { … } } REPOSITORY - SAVE STREAM EVENT SOURCE EVERYTHING! // SAVE IN SINGLE “TRANSACTION”! async saveStream(events) { await this.eventsCollection.insert(events); return events; }
  • 40. | id | aggregateId | aggregateVersion | type | createDate | payload | version | | 10 | 1 | 10 | EMPLOYEE_UPDATED | 2018-01-11 | { ………………………………… } | 4 | | 9 | 1 | 9 | EMPLOYEE_UPDATED | 2018-01-10 | { ………………………………… } | 4 | | 8 | 1 | 8 | EMPLOYEE_UPDATED | 2018-01-09 | { ………………………………… } | 3 | | 7 | 1 | 7 | EMPLOYEE_UPDATED | 2018-01-08 | { ………………………………… } | 3 | | 6 | 1 | 6 | EMPLOYEE_UPDATED | 2018-01-07 | { ………………………………… } | 2 | | 5 | 1 | 5 | EMPLOYEE_UPDATED | 2018-01-06 | { ………………………………… } | 2 | | 4 | 1 | 4 | EMPLOYEE_UPDATED | 2018-01-05 | { ………………………………… } | 1 | | 3 | 1 | 3 | EMPLOYEE_UPDATED | 2018-01-04 | { ………………………………… } | 1 | | 2 | 1 | 2 | EMPLOYEE_UPDATED | 2018-01-03 | { ………………………………… } | 1 | | 1 | 1 | 1 | EMPLOYEE_EMPLOYED | 2018-01-02 | { ………………………………… } | 1 | EVENT STORE - PERSISTED EVENTS EVENT SOURCE EVERYTHING!
  • 41. ▸ HOW TO PAGINATE? ▸ HOW TO DISPLAY SINGLE EMPLOYEE? ▸ HOW TO DISPLAY FILTER EMPLOYEES? ▸ HOW TO DO BASIC UI OPERATIONS? EVENT SOURCE EVERYTHING! PROBLEMS
  • 42. EVENT BUS PROJECTION STORAGE UI EVENT SOURCE EVERYTHING! READ SIDE
  • 44. EVENT BUS router.post('/:id/vacations', celebrate({ body: Joi.object().keys({ from: Joi.date().required(), to: Joi.date().required(), comment: Joi.string().allow(null) }) }), async (req, res, next) => { const employeeId = req.params.id; try { return res.status(204).send(); } catch (error) { next(error);} }); const events = await eventStore.getEventsFor(employeeId); const employee = Employee.fromEvents(employeeId, events); employee.issueVacationRequest(req.body.from, req.body.to, req.body.comment); await eventStore.saveStream(employee.getRecentEvents()).then(events => eventBus.publish(events)); EVENT SOURCE EVERYTHING!
  • 45. EVENT BUS EVENT BUS PROJECTORS MAILER HANDLER EVENT SOURCE EVERYTHING! { type: ‘EMPLOYEE_EMPLOYED’, aggregateId: my-uuid, version: 1, createDate: ‘2018-01-01 12:23:34’, payload: { firstName: ‘Adam’, lastName: ‘Polak’, occupation: ‘Head of Node.js’ } }
  • 47. EVENTS BUS class EventBus { constructor() { this.subscribers = []; } subscribe(event, handler) { if (typeof this.subscribers[`${event}`] === 'undefined') { this.subscribers[`${event}`] = []; } this.subscribers[`${event}`].push(handler); } publish(events) { events.forEach(event => { if (this.subscribers[`${event.type}`]) { this.subscribers[`${event.type}`].forEach((handler) => handler(event)); } } } } EVENT SOURCE EVERYTHING!
  • 49. PROJECTOR const handler = connection => event => { if (event.type === eventTypes.EMPLOYEE_EMPLOYED) { return connection.collection(‘employees’).insert({ … }); } if (event.type === eventTypes.EMPLOYEE_OCCUPATION_CHANGED) { return connection.collection(‘employees’).update({ … }); } }; EVENT SOURCE EVERYTHING!
  • 52. PROJECTION REBUILD app.then(async (db) => { const eventStoreRepository = new EventStoreRepository(db, new Upcasters()); const projector = activeTasksProjector.rebuilder(db); const events = await eventStoreRepository.getAll(); projector(events).then(() => process.exit(0)); }); EVENT SOURCE EVERYTHING!
  • 53. ▸ IT IS CQRS ▸ REBUILD AGGREGATE FROM PREVIOUS EVENTS ▸ EVERY OPERATION EMITS DOMAIN EVENTS ▸ STORE EVENTS IN EVENT STORE ▸ PUBLISH THEM THROUGH EVENT BUS ▸ BUILD PROJECTIONS FROM PUBLISHED EVENTS ▸ REBUILD PROJECTIONS IF NECESSARY EVENT SOURCE EVERYTHING! SUMMARY
  • 56. ▸ REBUILD EVENT STORE (NOT A GOOD IDEA) ▸ UPCAST AT RUNTIME EVENT SOURCE EVERYTHING! VERSIONING
  • 58. UPCASTING { type: ‘REQUEST_APPROVED’, aggregateId: 1, version: 1, createDate: ‘2018-01-01 12:23:34’, payload: { id: ‘some-request-id’ } } { type: ‘REQUEST_APPROVED’, aggregateId: 1, version: 2, createDate: ‘2018-01-01 12:23:34’, payload: { id: ‘some-request-id’, approver: ‘Adam Polak’ } } EVENT SOURCE EVERYTHING!
  • 59. UPCASTING { type: ‘REQUEST_APPROVED’, aggregateId: 1, version: 1, createDate: ‘2018-01-01 12:23:34’, payload: { id: ‘some-request-id’ } } { type: ‘REQUEST_APPROVED’, aggregateId: 1, version: 2, createDate: ‘2018-01-01 12:23:34’, payload: { id: ‘some-request-id’, approver: ‘Adam Polak’ } } EVENT SOURCE EVERYTHING! UPCASTER
  • 60. class EventStoreRepository { constructor(db, upcasters) { … } async saveStream(events) { … } getAllEvents() { … } } getEventsFor(aggregateId) { return this.eventsCollection.find({ aggregateId }) .sort({ "payload.createDate": 1 }) .then(events => events.map(event => this.upcasters.upcast(event))); } EVENT SOURCE EVERYTHING! EVENT STORE REPOSITORY
  • 61. UPCASTER const eventTypes = require('../employee/events/types'); module.exports = { canUpcast: (event) => event.version === 1 && event.type === eventTypes.VACATION_REQUEST_APPROVED, upcast: (event) => ({ ...event, version: 2, payload: { ...event.payload, approver: ‘hr-member‘ } }) }; EVENT SOURCE EVERYTHING!
  • 64. ▸ AVOID LONG LIVING AGGREGATES ▸ SNAPSHOTS EVENT SOURCE EVERYTHING! PERFORMANCE IMPROVEMENTS
  • 66. 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 13:23:34’, payload: { description: ‘new-description’ } } { type: ‘TASK_SNAPSHOT, aggregateId: 1, aggregateVersion: 2, version: 1, createDate: ‘2018-01-01 14:23:34’, payload: { title: ‘some-title’, description: ‘new-description’ } } EVENT SOURCE EVERYTHING!