Skip to content

Instantly share code, notes, and snippets.

@erikrozendaal
Created December 9, 2011 21:25
Show Gist options
  • Save erikrozendaal/1453364 to your computer and use it in GitHub Desktop.
Save erikrozendaal/1453364 to your computer and use it in GitHub Desktop.
Simple Ruby DSL for CQRS+ES aggregates
# CQRS+ES Domain DSL
class Symbol
def snake_case
to_s.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
gsub(/([a-z\d])([A-Z])/, '\1_\2').
tr("-", "_").
downcase.to_sym
end
def camel_case
to_s.split('_').map(&:capitalize).join.to_sym
end
end
module DomainSupport
module Events
class Event
attr_reader :aggregate_id
def self.simple_name
name.split("::").last.to_sym
end
def initialize(aggregate_id, params={})
@aggregate_id = aggregate_id
params.each do |name, value|
instance_variable_set("@#{name}", value)
end
end
# Add ==, hash, type, payload, etc.
end
def self.included(target)
raise "#{self} can only be included into a module" unless target.is_a?(Module) && !target.is_a?(Class)
target.instance_eval do
extend ModuleMethods
@events = []
def self.included(aggregate)
raise "#{self} can only be included into an AggregateRoot" unless aggregate.ancestors.include?(AggregateRoot)
end
end
end
module ModuleMethods
attr_reader :events
def define_event(name, *fields)
result = Class.new(Event) do
fields.each do |field|
attr_reader field
end
end
const_set(name, result)
@events << result
define_method(name.snake_case) do |*args|
puts "Generating #{result} with #{args.inspect}"
record result.new(@id, *args)
end
private(name.snake_case)
yield result if block_given?
result
end
end
end
class AggregateRoot
attr_reader :id, :uncommitted_events
def initialize(id)
@id = id
@uncommitted_events = []
end
def self.load_from_history(history)
raise "empty history" if history.empty?
result = allocate()
result.instance_eval do
@id = history.first.aggregate_id
@uncommitted_events = []
history.each do |event|
apply(event)
end
end
result
end
def clear_uncommitted_events
@uncommitted_events = []
nil
end
private
def record(event)
apply(event)
@uncommitted_events << event
nil
end
def apply(event)
puts "Applying #{event.inspect} to #{inspect}"
simple_name = event.class.simple_name.snake_case
send("on_#{simple_name}", event)
nil
end
end
end
module OrganizationEvents
include DomainSupport::Events
define_event :Created, :name
end
module InvoiceEvents
include DomainSupport::Events
define_event :Created
define_event :LineItemAdded, :line_item_id, :description, :amount
define_event :Sent, :sent_date, :due_date do |event|
puts "#{event} defined!"
end
end
class Invoice < DomainSupport::AggregateRoot
include InvoiceEvents
def initialize(id)
super(id)
created
end
def add_line_item(description, amount)
raise "Cannot add items to sent invoices" if @sent
line_item_added line_item_id: @next_line_item_id, description: description, amount: amount
end
def do_send
sent sent_date: "now"
end
private
def on_created(event)
@next_line_item_id = 0
@sent = false
end
def on_line_item_added(event)
@next_line_item_id = event.line_item_id + 1
end
def on_sent(event)
@sent = true
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment