SlideShare a Scribd company logo
2 years after the first
event - The Saga Pattern
Message Driven Architecture
● Commands
● Events
Commands
params,
form objects,
real command objects
module Seating
class BookEntranceCommand
include Command
attribute :booking_id, String
attribute :event_id, Integer
attribute :seats, Array[Seat]
attribute :places, Array[GeneralAdmission]
validates_presence_of :event_id, :booking_id
validate do
unless (seats.present? || places.present?)
errors.add(:base, "Missing seats or places")
end
end
end
end
Events
module Seating
module Events
SeatingSetUp = Class.new(EventStore::Event)
SeatingDisabled = Class.new(EventStore::Event)
EntranceBooked = Class.new(EventStore::Event)
EntranceUnBooked = Class.new(EventStore::Event)
end
end
Send PDF via
Postal
1. PostalAddedToOrder
2. PostalAddressFilledOut
3. PdfGenerated
4. PaymentPaid
5. => SendPdfViaPostal
How did we get there?
Publish events
class RegisterUserService
def call(email, password)
user = User.register(email, password)
event_store.publish(Users::UserRegisteredByEmail.new(
id: user.id,
email: user.email,
))
end
end
Publish events
event_store.publish(SeasonPassConnectedToAConference.new({
pass: {
internal_id: pass.id,
external_id: pass.external_id,
},
season_id: season.id,
booking_id: booking_id(event, pass),
id: ticket.id,
event_id: event.id,
ticket_id: ticket.id,
ticket_type_id: ticket.ticket_type_id,
barcode: ticket.barcode,
seat: {
uuid: seat.uuid,
label: seat.label,
category: seat.category,
},
holder: {
user_id: holder.user_id,
email: holder.email,
name: holder.name,
},
}))
Introduce
handlers
module Search
class UserRegisteredHandler
singleton_class.prepend(YamlDeserializeFact)
def self.perform(event)
new.call(event)
rescue => e
Honeybadger.notify(e)
end
def call(event)
Elasticsearch::Model.client.index(
index: 'users',
type: 'admin_search_user',
id: event.data.fetch(:id),
body: {
email: event.data.fetch(:email)
})
end
end
end
Introduce
handlers
class WelcomeEmailForImportedUsers
singleton_class.prepend(YamlDeserializeFact)
@queue = :other
def call(fact)
data = fact.data
organizer = User.find(data.fetch(:organizer_id))
organization = Organization.find(data.fetch(:organization_id))
season = Season.find(data.fetch(:season_id))
config = Seasons::Config.find_by_organizer_id(organizer.id) ||
Seasons::Config.new(
class_name: "Seasons::Mailer",
method_name: "welcome_imported_season_pass_holder",
)
I18n.with_locale(organization.default_locale) do
config.class_name.constantize.send(config.method_name,
organization: organization,
email: data.fetch(:email),
reset_password_token: data.fetch(:password_token),
season_name: season.name,
organizer_name: organizer.name,
).deliver
end
end
end
One class
handles
multiple
events
TicketRefunded:
stream: "Order$%{order_id}"
handlers:
- Scanner::TicketRevokedHandler
- Salesforce::EventDataChangedHandler
- Reporting::Financials::TicketRefundedHandler
- Seating::ReleasePlaces
TicketCancelledByAdmin:
stream: "Order$%{order_id}"
handlers:
- Scanner::TicketRevokedHandler
module Scanner
class TicketRevokedHandler
singleton_class.prepend(YamlDeserializeFact)
def call(event)
data = event.data
revoke = Scanner::Revoke.new
revoke.call(
barcode: data.fetch(:barcode),
event_id: data.fetch(:event_id),
)
end
end
end
Handler+State=
Saga
class CheckBankSettingsSaga
singleton_class.prepend(::YamlDeserializeFact)
class State < ActiveRecord::Base
self.table_name = 'check_bank_settings_saga'
end
def call(event)
data = event.data
case event
when ConferenceCreated, ConferenceCloned
store_event(data)
when TicketKindCreated
check_bank_settings(data)
end
end
Handler+State=
Saga
def store_event(data)
State.create!(
event_id: data[:event_id],
organization_id: data[:organization_id],
organizer_id: data[:user_id],
checked: false
)
end
def check_bank_settings(data)
State.transaction do
record = State.lock.find_by_event_id(data[:event_id])
return if record.checked?
record.update_attribute(:checked, true)
Organizer::CheckBankSettings.new.call(
record.organizer_id,
record.organization_id,
record.event_id
)
end
end
end
Payment
paid too late
1. OrderPlaced
2. OrderExpired
3. PaymentPaid
4. => ReleasePayment
How to implement a Saga?
Technical details
Sync handlers
from EventStore
module Search
class UserRegisteredHandler
def self.perform(event)
new.call(event)
rescue => e
Honeybadger.notify(e)
end
def call(event)
Elasticsearch::Model.client.index(
index: 'users',
type: 'admin_search_user',
id: event.data.fetch(:id),
body: {
email: event.data.fetch(:email)
})
end
end
end
Contain
exceptions
in sync handlers
module Search
class UserRegisteredHandler
def self.perform(event)
new.call(event)
rescue => e
Honeybadger.notify(e)
end
end
end
class RegisterUserService
def call(email, password)
ActiveRecord::Base.transaction do
user = User.register(email, password)
event_store.publish(UserRegisteredByEmail.new(
id: user.id,
email: user.email,
))
end
end
end
Async handlers
def call(handler_class, event)
if handler_class.instance_variable_defined?(:@queue)
Resque.enqueue(handler_class, YAML.dump(event))
else
handler_class.perform(YAML.dump(event))
end
end
Async handlers
(after commit)
def call(handler_class, event)
if handler_class.instance_variable_defined?(:@queue)
if ActiveRecord::Base.connection.transaction_open?
ActiveRecord::Base.
connection.
current_transaction.
add_record( FakeActiveRecord.new(
handler,
YAML.dump(event))
)
else
Resque.enqueue(handler_class, YAML.dump(event))
end
else
handler_class.perform(YAML.dump(event))
end
end
https://blog.arkency.com/2015/10/run-it-in-background-job-after-commit/
YAML
module Search
class UserRegisteredHandler
singleton_class.prepend(::YamlDeserializeEvent)
def self.perform(event)
new.call(event)
rescue => e
Honeybadger.notify(e)
end
end
end
module YamlDeserializeFact
def perform(arg)
case arg
when String
super(YAML.load(arg))
else
super
end
end
end
Re-raise
exceptions
in async
handlers
module Search
class UserRegisteredHandler
@queue = :low_priority
def self.perform(event)
new.call(event)
rescue => e
Honeybadger.notify(e)
raise
end
end
end
Initializing the
state of a saga
class PostalSaga
singleton_class.prepend(YamlDeserializeFact)
@queue = :low_priority
# add_index "sagas", ["order_id"], unique: true
class State < ActiveRecord::Base
self.table_name = 'sagas'
def self.get_by_order_id(order_id) do
transaction do
yield lock.find_or_create_by(order_id: order_id)
end
rescue ActiveRecord::RecordNotUnique
retry
end
end
def call(fact)
data = fact.data
State.get_by_order_id(data.fetch(:order_id)) do |state|
state.do_something
state.save!
end
end
end
Processing an
event by a saga
class Postal::FilledOut
singleton_class.prepend(YamlDeserializeFact)
@queue = :low_priority
def self.perform(event)
new().call(event)
rescue => e
Honeybadger.notify(e, { context: { event: event } } )
raise
end
def call(event)
data = event.data
order_id = data.fetch(:order_id)
State.get_by_order_id(order_id) do |state|
state.filled_out(
filled_out_at: Time.zone.now,
adapter: Rails.configuration.insurance_adapter,
)
end
end
end
Triggering a
command
class Postal::State < ActiveRecord::Base
def added_to_basket(added_to_basket_at:, uploader:)
self.added_to_basket_at ||= added_to_basket_at
save!
maybe_send_postal_via_api(uploader: uploader)
end
def filled_out(filled_out_at:, uploader:)
self.filled_out_at ||= filled_out_at
save!
maybe_send_postal_via_api(uploader: uploader)
end
def paid(paid_at:, uploader:)
self.paid_at ||= paid_at
save!
maybe_send_postal_via_api(uploader: uploader)
end
def tickets_pdf_generated(generated_at:, pdf_id:, uploader:)
return if self.tickets_generated_at
self.tickets_generated_at ||= generated_at
self.pdf_id ||= pdf_id
save!
maybe_send_postal_via_api(uploader: uploader)
end
Triggering a
command
class Postal::State < ActiveRecord::Base
private
def maybe_send_postal_via_api(uploader:)
return unless added_to_basket_at && paid_at && filled_out_at
tickets_generated_at
return if uploaded_at
uploader.transmit(Pdf.find(pdf_id))
self.uploaded_at = Time.now
save!
rescue
# error handling...
end
end
Triggering a
command
(better way)
class Postal::State < ActiveRecord::Base
private
def maybe_send_postal_via_api
return unless added_to_basket_at && paid_at && filled_out_at
tickets_generated_at
return if uploaded_at
self.uploaded_at = Time.now
save!
command_bus.send(DeliverPostalPdf.new({
order_id: order_id,
pdf_id: pdf_id
}))
end
end
https://github.com/pawelpacana/command_bus
Thank you!
Make events, not CRUD
35% DISCOUNT
with code
WROCLOVE2016
https://blog.arkency.com/products/
Resources
● original saga patterns
○ http://vasters.
com/clemensv/2012/09/01/Sagas.aspx
○ http://kellabyte.com/2012/05/30/clarifying-the-
saga-pattern/
● saga - process manager
○ https://msdn.microsoft.com/en-
us/library/jj591569.aspx
○ http://udidahan.com/2009/04/20/saga-
persistence-and-event-driven-architectures/
● other
○ http://verraes.net/2014/05/functional-
foundation-for-cqrs-event-sourcing/
○ http://stackoverflow.
com/questions/15528015/what-is-the-difference-
between-a-saga-a-process-manager-and-a-
document-based-ap
○ http://theawkwardyeti.com/comic/moment/

More Related Content

What's hot

DOM and Events
DOM and EventsDOM and Events
DOM and Events
Julie Iskander
 
J Query Presentation of David
J Query Presentation of DavidJ Query Presentation of David
J Query Presentation of David
Arun David Johnson R
 
A Rich Web Experience with jQuery, Ajax and .NET
A Rich Web Experience with jQuery, Ajax and .NETA Rich Web Experience with jQuery, Ajax and .NET
A Rich Web Experience with jQuery, Ajax and .NET
James Johnson
 
Why ruby
Why rubyWhy ruby
Why ruby
rstankov
 
Persisting Data on SQLite using Room
Persisting Data on SQLite using RoomPersisting Data on SQLite using Room
Persisting Data on SQLite using Room
Nelson Glauber Leal
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Rails
rstankov
 
Backbone js
Backbone jsBackbone js
Backbone js
rstankov
 
Mvc - Model: the great forgotten
Mvc - Model: the great forgottenMvc - Model: the great forgotten
Mvc - Model: the great forgotten
David Rodenas
 
Active record(1)
Active record(1)Active record(1)
Active record(1)
Ananta Lamichhane
 
MVS: An angular MVC
MVS: An angular MVCMVS: An angular MVC
MVS: An angular MVC
David Rodenas
 
jQuery, CSS3 and ColdFusion
jQuery, CSS3 and ColdFusionjQuery, CSS3 and ColdFusion
jQuery, CSS3 and ColdFusion
Denard Springle IV
 
iOS Beginners Lesson 4
iOS Beginners Lesson 4iOS Beginners Lesson 4
iOS Beginners Lesson 4
Calvin Cheng
 
JQuery New Evolution
JQuery New EvolutionJQuery New Evolution
JQuery New EvolutionAllan Huang
 
A single language for backend and frontend from AngularJS to cloud with Clau...
A single language for backend and frontend  from AngularJS to cloud with Clau...A single language for backend and frontend  from AngularJS to cloud with Clau...
A single language for backend and frontend from AngularJS to cloud with Clau...
Walter Dal Mut
 
Intorduction of Playframework
Intorduction of PlayframeworkIntorduction of Playframework
Intorduction of Playframework
maltiyadav
 
A To-do Web App on Google App Engine
A To-do Web App on Google App EngineA To-do Web App on Google App Engine
A To-do Web App on Google App Engine
Michael Parker
 
iOS. EventKit Framework. Work with calendars and reminders
iOS. EventKit Framework. Work with calendars and remindersiOS. EventKit Framework. Work with calendars and reminders
iOS. EventKit Framework. Work with calendars and remindersVoityuk Alexander
 
Modules in angular 2.0 beta.1
Modules in angular 2.0 beta.1Modules in angular 2.0 beta.1
Modules in angular 2.0 beta.1
David Rodenas
 

What's hot (19)

DOM and Events
DOM and EventsDOM and Events
DOM and Events
 
J Query Presentation of David
J Query Presentation of DavidJ Query Presentation of David
J Query Presentation of David
 
jQuery secrets
jQuery secretsjQuery secrets
jQuery secrets
 
A Rich Web Experience with jQuery, Ajax and .NET
A Rich Web Experience with jQuery, Ajax and .NETA Rich Web Experience with jQuery, Ajax and .NET
A Rich Web Experience with jQuery, Ajax and .NET
 
Why ruby
Why rubyWhy ruby
Why ruby
 
Persisting Data on SQLite using Room
Persisting Data on SQLite using RoomPersisting Data on SQLite using Room
Persisting Data on SQLite using Room
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Rails
 
Backbone js
Backbone jsBackbone js
Backbone js
 
Mvc - Model: the great forgotten
Mvc - Model: the great forgottenMvc - Model: the great forgotten
Mvc - Model: the great forgotten
 
Active record(1)
Active record(1)Active record(1)
Active record(1)
 
MVS: An angular MVC
MVS: An angular MVCMVS: An angular MVC
MVS: An angular MVC
 
jQuery, CSS3 and ColdFusion
jQuery, CSS3 and ColdFusionjQuery, CSS3 and ColdFusion
jQuery, CSS3 and ColdFusion
 
iOS Beginners Lesson 4
iOS Beginners Lesson 4iOS Beginners Lesson 4
iOS Beginners Lesson 4
 
JQuery New Evolution
JQuery New EvolutionJQuery New Evolution
JQuery New Evolution
 
A single language for backend and frontend from AngularJS to cloud with Clau...
A single language for backend and frontend  from AngularJS to cloud with Clau...A single language for backend and frontend  from AngularJS to cloud with Clau...
A single language for backend and frontend from AngularJS to cloud with Clau...
 
Intorduction of Playframework
Intorduction of PlayframeworkIntorduction of Playframework
Intorduction of Playframework
 
A To-do Web App on Google App Engine
A To-do Web App on Google App EngineA To-do Web App on Google App Engine
A To-do Web App on Google App Engine
 
iOS. EventKit Framework. Work with calendars and reminders
iOS. EventKit Framework. Work with calendars and remindersiOS. EventKit Framework. Work with calendars and reminders
iOS. EventKit Framework. Work with calendars and reminders
 
Modules in angular 2.0 beta.1
Modules in angular 2.0 beta.1Modules in angular 2.0 beta.1
Modules in angular 2.0 beta.1
 

Viewers also liked

Reactive Design Patterns: a talk by Typesafe's Dr. Roland Kuhn
Reactive Design Patterns: a talk by Typesafe's Dr. Roland KuhnReactive Design Patterns: a talk by Typesafe's Dr. Roland Kuhn
Reactive Design Patterns: a talk by Typesafe's Dr. Roland Kuhn
Zalando Technology
 
