event_sourcing 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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