event_sourcing 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +7 -0
  5. data/Guardfile +12 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +29 -0
  8. data/Rakefile +13 -0
  9. data/event_sourcing.gemspec +28 -0
  10. data/features/steps/whole_stack.rb +29 -0
  11. data/features/support/env.rb +5 -0
  12. data/features/support/logger.log +1 -0
  13. data/features/support/sample_app.rb +40 -0
  14. data/features/support/spinach.log +0 -0
  15. data/features/whole_stack.feature +6 -0
  16. data/lib/event_sourcing.rb +3 -0
  17. data/lib/event_sourcing/aggregate.rb +30 -0
  18. data/lib/event_sourcing/aggregate/actor.rb +29 -0
  19. data/lib/event_sourcing/aggregate/manager.rb +33 -0
  20. data/lib/event_sourcing/aggregate/manager/cache.rb +17 -0
  21. data/lib/event_sourcing/aggregate/manager/instance_of.rb +11 -0
  22. data/lib/event_sourcing/aggregate/manager/reference.rb +14 -0
  23. data/lib/event_sourcing/aggregate/message.rb +7 -0
  24. data/lib/event_sourcing/aggregate/wrapper.rb +18 -0
  25. data/lib/event_sourcing/application.rb +37 -0
  26. data/lib/event_sourcing/application/actor.rb +40 -0
  27. data/lib/event_sourcing/application/actor/reference.rb +23 -0
  28. data/lib/event_sourcing/command.rb +30 -0
  29. data/lib/event_sourcing/command/bus.rb +16 -0
  30. data/lib/event_sourcing/event.rb +29 -0
  31. data/lib/event_sourcing/event/bus.rb +29 -0
  32. data/lib/event_sourcing/event/bus/reference.rb +28 -0
  33. data/lib/event_sourcing/event/publisher.rb +47 -0
  34. data/lib/event_sourcing/event/publisher/reference.rb +11 -0
  35. data/lib/event_sourcing/event/store.rb +8 -0
  36. data/lib/event_sourcing/event/store/memory.rb +43 -0
  37. data/lib/event_sourcing/event/stream.rb +23 -0
  38. data/lib/event_sourcing/event/subscriber.rb +16 -0
  39. data/lib/event_sourcing/version.rb +3 -0
  40. data/spec/concurrent_logging.rb +6 -0
  41. data/spec/spec_helper.rb +8 -0
  42. data/spec/support/actor_helpers.rb +11 -0
  43. data/spec/support/shared_examples/a_store_implementation.rb +70 -0
  44. data/spec/unit/aggregate/actor_spec.rb +59 -0
  45. data/spec/unit/aggregate/manager/cache_spec.rb +26 -0
  46. data/spec/unit/aggregate/manager/reference_spec.rb +14 -0
  47. data/spec/unit/aggregate/manager_spec.rb +32 -0
  48. data/spec/unit/aggregate/wrapper_spec.rb +22 -0
  49. data/spec/unit/aggregate_spec.rb +75 -0
  50. data/spec/unit/application/actor/reference_spec.rb +25 -0
  51. data/spec/unit/application/actor_spec.rb +36 -0
  52. data/spec/unit/application_spec.rb +41 -0
  53. data/spec/unit/command/bus_spec.rb +15 -0
  54. data/spec/unit/command_spec.rb +38 -0
  55. data/spec/unit/event/bus/reference_spec.rb +48 -0
  56. data/spec/unit/event/bus_spec.rb +41 -0
  57. data/spec/unit/event/publisher_spec.rb +28 -0
  58. data/spec/unit/event/store/memory_spec.rb +6 -0
  59. data/spec/unit/event/stream_spec.rb +30 -0
  60. data/spec/unit/event/subscriber_spec.rb +27 -0
  61. data/spec/unit/event_spec.rb +27 -0
  62. data/spec/unit_helper.rb +14 -0
  63. metadata +233 -0
@@ -0,0 +1,59 @@
1
+ require "unit_helper"
2
+ require "event_sourcing/aggregate/actor"
3
+
4
+ describe EventSourcing::Aggregate::Actor do
5
+ context "for()" do
6
+ let(:aggregate) { double("Aggregate class", new: true) }
7
+
8
+ subject do
9
+ EventSourcing::Aggregate::Actor.for(aggregate)
10
+ end
11
+
12
+ it "returns an actor" do
13
+ expect(subject < Concurrent::Actor::RestartingContext).to be_truthy
14
+ end
15
+ end
16
+
17
+ context "instance" do
18
+ subject { actor }
19
+ let(:aggregate_class) { double("Aggregate class", new: aggregate_instance, instance_methods: [:publish])}
20
+ let(:aggregate_instance) { double("Aggregate instance", publish: :published)}
21
+ let(:event_stream) { instance_double("EventSourcing::Event::Stream") }
22
+ let(:event_bus) { instance_double("EventSourcing::Event::Bus::Reference") }
23
+ let(:actor) { EventSourcing::Aggregate::Actor.for(aggregate_class).new(event_bus, event_stream) }
24
+
25
+ before do
26
+ allow(aggregate_instance).to receive(:_apply).with(:published)
27
+ allow(event_bus).to receive(:publish)
28
+ allow(event_stream).to receive(:append)
29
+ end
30
+
31
+ context "when receiving supported messages" do
32
+ after do
33
+ subject.on_message([:publish, :some_arg])
34
+ end
35
+
36
+ it "stores the events using the stream" do
37
+ expect(event_stream).to receive(:append).with(:published)
38
+ end
39
+
40
+ it "delegates on aggregate and publishes events" do
41
+ expect(event_bus).to receive(:publish).with(:published)
42
+ end
43
+
44
+ it "gets events applied" do
45
+ expect(aggregate_instance).to receive(:_apply).with(:published)
46
+ end
47
+ end
48
+
49
+ context "when receiving unsupported messages" do
50
+ after do
51
+ subject.on_message([:fake])
52
+ end
53
+
54
+ it "does nothing" do
55
+ expect(event_bus).not_to receive(:publish)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,26 @@
1
+ require "unit_helper"
2
+ require "event_sourcing/aggregate/manager/cache"
3
+
4
+ describe EventSourcing::Aggregate::Manager::Cache do
5
+ subject { EventSourcing::Aggregate::Manager::Cache.new(event_bus) }
6
+
7
+ let(:aggregate) { Class.new }
8
+ let(:actor) { double("Actor")}
9
+ let(:instance) { double("Aggregate instance") }
10
+ let(:event_bus) { instance_double("EventSourcing::Event::Bus::Reference") }
11
+ let(:event_stream) { double("Event stream") }
12
+
13
+ before do
14
+ allow(event_bus).to receive(:get_stream).with("some-id").and_return(event_stream)
15
+ aggregate.const_set("Actor", actor)
16
+ allow(actor).to receive(:spawn!).once.with(name: "some-id", args: [event_bus, event_stream], supervise: true).and_return(instance)
17
+ end
18
+
19
+ it "returns instances" do
20
+ expect(subject.instance_of(aggregate, "some-id")).to eq(instance)
21
+ end
22
+
23
+ it "memoizes instances" do
24
+ expect(subject.instance_of(aggregate, "some-id")).to eq(subject.instance_of(aggregate, "some-id"))
25
+ end
26
+ end
@@ -0,0 +1,14 @@
1
+ require "unit_helper"
2
+ require "event_sourcing/aggregate/manager/reference"
3
+
4
+ describe EventSourcing::Aggregate::Manager::Reference do
5
+ context "instance_of" do
6
+ let(:aggregate) { double("Aggregate") }
7
+ let(:wrapper) { EventSourcing::Aggregate::Wrapper.new(subject, aggregate, "some-id") }
8
+ subject { EventSourcing::Aggregate::Manager::Reference.new(instance_double("Concurrent::Actor::Core", is_a?: true)) }
9
+
10
+ it "returns a wrapper" do
11
+ expect(subject.instance_of(aggregate, "some-id")).to eq(wrapper)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ require "unit_helper"
2
+ require "event_sourcing/aggregate/manager"
3
+
4
+ describe EventSourcing::Aggregate::Manager do
5
+ it "is a restarting actor" do
6
+ expect(EventSourcing::Aggregate::Manager.ancestors).to include(Concurrent::Actor::RestartingContext)
7
+ end
8
+
9
+ context "on message of kind Aggregate::Message" do
10
+ let(:aggregate) { double("Aggregate") }
11
+ let(:instance) { double("Aggregate instance") }
12
+ let(:cache_class) { class_double("EventSourcing::Aggregate::Manager::Cache").as_stubbed_const }
13
+ let(:cache_instance) { instance_double("EventSourcing::Aggregate::Manager::Cache") }
14
+ let(:manager) { EventSourcing::Aggregate::Manager.new(event_bus) }
15
+ let(:event_bus) { double("EventSourcing::Event::Bus::Reference") }
16
+ let(:wrapped_message) { EventSourcing::Aggregate::Message.new(aggregate, "some-id", actual_message) }
17
+ let(:actual_message) { :publish }
18
+
19
+ before do
20
+ allow(cache_class).to receive(:new).with(event_bus).once.and_return(cache_instance)
21
+ allow(cache_instance).to receive(:instance_of).with(aggregate, "some-id").and_return(instance)
22
+ end
23
+
24
+ after do
25
+ manager.on_message(wrapped_message)
26
+ end
27
+
28
+ it "redirects the message to the aggregate actor" do
29
+ expect(instance).to receive(:tell).with(actual_message)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ require "unit_helper"
2
+ require "event_sourcing/aggregate/wrapper"
3
+
4
+ describe EventSourcing::Aggregate::Wrapper do
5
+ let(:manager) { instance_double("EventSourcing::Aggregate::Manager::Reference") }
6
+ let(:wrapper) { EventSourcing::Aggregate::Wrapper.new(manager, aggregate, "some-id") }
7
+ let(:aggregate) do
8
+ Class.new do
9
+ def publish
10
+ :stuff
11
+ end
12
+ end
13
+ end
14
+
15
+ context "when is sent a message using tell" do
16
+ after { wrapper.publish }
17
+
18
+ it "routes it through the manager" do
19
+ expect(manager).to receive(:tell).with(EventSourcing::Aggregate::Message.new(aggregate, "some-id", [:publish]))
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,75 @@
1
+ require "unit_helper"
2
+ require "event_sourcing/aggregate"
3
+
4
+ describe EventSourcing::Aggregate do
5
+ context "once included" do
6
+
7
+ let(:actor_class) { class_double("EventSourcing::Aggregate::Actor") }
8
+ let(:aggregate_class) { Class.new }
9
+ let(:aggregate_actor) { double("Aggregate actor") }
10
+
11
+ before do
12
+ stub_const("EventSourcing::Aggregate::Actor", actor_class)
13
+ allow(actor_class).to receive(:for).with(aggregate_class).and_return(aggregate_actor)
14
+ end
15
+
16
+ subject do
17
+ aggregate_class.class_eval do
18
+ include EventSourcing::Aggregate
19
+ end
20
+ end
21
+
22
+ it "defines an actor" do
23
+ expect(subject::Actor).to eq(aggregate_actor)
24
+ end
25
+ end
26
+
27
+ context "when it has defined handlers" do
28
+
29
+
30
+ let(:event) { instance_double("EventSourcing::Event", to_s: "SomethingHappened") }
31
+
32
+ let(:aggregate_class) do
33
+ Class.new do
34
+ include EventSourcing::Aggregate
35
+
36
+ def do_something
37
+ :did_something unless @something_happened
38
+ end
39
+
40
+ handle "SomethingHappened" do |e|
41
+ @something_happened = true
42
+ end
43
+ end
44
+ end
45
+
46
+ let(:clean_aggregate) { aggregate_class.new() }
47
+ let(:aggregate_with_events) { aggregate_class.new([event]) }
48
+
49
+ it "can be initialized without an event stream" do
50
+ expect(clean_aggregate.do_something).to eq(:did_something)
51
+ end
52
+
53
+ it "can be initialized with an event stream" do
54
+ expect(aggregate_with_events.do_something).to be_nil
55
+ end
56
+
57
+ it "can have events applied to it" do
58
+ clean_aggregate._apply(event)
59
+ expect(clean_aggregate.do_something).to be_nil
60
+ end
61
+
62
+
63
+ context "with unsupported events" do
64
+ let(:unsupported_event) { instance_double("EventSourcing::Event", to_s: "WrongEvent") }
65
+
66
+ it "fails when initialized with unsupported events" do
67
+ expect { aggregate_class.new([unsupported_event])}.to raise_error(RuntimeError, "unsupported event WrongEvent")
68
+ end
69
+
70
+ it "fails when unsupported events are applied" do
71
+ expect { aggregate_class.new._apply(unsupported_event) }.to raise_error(RuntimeError, "unsupported event WrongEvent")
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,25 @@
1
+ require "unit_helper"
2
+ require "event_sourcing/application/actor/reference"
3
+
4
+ describe EventSourcing::Application::Actor::Reference do
5
+ subject { EventSourcing::Application::Actor::Reference.new(instance_double("Concurrent::Actor::Core", is_a?: true)) }
6
+ context "terminate!" do
7
+ after { subject.terminate! }
8
+
9
+ it "tells the actor to terminate" do
10
+ expect(subject).to receive(:tell).with(:terminate!)
11
+ end
12
+ end
13
+
14
+ context "execute_command" do
15
+ let(:command) { double("Command") }
16
+ let(:command_bus) { double("Command bus") }
17
+
18
+ after { subject.execute_command(command) }
19
+ before { allow(subject).to receive(:ask!).with(:get_command_bus).once.and_return(command_bus) }
20
+
21
+ it "delegates on the command bus" do
22
+ expect(command_bus).to receive(:tell).with(command)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ require "unit_helper"
2
+ require "event_sourcing/application/actor"
3
+
4
+ describe EventSourcing::Application::Actor do
5
+ subject { EventSourcing::Application::Actor.new(event_store) }
6
+
7
+ let(:event_store) { instance_double("EventSourcing::Event::Store::Memory") }
8
+ let(:command_bus) { class_double("EventSourcing::Command::Bus").as_stubbed_const(transfer_nested_constants: true) }
9
+ let(:command_bus_ref) { double("EventSourcing::Command::Bus::Reference") }
10
+ let(:event_bus) { class_double("EventSourcing::Event::Bus").as_stubbed_const(transfer_nested_constants: true) }
11
+ let(:event_bus_ref) { double("EventSourcing::Event::Bus::Reference") }
12
+ let(:aggregate_manager) { class_double("EventSourcing::Aggregate::Manager").as_stubbed_const(transfer_nested_constants: true) }
13
+ let(:aggregate_manager_ref) { instance_double("EventSourcing::Aggregate::Manager::Reference") }
14
+
15
+ before do
16
+ allow(command_bus).to receive(:spawn!).with(name: :command_bus, supervise: true, args: [aggregate_manager_ref]).and_return(command_bus_ref)
17
+ allow(event_bus).to receive(:spawn!).with(name: :event_bus, supervise: true, args: [event_store]).and_return(event_bus_ref)
18
+ allow(aggregate_manager).to receive(:spawn!).with(name: :aggregate_manager, supervise: true, args: [event_bus_ref]).and_return(aggregate_manager_ref)
19
+ end
20
+
21
+ it "uses Actor::Reference internally" do
22
+ expect(EventSourcing::Application::Actor.new(event_store).default_reference_class).to eq(EventSourcing::Application::Actor::Reference)
23
+ end
24
+
25
+ it "supervises and returns a command bus" do
26
+ expect(subject.on_message(:get_command_bus)).to eq(command_bus_ref)
27
+ end
28
+
29
+ it "supervises and returns an event bus" do
30
+ expect(subject.on_message(:get_event_bus)).to eq(event_bus_ref)
31
+ end
32
+
33
+ it "supervises and returns an event bus" do
34
+ expect(subject.on_message(:get_aggregate_manager)).to eq(aggregate_manager_ref)
35
+ end
36
+ end
@@ -0,0 +1,41 @@
1
+ require "unit_helper"
2
+ require "event_sourcing/application"
3
+
4
+ describe EventSourcing::Application do
5
+ let(:sample_app) { EventSourcing::Application.new(:sample_app) }
6
+
7
+ it "can be inspected" do
8
+ expect(sample_app.inspect).to eq("EventSourcing::Application(sample_app)")
9
+ end
10
+
11
+ context "while running" do
12
+ subject { running_app }
13
+
14
+ let(:running_app) { sample_app.run!(event_store: event_store) }
15
+ let(:event_store) { instance_double("EventSourcing::Event::Store::Memory") }
16
+ #FIXME Remove to_str. It's only needed for specs to pass under jruby
17
+ let(:actor_class) { class_double("EventSourcing::Application::Actor", to_str: "EventSourcing::Application::Actor").as_stubbed_const(transfer_nested_constants: true) }
18
+ let(:actor_reference) { instance_double("EventSourcing::Application::Actor::Reference") }
19
+
20
+ before do
21
+ allow(actor_class).to receive(:spawn!).with(name: :sample_app, args: [event_store]).and_return(actor_reference)
22
+ end
23
+
24
+ context "shutdown" do
25
+ after { subject.shutdown }
26
+
27
+ it "terminates the application actor" do
28
+ expect(actor_reference).to receive(:terminate!)
29
+ end
30
+ end
31
+
32
+ context "execute command" do
33
+ let(:command) { double("Command") }
34
+ after { subject.execute_command(command)}
35
+
36
+ it "delegates on the application actor" do
37
+ expect(actor_reference).to receive(:execute_command).with(command)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,15 @@
1
+ require "unit_helper"
2
+ require "event_sourcing/command/bus"
3
+
4
+ describe EventSourcing::Command::Bus do
5
+ let(:aggregate_manager) { instance_double("EventSourcing::Aggregate::Manager::Reference") }
6
+ let(:command) { double("EventSourcing::Command") }
7
+
8
+ subject { EventSourcing::Command::Bus.new(aggregate_manager) }
9
+
10
+ after { subject.on_message(command) }
11
+
12
+ it "executes commands" do
13
+ expect(command).to receive(:execute).with(aggregate_manager)
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ require "unit_helper"
2
+ require "event_sourcing/command"
3
+
4
+ describe EventSourcing::Command do
5
+ it "expects a block" do
6
+ expect { EventSourcing::Command.define }.to raise_error("Commands require an execution block")
7
+ end
8
+
9
+ it "can't be directly instantiated" do
10
+ expect { EventSourcing::Command.new }.to raise_error(NoMethodError)
11
+ end
12
+
13
+ context "instance" do
14
+ subject { EventSourcing::Command.define {} }
15
+
16
+ it "is a EventSourcing::Command" do
17
+ expect(subject.new).to be_a(EventSourcing::Command)
18
+ end
19
+ end
20
+
21
+ context "with required parameters" do
22
+ subject { EventSourcing::Command.define(:title) {} }
23
+
24
+ it "can't be instantiated if those are not provided" do
25
+ expect { subject.new }.to raise_error(ArgumentError, "missing keyword: title")
26
+ end
27
+ end
28
+
29
+ context "execute" do
30
+ let(:dependency) { double("Dependency", value: 42)}
31
+ let(:command) { EventSourcing::Command.define(:prop) { |dep| "returning #{dep.value} and #{prop}"} }
32
+ subject { command.new(prop: "own_stuff") }
33
+
34
+ it "runs the passed block under the current command context" do
35
+ expect(subject.execute(dependency)).to eq("returning 42 and own_stuff")
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,48 @@
1
+ require "unit_helper"
2
+ require "event_sourcing/event/bus/reference"
3
+
4
+ describe EventSourcing::Event::Bus::Reference do
5
+ let(:bus_ref) { actor_reference(EventSourcing::Event::Bus::Reference) }
6
+ let(:stream) { instance_double("EventSourcing::Event::Stream") }
7
+
8
+ context "get_stream" do
9
+ let(:store) { instance_double("EventSourcing::Event::Store::Memory") }
10
+
11
+ before do
12
+ allow(bus_ref).to receive(:ask!).once.with(:get_event_store).and_return(store)
13
+ allow(store).to receive(:get_stream).with("some-id").and_return(stream)
14
+ end
15
+
16
+ it "returns a stream from the event store" do
17
+ expect(bus_ref.get_stream("some-id")).to eq(stream)
18
+ end
19
+
20
+ it "memoizes the store" do
21
+ expect(bus_ref.get_stream("some-id")).to eq(bus_ref.get_stream("some-id"))
22
+ end
23
+ end
24
+
25
+ context "publish" do
26
+ after { bus_ref.publish(event) }
27
+
28
+ before do
29
+ allow(bus_ref).to receive(:ask!).with(:get_event_publisher).and_return(publisher)
30
+ end
31
+
32
+ let(:event) { instance_double("EventSourcing::Event") }
33
+ let(:publisher) { double("Publisher ref") }
34
+
35
+ it "delegates on the memoized bus publisher" do
36
+ expect(publisher).to receive(:publish).with(event)
37
+ end
38
+
39
+ context "multiple times" do
40
+ after { bus_ref.publish(:event) }
41
+ before { allow(publisher).to receive(:publish) }
42
+
43
+ it "memoizes the publisher reference" do
44
+ expect(bus_ref).to receive(:ask!).once.with(:get_event_publisher).and_return(publisher)
45
+ end
46
+ end
47
+ end
48
+ end