Domain Driven Design
Domain Driven DesignDomain Driven Design
Domain Driven Design
Pascal Larocque
 
Leveraging more then DDD Lite in the startup project
Leveraging more then DDD Lite in the startup projectLeveraging more then DDD Lite in the startup project
Leveraging more then DDD Lite in the startup project
Thomas Jaskula
 
E-VAN - Sagas
E-VAN  - SagasE-VAN  - Sagas
E-VAN - Sagas
andreasohlund
 
Reactive Design Patterns
Reactive Design PatternsReactive Design Patterns
Reactive Design Patterns
Legacy Typesafe (now Lightbend)
 
Developing event-driven microservices with event sourcing and CQRS (svcc, sv...
Developing event-driven microservices with event sourcing and CQRS  (svcc, sv...Developing event-driven microservices with event sourcing and CQRS  (svcc, sv...
Developing event-driven microservices with event sourcing and CQRS (svcc, sv...
Chris Richardson
 
AWS Black Belt Online Seminar 2017 Docker on AWS
AWS Black Belt Online Seminar 2017 Docker on AWSAWS Black Belt Online Seminar 2017 Docker on AWS
AWS Black Belt Online Seminar 2017 Docker on AWS
Amazon Web Services Japan
 

Viewers also liked (7)

Reactive Design Patterns: a talk by Typesafe's Dr. Roland Kuhn
Reactive Design Patterns: a talk by Typesafe's Dr. Roland KuhnReactive Design Patterns: a talk by Typesafe's Dr. Roland Kuhn
Reactive Design Patterns: a talk by Typesafe's Dr. Roland Kuhn
 
Domain Driven Design
Domain Driven DesignDomain Driven Design
Domain Driven Design
 
Leveraging more then DDD Lite in the startup project
Leveraging more then DDD Lite in the startup projectLeveraging more then DDD Lite in the startup project
Leveraging more then DDD Lite in the startup project
 
E-VAN - Sagas
E-VAN  - SagasE-VAN  - Sagas
E-VAN - Sagas
 
Reactive Design Patterns
Reactive Design PatternsReactive Design Patterns
Reactive Design Patterns
 
Developing event-driven microservices with event sourcing and CQRS (svcc, sv...
Developing event-driven microservices with event sourcing and CQRS  (svcc, sv...Developing event-driven microservices with event sourcing and CQRS  (svcc, sv...
Developing event-driven microservices with event sourcing and CQRS (svcc, sv...
 
AWS Black Belt Online Seminar 2017 Docker on AWS
AWS Black Belt Online Seminar 2017 Docker on AWSAWS Black Belt Online Seminar 2017 Docker on AWS
AWS Black Belt Online Seminar 2017 Docker on AWS
 

Similar to 2 years after the first event - The Saga Pattern

A Series of Fortunate Events - Symfony Camp Sweden 2014
A Series of Fortunate Events - Symfony Camp Sweden 2014A Series of Fortunate Events - Symfony Camp Sweden 2014
A Series of Fortunate Events - Symfony Camp Sweden 2014
Matthias Noback
 
A Series of Fortunate Events - PHP Benelux Conference 2015
A Series of Fortunate Events - PHP Benelux Conference 2015A Series of Fortunate Events - PHP Benelux Conference 2015
A Series of Fortunate Events - PHP Benelux Conference 2015
Matthias Noback
 
Pyimproved again
Pyimproved againPyimproved again
Pyimproved again
rik0
 
Scala UA: Big Step To Functional Programming
Scala UA: Big Step To Functional ProgrammingScala UA: Big Step To Functional Programming
Scala UA: Big Step To Functional Programming
Alex Fruzenshtein
 
Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)
Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)
Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)
mircodotta
 
Dependency injection in Scala
Dependency injection in ScalaDependency injection in Scala
Dependency injection in Scala
Alf Kristian Støyle
 
The Rule of 10,000 Spark Jobs - Learning from Exceptions and Serializing Your...
The Rule of 10,000 Spark Jobs - Learning from Exceptions and Serializing Your...The Rule of 10,000 Spark Jobs - Learning from Exceptions and Serializing Your...
The Rule of 10,000 Spark Jobs - Learning from Exceptions and Serializing Your...
Matthew Tovbin
 
The Rule of 10,000 Spark Jobs: Learning From Exceptions and Serializing Your ...
The Rule of 10,000 Spark Jobs: Learning From Exceptions and Serializing Your ...The Rule of 10,000 Spark Jobs: Learning From Exceptions and Serializing Your ...
The Rule of 10,000 Spark Jobs: Learning From Exceptions and Serializing Your ...
Databricks
 
Event Driven Applications in F#
Event Driven Applications in F#Event Driven Applications in F#
Event Driven Applications in F#
☁️ Mikhail Shilkov
 
Ext Js Events
Ext Js EventsExt Js Events
Ext Js Events
jason hu 金良胡
 
Ext Js Events
Ext Js EventsExt Js Events
Ext Js Events
jason hu 金良胡
 
Chapter 11.5
Chapter 11.5Chapter 11.5
Chapter 11.5sotlsoc
 
Magic of Ruby
Magic of RubyMagic of Ruby
Magic of Ruby
Gabriele Lana
 
Knockoutjs UG meeting presentation
Knockoutjs UG meeting presentationKnockoutjs UG meeting presentation
Knockoutjs UG meeting presentation
Valdis Iljuconoks
 
Functional programming techniques in real-world microservices
Functional programming techniques in real-world microservicesFunctional programming techniques in real-world microservices
Functional programming techniques in real-world microservices
András Papp
 
Think Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSThink Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJS
Adam L Barrett
 
Java Svet - Communication Between Android App Components
Java Svet - Communication Between Android App ComponentsJava Svet - Communication Between Android App Components
Java Svet - Communication Between Android App Components
Aleksandar Ilić
 
Java Svet - Communication Between Android App Components
Java Svet - Communication Between Android App ComponentsJava Svet - Communication Between Android App Components
Java Svet - Communication Between Android App Components
PSTechSerbia
 
Booa8 Slide 09
Booa8 Slide 09Booa8 Slide 09
Booa8 Slide 09oswchavez
 
Improving Correctness With Type - Goto Con Berlin
Improving Correctness With Type - Goto Con BerlinImproving Correctness With Type - Goto Con Berlin
Improving Correctness With Type - Goto Con Berlin
Iain Hull
 

Similar to 2 years after the first event - The Saga Pattern (20)

A Series of Fortunate Events - Symfony Camp Sweden 2014
A Series of Fortunate Events - Symfony Camp Sweden 2014A Series of Fortunate Events - Symfony Camp Sweden 2014
A Series of Fortunate Events - Symfony Camp Sweden 2014
 
A Series of Fortunate Events - PHP Benelux Conference 2015
A Series of Fortunate Events - PHP Benelux Conference 2015A Series of Fortunate Events - PHP Benelux Conference 2015
A Series of Fortunate Events - PHP Benelux Conference 2015
 
Pyimproved again
Pyimproved againPyimproved again
Pyimproved again
 
Scala UA: Big Step To Functional Programming
Scala UA: Big Step To Functional ProgrammingScala UA: Big Step To Functional Programming
Scala UA: Big Step To Functional Programming
 
Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)
Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)
Lightbend Lagom: Microservices Just Right (Scala Days 2016 Berlin)
 
Dependency injection in Scala
Dependency injection in ScalaDependency injection in Scala
Dependency injection in Scala
 
The Rule of 10,000 Spark Jobs - Learning from Exceptions and Serializing Your...
The Rule of 10,000 Spark Jobs - Learning from Exceptions and Serializing Your...The Rule of 10,000 Spark Jobs - Learning from Exceptions and Serializing Your...
The Rule of 10,000 Spark Jobs - Learning from Exceptions and Serializing Your...
 
The Rule of 10,000 Spark Jobs: Learning From Exceptions and Serializing Your ...
The Rule of 10,000 Spark Jobs: Learning From Exceptions and Serializing Your ...The Rule of 10,000 Spark Jobs: Learning From Exceptions and Serializing Your ...
The Rule of 10,000 Spark Jobs: Learning From Exceptions and Serializing Your ...
 
Event Driven Applications in F#
Event Driven Applications in F#Event Driven Applications in F#
Event Driven Applications in F#
 
Ext Js Events
Ext Js EventsExt Js Events
Ext Js Events
 
Ext Js Events
Ext Js EventsExt Js Events
Ext Js Events
 
Chapter 11.5
Chapter 11.5Chapter 11.5
Chapter 11.5
 
Magic of Ruby
Magic of RubyMagic of Ruby
Magic of Ruby
 
Knockoutjs UG meeting presentation
Knockoutjs UG meeting presentationKnockoutjs UG meeting presentation
Knockoutjs UG meeting presentation
 
Functional programming techniques in real-world microservices
Functional programming techniques in real-world microservicesFunctional programming techniques in real-world microservices
Functional programming techniques in real-world microservices
 
Think Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSThink Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJS
 
Java Svet - Communication Between Android App Components
Java Svet - Communication Between Android App ComponentsJava Svet - Communication Between Android App Components
Java Svet - Communication Between Android App Components
 
Java Svet - Communication Between Android App Components
Java Svet - Communication Between Android App ComponentsJava Svet - Communication Between Android App Components
Java Svet - Communication Between Android App Components
 
Booa8 Slide 09
Booa8 Slide 09Booa8 Slide 09
Booa8 Slide 09
 
Improving Correctness With Type - Goto Con Berlin
Improving Correctness With Type - Goto Con BerlinImproving Correctness With Type - Goto Con Berlin
Improving Correctness With Type - Goto Con Berlin
 

Recently uploaded

Essentials of Automations: Optimizing FME Workflows with Parameters
Essentials of Automations: Optimizing FME Workflows with ParametersEssentials of Automations: Optimizing FME Workflows with Parameters
Essentials of Automations: Optimizing FME Workflows with Parameters
Safe Software
 
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
Product School
 
FIDO Alliance Osaka Seminar: FIDO Security Aspects.pdf
FIDO Alliance Osaka Seminar: FIDO Security Aspects.pdfFIDO Alliance Osaka Seminar: FIDO Security Aspects.pdf
FIDO Alliance Osaka Seminar: FIDO Security Aspects.pdf
FIDO Alliance
 
PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)
Ralf Eggert
 
DevOps and Testing slides at DASA Connect
DevOps and Testing slides at DASA ConnectDevOps and Testing slides at DASA Connect
DevOps and Testing slides at DASA Connect
Kari Kakkonen
 
LF Energy Webinar: Electrical Grid Modelling and Simulation Through PowSyBl -...
LF Energy Webinar: Electrical Grid Modelling and Simulation Through PowSyBl -...LF Energy Webinar: Electrical Grid Modelling and Simulation Through PowSyBl -...
LF Energy Webinar: Electrical Grid Modelling and Simulation Through PowSyBl -...
DanBrown980551
 
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdfFIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance
 
When stars align: studies in data quality, knowledge graphs, and machine lear...
When stars align: studies in data quality, knowledge graphs, and machine lear...When stars align: studies in data quality, knowledge graphs, and machine lear...
When stars align: studies in data quality, knowledge graphs, and machine lear...
Elena Simperl
 
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
BookNet Canada
 
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Product School
 
Generative AI Deep Dive: Advancing from Proof of Concept to Production
Generative AI Deep Dive: Advancing from Proof of Concept to ProductionGenerative AI Deep Dive: Advancing from Proof of Concept to Production
Generative AI Deep Dive: Advancing from Proof of Concept to Production
Aggregage
 
Secstrike : Reverse Engineering & Pwnable tools for CTF.pptx
Secstrike : Reverse Engineering & Pwnable tools for CTF.pptxSecstrike : Reverse Engineering & Pwnable tools for CTF.pptx
Secstrike : Reverse Engineering & Pwnable tools for CTF.pptx
nkrafacyberclub
 
Welocme to ViralQR, your best QR code generator.
Welocme to ViralQR, your best QR code generator.Welocme to ViralQR, your best QR code generator.
Welocme to ViralQR, your best QR code generator.
ViralQR
 
The Future of Platform Engineering
The Future of Platform EngineeringThe Future of Platform Engineering
The Future of Platform Engineering
Jemma Hussein Allen
 
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
UiPathCommunity
 
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
Sri Ambati
 
Key Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdfKey Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdf
Cheryl Hung
 
Quantum Computing: Current Landscape and the Future Role of APIs
Quantum Computing: Current Landscape and the Future Role of APIsQuantum Computing: Current Landscape and the Future Role of APIs
Quantum Computing: Current Landscape and the Future Role of APIs
Vlad Stirbu
 
Elevating Tactical DDD Patterns Through Object Calisthenics
Elevating Tactical DDD Patterns Through Object CalisthenicsElevating Tactical DDD Patterns Through Object Calisthenics
Elevating Tactical DDD Patterns Through Object Calisthenics
Dorra BARTAGUIZ
 
Le nuove frontiere dell'AI nell'RPA con UiPath Autopilot™
Le nuove frontiere dell'AI nell'RPA con UiPath Autopilot™Le nuove frontiere dell'AI nell'RPA con UiPath Autopilot™
Le nuove frontiere dell'AI nell'RPA con UiPath Autopilot™
UiPathCommunity
 

Recently uploaded (20)

Essentials of Automations: Optimizing FME Workflows with Parameters
Essentials of Automations: Optimizing FME Workflows with ParametersEssentials of Automations: Optimizing FME Workflows with Parameters
Essentials of Automations: Optimizing FME Workflows with Parameters
 
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
De-mystifying Zero to One: Design Informed Techniques for Greenfield Innovati...
 
FIDO Alliance Osaka Seminar: FIDO Security Aspects.pdf
FIDO Alliance Osaka Seminar: FIDO Security Aspects.pdfFIDO Alliance Osaka Seminar: FIDO Security Aspects.pdf
FIDO Alliance Osaka Seminar: FIDO Security Aspects.pdf
 
PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)
 
DevOps and Testing slides at DASA Connect
DevOps and Testing slides at DASA ConnectDevOps and Testing slides at DASA Connect
DevOps and Testing slides at DASA Connect
 
LF Energy Webinar: Electrical Grid Modelling and Simulation Through PowSyBl -...
LF Energy Webinar: Electrical Grid Modelling and Simulation Through PowSyBl -...LF Energy Webinar: Electrical Grid Modelling and Simulation Through PowSyBl -...
LF Energy Webinar: Electrical Grid Modelling and Simulation Through PowSyBl -...
 
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdfFIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
 
When stars align: studies in data quality, knowledge graphs, and machine lear...
When stars align: studies in data quality, knowledge graphs, and machine lear...When stars align: studies in data quality, knowledge graphs, and machine lear...
When stars align: studies in data quality, knowledge graphs, and machine lear...
 
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
 
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
 
Generative AI Deep Dive: Advancing from Proof of Concept to Production
Generative AI Deep Dive: Advancing from Proof of Concept to ProductionGenerative AI Deep Dive: Advancing from Proof of Concept to Production
Generative AI Deep Dive: Advancing from Proof of Concept to Production
 
Secstrike : Reverse Engineering & Pwnable tools for CTF.pptx
Secstrike : Reverse Engineering & Pwnable tools for CTF.pptxSecstrike : Reverse Engineering & Pwnable tools for CTF.pptx
Secstrike : Reverse Engineering & Pwnable tools for CTF.pptx
 
Welocme to ViralQR, your best QR code generator.
Welocme to ViralQR, your best QR code generator.Welocme to ViralQR, your best QR code generator.
Welocme to ViralQR, your best QR code generator.
 
The Future of Platform Engineering
The Future of Platform EngineeringThe Future of Platform Engineering
The Future of Platform Engineering
 
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
 
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
 
Key Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdfKey Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdf
 
Quantum Computing: Current Landscape and the Future Role of APIs
Quantum Computing: Current Landscape and the Future Role of APIsQuantum Computing: Current Landscape and the Future Role of APIs
Quantum Computing: Current Landscape and the Future Role of APIs
 
Elevating Tactical DDD Patterns Through Object Calisthenics
Elevating Tactical DDD Patterns Through Object CalisthenicsElevating Tactical DDD Patterns Through Object Calisthenics
Elevating Tactical DDD Patterns Through Object Calisthenics
 
Le nuove frontiere dell'AI nell'RPA con UiPath Autopilot™
Le nuove frontiere dell'AI nell'RPA con UiPath Autopilot™Le nuove frontiere dell'AI nell'RPA con UiPath Autopilot™
Le nuove frontiere dell'AI nell'RPA con UiPath Autopilot™
 

2 years after the first event - The Saga Pattern

  • 1.
  • 2. 2 years after the first event - The Saga Pattern
  • 3. Message Driven Architecture ● Commands ● Events
  • 4. Commands params, form objects, real command objects module Seating class BookEntranceCommand include Command attribute :booking_id, String attribute :event_id, Integer attribute :seats, Array[Seat] attribute :places, Array[GeneralAdmission] validates_presence_of :event_id, :booking_id validate do unless (seats.present? || places.present?) errors.add(:base, "Missing seats or places") end end end end
  • 5. Events module Seating module Events SeatingSetUp = Class.new(EventStore::Event) SeatingDisabled = Class.new(EventStore::Event) EntranceBooked = Class.new(EventStore::Event) EntranceUnBooked = Class.new(EventStore::Event) end end
  • 6. Send PDF via Postal 1. PostalAddedToOrder 2. PostalAddressFilledOut 3. PdfGenerated 4. PaymentPaid 5. => SendPdfViaPostal
  • 7. How did we get there?
  • 8. Publish events class RegisterUserService def call(email, password) user = User.register(email, password) event_store.publish(Users::UserRegisteredByEmail.new( id: user.id, email: user.email, )) end end
  • 9. Publish events event_store.publish(SeasonPassConnectedToAConference.new({ pass: { internal_id: pass.id, external_id: pass.external_id, }, season_id: season.id, booking_id: booking_id(event, pass), id: ticket.id, event_id: event.id, ticket_id: ticket.id, ticket_type_id: ticket.ticket_type_id, barcode: ticket.barcode, seat: { uuid: seat.uuid, label: seat.label, category: seat.category, }, holder: { user_id: holder.user_id, email: holder.email, name: holder.name, }, }))
  • 10. Introduce handlers module Search class UserRegisteredHandler singleton_class.prepend(YamlDeserializeFact) def self.perform(event) new.call(event) rescue => e Honeybadger.notify(e) end def call(event) Elasticsearch::Model.client.index( index: 'users', type: 'admin_search_user', id: event.data.fetch(:id), body: { email: event.data.fetch(:email) }) end end end
  • 11. Introduce handlers class WelcomeEmailForImportedUsers singleton_class.prepend(YamlDeserializeFact) @queue = :other def call(fact) data = fact.data organizer = User.find(data.fetch(:organizer_id)) organization = Organization.find(data.fetch(:organization_id)) season = Season.find(data.fetch(:season_id)) config = Seasons::Config.find_by_organizer_id(organizer.id) || Seasons::Config.new( class_name: "Seasons::Mailer", method_name: "welcome_imported_season_pass_holder", ) I18n.with_locale(organization.default_locale) do config.class_name.constantize.send(config.method_name, organization: organization, email: data.fetch(:email), reset_password_token: data.fetch(:password_token), season_name: season.name, organizer_name: organizer.name, ).deliver end end end
  • 12. One class handles multiple events TicketRefunded: stream: "Order$%{order_id}" handlers: - Scanner::TicketRevokedHandler - Salesforce::EventDataChangedHandler - Reporting::Financials::TicketRefundedHandler - Seating::ReleasePlaces TicketCancelledByAdmin: stream: "Order$%{order_id}" handlers: - Scanner::TicketRevokedHandler module Scanner class TicketRevokedHandler singleton_class.prepend(YamlDeserializeFact) def call(event) data = event.data revoke = Scanner::Revoke.new revoke.call( barcode: data.fetch(:barcode), event_id: data.fetch(:event_id), ) end end end
  • 13. Handler+State= Saga class CheckBankSettingsSaga singleton_class.prepend(::YamlDeserializeFact) class State < ActiveRecord::Base self.table_name = 'check_bank_settings_saga' end def call(event) data = event.data case event when ConferenceCreated, ConferenceCloned store_event(data) when TicketKindCreated check_bank_settings(data) end end
  • 14. Handler+State= Saga def store_event(data) State.create!( event_id: data[:event_id], organization_id: data[:organization_id], organizer_id: data[:user_id], checked: false ) end def check_bank_settings(data) State.transaction do record = State.lock.find_by_event_id(data[:event_id]) return if record.checked? record.update_attribute(:checked, true) Organizer::CheckBankSettings.new.call( record.organizer_id, record.organization_id, record.event_id ) end end end
  • 15. Payment paid too late 1. OrderPlaced 2. OrderExpired 3. PaymentPaid 4. => ReleasePayment
  • 16. How to implement a Saga? Technical details
  • 17. Sync handlers from EventStore module Search class UserRegisteredHandler def self.perform(event) new.call(event) rescue => e Honeybadger.notify(e) end def call(event) Elasticsearch::Model.client.index( index: 'users', type: 'admin_search_user', id: event.data.fetch(:id), body: { email: event.data.fetch(:email) }) end end end
  • 18. Contain exceptions in sync handlers module Search class UserRegisteredHandler def self.perform(event) new.call(event) rescue => e Honeybadger.notify(e) end end end class RegisterUserService def call(email, password) ActiveRecord::Base.transaction do user = User.register(email, password) event_store.publish(UserRegisteredByEmail.new( id: user.id, email: user.email, )) end end end
  • 19. Async handlers def call(handler_class, event) if handler_class.instance_variable_defined?(:@queue) Resque.enqueue(handler_class, YAML.dump(event)) else handler_class.perform(YAML.dump(event)) end end
  • 20. Async handlers (after commit) def call(handler_class, event) if handler_class.instance_variable_defined?(:@queue) if ActiveRecord::Base.connection.transaction_open? ActiveRecord::Base. connection. current_transaction. add_record( FakeActiveRecord.new( handler, YAML.dump(event)) ) else Resque.enqueue(handler_class, YAML.dump(event)) end else handler_class.perform(YAML.dump(event)) end end https://blog.arkency.com/2015/10/run-it-in-background-job-after-commit/
  • 21. YAML module Search class UserRegisteredHandler singleton_class.prepend(::YamlDeserializeEvent) def self.perform(event) new.call(event) rescue => e Honeybadger.notify(e) end end end module YamlDeserializeFact def perform(arg) case arg when String super(YAML.load(arg)) else super end end end
  • 22. Re-raise exceptions in async handlers module Search class UserRegisteredHandler @queue = :low_priority def self.perform(event) new.call(event) rescue => e Honeybadger.notify(e) raise end end end
  • 23. Initializing the state of a saga class PostalSaga singleton_class.prepend(YamlDeserializeFact) @queue = :low_priority # add_index "sagas", ["order_id"], unique: true class State < ActiveRecord::Base self.table_name = 'sagas' def self.get_by_order_id(order_id) do transaction do yield lock.find_or_create_by(order_id: order_id) end rescue ActiveRecord::RecordNotUnique retry end end def call(fact) data = fact.data State.get_by_order_id(data.fetch(:order_id)) do |state| state.do_something state.save! end end end
  • 24. Processing an event by a saga class Postal::FilledOut singleton_class.prepend(YamlDeserializeFact) @queue = :low_priority def self.perform(event) new().call(event) rescue => e Honeybadger.notify(e, { context: { event: event } } ) raise end def call(event) data = event.data order_id = data.fetch(:order_id) State.get_by_order_id(order_id) do |state| state.filled_out( filled_out_at: Time.zone.now, adapter: Rails.configuration.insurance_adapter, ) end end end
  • 25. Triggering a command class Postal::State < ActiveRecord::Base def added_to_basket(added_to_basket_at:, uploader:) self.added_to_basket_at ||= added_to_basket_at save! maybe_send_postal_via_api(uploader: uploader) end def filled_out(filled_out_at:, uploader:) self.filled_out_at ||= filled_out_at save! maybe_send_postal_via_api(uploader: uploader) end def paid(paid_at:, uploader:) self.paid_at ||= paid_at save! maybe_send_postal_via_api(uploader: uploader) end def tickets_pdf_generated(generated_at:, pdf_id:, uploader:) return if self.tickets_generated_at self.tickets_generated_at ||= generated_at self.pdf_id ||= pdf_id save! maybe_send_postal_via_api(uploader: uploader) end
  • 26. Triggering a command class Postal::State < ActiveRecord::Base private def maybe_send_postal_via_api(uploader:) return unless added_to_basket_at && paid_at && filled_out_at tickets_generated_at return if uploaded_at uploader.transmit(Pdf.find(pdf_id)) self.uploaded_at = Time.now save! rescue # error handling... end end
  • 27. Triggering a command (better way) class Postal::State < ActiveRecord::Base private def maybe_send_postal_via_api return unless added_to_basket_at && paid_at && filled_out_at tickets_generated_at return if uploaded_at self.uploaded_at = Time.now save! command_bus.send(DeliverPostalPdf.new({ order_id: order_id, pdf_id: pdf_id })) end end https://github.com/pawelpacana/command_bus
  • 30. Resources ● original saga patterns ○ http://vasters. com/clemensv/2012/09/01/Sagas.aspx ○ http://kellabyte.com/2012/05/30/clarifying-the- saga-pattern/ ● saga - process manager ○ https://msdn.microsoft.com/en- us/library/jj591569.aspx ○ http://udidahan.com/2009/04/20/saga- persistence-and-event-driven-architectures/ ● other ○ http://verraes.net/2014/05/functional- foundation-for-cqrs-event-sourcing/ ○ http://stackoverflow. com/questions/15528015/what-is-the-difference- between-a-saga-a-process-manager-and-a- document-based-ap ○ http://theawkwardyeti.com/comic/moment/