Skip to content

Instantly share code, notes, and snippets.

@sklppr
Created September 2, 2016 06:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sklppr/b3c7992984324305b7c660bc6f4973f9 to your computer and use it in GitHub Desktop.
Save sklppr/b3c7992984324305b7c660bc6f4973f9 to your computer and use it in GitHub Desktop.
Immutable domain model
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