Created
September 2, 2016 06:52
-
-
Save sklppr/b3c7992984324305b7c660bc6f4973f9 to your computer and use it in GitHub Desktop.
Immutable domain model
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module SnakeCase | |
refine String do | |
def snake_case | |
self.gsub(/::/, '/') | |
.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2') | |
.gsub(/([a-z\d])([A-Z])/,'\1_\2') | |
.tr("-", "_") | |
.downcase | |
end | |
end | |
end | |
class AggregateRoot | |
def initialize | |
@uncommitted_events = [] | |
end | |
attr_reader :uncommitted_events | |
def load_from_history(history) | |
history.reduce(self) { |aggregate, event| aggregate.apply_event(event) }.mark_committed | |
end | |
protected | |
def apply_event(event) | |
handler = event_handler(event) | |
raise not_applicable(event) unless respond_to?(handler) | |
send(handler, event) | |
end | |
using SnakeCase | |
def event_handler(event) | |
"apply_#{event.class.name.snake_case.gsub(/struct\/([^_]+)_/, "")}" | |
end | |
def not_applicable(event) | |
"Event #{event.class }not applicable to #{self.class}" | |
end | |
def mark_committed | |
copy(self.class, uncommitted_events: []) | |
end | |
def record(event) | |
aggregate = apply_event(event) | |
aggregate.copy(aggregate.class, uncommitted_events: @uncommitted_events + [event]) | |
end | |
def copy(klass, data={}) | |
aggregate = klass.new | |
instance_variables_hash.merge(with_instance_variable_names(data)).each_pair do |var, val| | |
aggregate.instance_variable_set(var, val) | |
end | |
aggregate | |
end | |
def instance_variables_hash | |
Hash[ instance_variables.map { |var| [ var, instance_variable_get(var) ] } ] | |
end | |
def with_instance_variable_names(data) | |
Hash[ data.map { |var, val| [ (var.to_s.start_with?("@") ? var : "@#{var}"), val ] } ] | |
end | |
end | |
InvoiceCreated = Struct.new("InvoiceCreated") | |
InvoiceAmountSet = Struct.new("InvoiceAmountSet", :amount) | |
InvoicePaid = Struct.new("InvoicePaid", :date) | |
class Invoice < AggregateRoot | |
attr_reader :amount, :payment_date | |
def is_paid? | |
@is_paid | |
end | |
def apply_created(event) | |
copy(DraftInvoice) | |
end | |
end | |
class DraftInvoice < Invoice | |
def set_amount(amount) | |
record(InvoiceAmountSet.new(amount)) | |
end | |
def apply_amount_set(event) | |
copy(WrittenInvoice, amount: event.amount) | |
end | |
end | |
class WrittenInvoice < Invoice | |
def pay | |
record(InvoicePaid.new("today")) | |
end | |
def apply_paid(event) | |
copy(PaidInvoice, is_paid: true, payment_date: event.date) | |
end | |
end | |
class PaidInvoice < Invoice | |
end | |
invoice = Invoice.new | |
.load_from_history([ InvoiceCreated.new, InvoiceAmountSet.new(100) ]) | |
.pay | |
p invoice.uncommitted_events | |
p invoice.amount | |
p invoice.is_paid? | |
p invoice.payment_date |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment