SlideShare une entreprise Scribd logo
1  sur  30
Télécharger pour lire hors ligne
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/

Contenu connexe

Tendances

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 .NETJames Johnson
 
Persisting Data on SQLite using Room
Persisting Data on SQLite using RoomPersisting Data on SQLite using Room
Persisting Data on SQLite using RoomNelson Glauber Leal
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Railsrstankov
 
Backbone js
Backbone jsBackbone js
Backbone jsrstankov
 
Mvc - Model: the great forgotten
Mvc - Model: the great forgottenMvc - Model: the great forgotten
Mvc - Model: the great forgottenDavid Rodenas
 
iOS Beginners Lesson 4
iOS Beginners Lesson 4iOS Beginners Lesson 4
iOS Beginners Lesson 4Calvin 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 Playframeworkmaltiyadav
 
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 EngineMichael 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.1David Rodenas
 

Tendances (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
 

En vedette

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 KuhnZalando Technology
 
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 projectThomas Jaskula
 
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 AWSAmazon Web Services Japan
 

En vedette (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
 

Similaire à 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 2014Matthias 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 2015Matthias Noback
 
Pyimproved again
Pyimproved againPyimproved again
Pyimproved againrik0
 
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 ProgrammingAlex 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
 
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
 
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
 
Chapter 11.5
Chapter 11.5Chapter 11.5
Chapter 11.5sotlsoc
 
Knockoutjs UG meeting presentation
Knockoutjs UG meeting presentationKnockoutjs UG meeting presentation
Knockoutjs UG meeting presentationValdis 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 microservicesAndrás Papp
 
Think Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSThink Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSAdam 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 ComponentsAleksandar 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 ComponentsPSTechSerbia
 
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 BerlinIain Hull
 

Similaire à 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
 

Dernier

Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024The Digital Insurer
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CVKhem
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxMalak Abu Hammad
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024Results
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...Martijn de Jong
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEarley Information Science
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Miguel Araújo
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreternaman860154
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)Gabriella Davis
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)wesley chun
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024The Digital Insurer
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Enterprise Knowledge
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessPixlogix Infotech
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Scriptwesley chun
 

Dernier (20)

Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your Business
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 

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/