event_sourcery 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +37 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG.md +82 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +399 -0
  10. data/Rakefile +6 -0
  11. data/bin/console +6 -0
  12. data/bin/setup +15 -0
  13. data/event_sourcery.gemspec +28 -0
  14. data/lib/event_sourcery.rb +49 -0
  15. data/lib/event_sourcery/aggregate_root.rb +68 -0
  16. data/lib/event_sourcery/config.rb +43 -0
  17. data/lib/event_sourcery/errors.rb +19 -0
  18. data/lib/event_sourcery/event.rb +49 -0
  19. data/lib/event_sourcery/event_body_serializer.rb +42 -0
  20. data/lib/event_sourcery/event_processing/error_handlers/constant_retry.rb +23 -0
  21. data/lib/event_sourcery/event_processing/error_handlers/error_handler.rb +20 -0
  22. data/lib/event_sourcery/event_processing/error_handlers/exponential_backoff_retry.rb +40 -0
  23. data/lib/event_sourcery/event_processing/error_handlers/no_retry.rb +19 -0
  24. data/lib/event_sourcery/event_processing/esp_process.rb +41 -0
  25. data/lib/event_sourcery/event_processing/esp_runner.rb +105 -0
  26. data/lib/event_sourcery/event_processing/event_stream_processor.rb +125 -0
  27. data/lib/event_sourcery/event_processing/event_stream_processor_registry.rb +29 -0
  28. data/lib/event_sourcery/event_store/each_by_range.rb +25 -0
  29. data/lib/event_sourcery/event_store/event_builder.rb +19 -0
  30. data/lib/event_sourcery/event_store/event_sink.rb +18 -0
  31. data/lib/event_sourcery/event_store/event_source.rb +21 -0
  32. data/lib/event_sourcery/event_store/event_type_serializers/class_name.rb +19 -0
  33. data/lib/event_sourcery/event_store/event_type_serializers/legacy.rb +17 -0
  34. data/lib/event_sourcery/event_store/event_type_serializers/underscored.rb +68 -0
  35. data/lib/event_sourcery/event_store/poll_waiter.rb +18 -0
  36. data/lib/event_sourcery/event_store/signal_handling_subscription_master.rb +22 -0
  37. data/lib/event_sourcery/event_store/subscription.rb +43 -0
  38. data/lib/event_sourcery/memory/event_store.rb +76 -0
  39. data/lib/event_sourcery/memory/tracker.rb +27 -0
  40. data/lib/event_sourcery/repository.rb +31 -0
  41. data/lib/event_sourcery/rspec/event_store_shared_examples.rb +352 -0
  42. data/lib/event_sourcery/version.rb +3 -0
  43. metadata +158 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5a53861c6b78ba25db797887cbb523bec700832a
4
+ data.tar.gz: 2cc44cb2db162bf4b1f35e356e24eab1c213534d
5
+ SHA512:
6
+ metadata.gz: 77e1852829109a6fcab8e4c2efb930de89b88dfa45eccbecf761484c7a19f6ab8825fd9179c9112002e6c82ab281033106dc0da60fad2f8be76dd97e0c42d782
7
+ data.tar.gz: e0c6879eb49868144c628b6649c37d6577637f3514ef2653606a47d342cd820c9ed38f44588f77acd145f7b528a98b314a3c9189e27cd8f2971391c9ffe2978c
data/.gitignore ADDED
@@ -0,0 +1,37 @@
1
+ # Created by .ignore support plugin (hsz.mobi)
2
+ ### Ruby template
3
+ *.gem
4
+ *.rbc
5
+ /.config
6
+ /coverage/
7
+ /InstalledFiles
8
+ /pkg/
9
+ /spec/reports/
10
+ /spec/examples.txt
11
+ /test/tmp/
12
+ /test/version_tmp/
13
+ /tmp/
14
+
15
+ # Used by dotenv library to load environment variables.
16
+ # .env
17
+
18
+ ## Documentation cache and generated files:
19
+ /.yardoc/
20
+ /_yardoc/
21
+ /doc/
22
+ /rdoc/
23
+
24
+ ## Environment normalization:
25
+ /.bundle/
26
+ /vendor/bundle
27
+ /lib/bundler/man/
28
+
29
+ # for a library or gem, you might want to ignore these files since the code is
30
+ # intended to run in multiple environments; otherwise, check them in:
31
+ Gemfile.lock
32
+ .ruby-version
33
+ .ruby-gemset
34
+
35
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
36
+ .rvmrc
37
+
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --format documentation
3
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2
5
+ - 2.3
6
+ - 2.4
7
+ before_install:
8
+ - gem install bundler
data/CHANGELOG.md ADDED
@@ -0,0 +1,82 @@
1
+ # Change Log
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](http://keepachangelog.com/)
5
+ and this project adheres to [Semantic Versioning](http://semver.org/).
6
+
7
+ ## [Unreleased]
8
+ ### Added
9
+ - The core Event class accepts `causation_id` to allow event stores to
10
+ add support for tracking causation ids with events.
11
+ - The core Memory event store saves the `causation_id` and `correlation_id`.
12
+
13
+ ### Changed
14
+ - The event store shared RSpec examples specify event stores should save
15
+ the `causation_id` and `correlation_id`.
16
+
17
+ ### Removed
18
+ - The `processing_event` method from the memory tracker. It was intended to
19
+ be a mechanism to wrap processing and tracker updates which appears to be
20
+ universally unused at this point.
21
+
22
+ ## [0.12.0] - 2017-6-1
23
+ ### Removed
24
+ - Removed usage `#shutdown!` as it should be a private method within custom PollWaiters.
25
+ An example of how event_sourcery-postgres has implemented `#shutdown!` can be
26
+ found [here](https://github.com/envato/event_sourcery-postgres/pull/5)
27
+
28
+ ## [0.11.2] - 2017-5-29
29
+ ### Fixed
30
+ - Fixed: default poll waiter now implements `shutdown!`
31
+
32
+ ## [0.11.0] - 2017-5-29
33
+ ### Fixed
34
+ - Use `processor.class.name` to set ESP process name
35
+ - Convert `processor_name` symbol to string explicitly
36
+
37
+ ## [0.11.0] - 2017-5-26
38
+ ### Added
39
+ - Make Event processing error handler class Configurable
40
+ - Add exponential back off retry error handler
41
+
42
+ ## [0.10.0] - 2017-5-24
43
+ ### Added
44
+ - The core Event class accepts `correlation_id` to allow event stores to
45
+ add support for tracking correlation IDs with events.
46
+ - `Repository#save` for saving aggregate instances.
47
+ - Configuration option to define custom event body serializers.
48
+
49
+ ### Fixed
50
+ - Resolved Sequel deprecation notice when loading events from the Postgres event
51
+ store.
52
+
53
+ ### Changed
54
+ - Aggregates no longer save events directly to an event sink. They must be
55
+ passed back to the repository for saving with `repository.save(aggregate)`.
56
+ - `AggregateRoot#apply_event` signature has changed from accepting an event or
57
+ a hash to accepting an event class followed by what would normally go in the
58
+ constructor of the event.
59
+
60
+ ### Removed
61
+ - Postgres specific code has moved to the [event_sourcery-postgres](https://github.com/envato/event_sourcery-postgres) gem.
62
+ Config options for postgres have moved to `EventSourcery::Postgres.config`.
63
+
64
+ ## [0.9.0] - 2017-05-02
65
+ ### Added
66
+ - Add `table_prefix` method to `TableOwner` to declare a table name prefix for
67
+ all tables in a projector or reactor.
68
+
69
+ ### Changed
70
+ - Schema change: the `writeEvents` function has been refactored slightly.
71
+ - The `Event` class no longer uses `Virtus.value_object`.
72
+ - `AggregateRoot` and `Repository` are namespaced under `EventSourcery` instead
73
+ of `EventSourcery::Command`.
74
+ - `EventSourcery::Postgres` namespace has been extracted from
75
+ `EventSourcery::(EventStore|EventProcessing)::Postgres` in preparation for
76
+ moving all Postgres related code into a separate gem.
77
+ - An advisory lock has replaced the exclusive table lock used to synchronise
78
+ event inserts.
79
+
80
+ ### Removed
81
+ - EventSourcery no longer depends on Virtus.
82
+ - `Command` and `CommandHandler` have been removed.
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at odindutton@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ ruby '>= 2.2.0'
4
+
5
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Envato
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,399 @@
1
+ # EventSourcery
2
+
3
+ [![Build Status](https://travis-ci.org/envato/event_sourcery.svg?branch=master)](https://travis-ci.org/envato/event_sourcery)
4
+
5
+ A framework for building event sourced, CQRS applications.
6
+
7
+ **Table of Contents**
8
+
9
+ - [Development Status](#development-status)
10
+ - [Goals](#goals)
11
+ - [Getting Started Guide](#getting-started-guide)
12
+ - [Configuration](#configuration)
13
+ - [Development](#development)
14
+ - [Dependencies](#dependencies)
15
+ - [Running the Test Suite](#running-the-test-suite)
16
+ - [Release](#release)
17
+ - [Core Concepts](#core-concepts)
18
+ - [Tour of an EventSourcery Web Application](#tour-of-an-eventsourcery-web-application)
19
+ - [Events](#events)
20
+ - [The Event Store](#the-event-store)
21
+ - [Storing Events](#storing-events)
22
+ - [Reading Events](#reading-events)
23
+ - [Aggregates and Command Handling](#aggregates-and-command-handling)
24
+ - [Event Processing](#event-processing)
25
+ - [Event Stream Processors](#event-stream-processors)
26
+ - [Projectors](#projectors)
27
+ - [Reactors](#reactors)
28
+ - [Running Multiple ESPs](#running-multiple-esps)
29
+ - [Typical Flow of State in an Event Sourcery Application](#typical-flow-of-state-in-an-event-sourcery-application)
30
+ - [1. Handling a Command](#1-handling-a-command)
31
+ - [2. Updating a Projection](#2-updating-a-projection)
32
+ - [3. Handling a Query](#3-handling-a-query)
33
+
34
+ ## Development Status
35
+
36
+ EventSourcery is currently being used in production by multiple apps but we
37
+ haven't finalized the API yet and things are still moving rapidly. Until we
38
+ release a 1.0 things may change without first being deprecated.
39
+
40
+ ## Goals
41
+
42
+ The goal of EventSourcery is to make it easier to build event sourced, CQRS applications.
43
+
44
+ The hope is that by using EventSourcery you can focus on modeling your domain with aggregates, commands, and events; and not worry about stitching together application plumbing.
45
+
46
+ ## Getting Started Guide
47
+
48
+ EventSourcery currently supports a Postgres-based event store via the [event_sourcery-postgres](https://github.com/envato/event_sourcery-postgres) gem.
49
+
50
+ **TODO**
51
+
52
+ ## Configuration
53
+
54
+ There are several ways to configure Event Sourcery to your liking. The following presents some examples:
55
+
56
+ ```ruby
57
+ EventSourcery.configure do |config|
58
+ # Add custom reporting of errors occurring during event processing.
59
+ # One might set up Rollbar here.
60
+ config.on_event_processor_error = proc { |exception, processor_name| … }
61
+
62
+ # Enable Event Sourcery logging.
63
+ config.logger = Logger.new('logs/my_event_sourcery_app.log')
64
+
65
+ # Customize how event body attributes are serialized
66
+ config.event_body_serializer
67
+ .add(BigDecimal) { |decimal| decimal.to_s('F') }
68
+
69
+ # Config how your want to handle event processing errors
70
+ config.error_handler_class = EventSourcery::EventProcessing::ErrorHandlers::ExponentialBackoffRetry
71
+ end
72
+ ```
73
+
74
+ ## Development
75
+
76
+ ### Dependencies
77
+
78
+ - Ruby
79
+
80
+ ### Running the Test Suite
81
+
82
+ Run the `setup` script, inside the project directory to install the gem dependencies and create the test database (if it is not already created).
83
+ ```bash
84
+ ./bin/setup
85
+ ```
86
+
87
+ Then you can run the test suite with rspec:
88
+ ```bash
89
+ bundle exec rspec
90
+ ```
91
+
92
+ ### Release
93
+
94
+ To release a new version:
95
+
96
+ 1. Update the version number in `lib/event_sourcery/version.rb`
97
+ 2. Get this change onto master via the normal PR process
98
+ 3. Run `bundle exec rake release`, this will create a git tag for the
99
+ version, push tags up to GitHub, and package the code in a `.gem` file.
100
+
101
+ ## Core Concepts
102
+
103
+ Not sure what Event Sourcing (ES), Command Query Responsibility Segregation (CQRS), or even Domain-Driven Design (DDD) are? Here are a few links to get you started:
104
+
105
+ - [CQRS and Event Sourcing Talk](https://www.youtube.com/watch?v=JHGkaShoyNs) - by Greg Young at Code on the Beach 2014
106
+ - [DDD/CQRS Google Group](https://groups.google.com/forum/#!forum/dddcqrs) - from people new to the concepts to old hands
107
+ - [DDD Weekly Newsletter](https://buildplease.com/pages/dddweekly/) - a weekly digest of what's happening in the community
108
+ - [Domain-Driven Design](https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215) - the definitive guide
109
+ - [Greg Young's Blog](https://goodenoughsoftware.net) - a (the?) lead proponent of all things Event Sourcing
110
+
111
+ ### Tour of an EventSourcery Web Application
112
+
113
+ Below is a high level view of a CQRS, event-sourced web application built using EventSourcery. The components marked with `*` can be created using building blocks provided by EventSourcery. Keep on reading and we'll describe each of the concepts illustrated.
114
+
115
+ ```
116
+ ┌─────────────┐ ┌─────────────┐
117
+ │ │ │ │
118
+ │ Client │ │ Client │
119
+ │ │ │ │
120
+ └─────────────┘ └─────────────┘
121
+ │ │
122
+ Issue Command Issue Query
123
+ │ │
124
+ ┌───────┴──────────────────────────────────────┴─────────┐
125
+ │ Web Layer │
126
+ └───────┬──────────────────────────────────────┬─────────┘
127
+ │ │
128
+ ▼ ▼
129
+ ┌─────────────┐ ┌─────────────┐
130
+ │ Command │ │Query Handler│
131
+ │ Handler │ │ │
132
+ └─────────────┘ └─────────────┘
133
+ │ │
134
+ ▼ ┌───────▼─────┐
135
+ ┌─────────────┐ │┌────────────┴┐
136
+ │ * Aggregate │ ││* Projection │
137
+ │ │ └┤ │
138
+ └─────────────┘ └─────────────┘
139
+ │ ▲
140
+ │ │
141
+ │ Update Projection
142
+ │ │
143
+ Emit Event ┌─────────────┐
144
+ │ │┌────────────┴┐
145
+ │ ││ * Projector │
146
+ ▼ └┤ │
147
+ ┌─────────────┐ └─────────────┘
148
+ │* Event Store│ Process ▲
149
+ ┌─▶│ │────────Event───────────────────┘
150
+ │ └─────────────┘
151
+ │ │
152
+ │ Process ┌─────────────┐
153
+ │ Event │┌────────────┴┐ ┌ ─ ─ ─ ─ ─ ─ ┐
154
+ │ └───────▶││ * Reactor │ External
155
+ │ └┤ │───Trigger ───▶│ System │
156
+ │ └─────────────┘ Behaviour ─ ─ ─ ─ ─ ─ ─
157
+ │ │
158
+ │ │
159
+ └────────Emit Event────────┘
160
+
161
+ ```
162
+
163
+ ### Events
164
+
165
+ Events are value objects that record something of meaning in the domain. Think of a sequence of events as a time series of immutable domain facts.
166
+
167
+ Events are targeted at an aggregate via an `aggregate_id` and have the following attributes.
168
+
169
+ ```ruby
170
+ module EventSourcery
171
+ class Event
172
+ attr_reader \
173
+ :id, # Sequence number
174
+ :uuid, # Unique ID
175
+ :aggregate_id, # ID of aggregate the event pertains to
176
+ :type, # type of the event
177
+ :body, # the payload (a hash)
178
+ :version, # Version of the aggregate
179
+ :created_at, # Created at date
180
+ :correlation_id # Correlation ID for tracing purposes
181
+
182
+ # ...
183
+ end
184
+ end
185
+ ```
186
+
187
+ You can define events in your domain as follows.
188
+
189
+ ```ruby
190
+ TodoAdded = Class.new(EventSourcery::Event)
191
+
192
+ # An example instance.
193
+ # #<TodoAdded:0x007fb6f88f04b0
194
+ # @id=24,
195
+ # @uuid="75dcc7eb-33c0-4f1c-ac23-31bf32fc5edc",
196
+ # @aggregate_id="fca315ff-d45d-46c5-a230-67c5bec0b06d",
197
+ # @type="todo_added",
198
+ # @body={"title"=>"My task"},
199
+ # @version=1,
200
+ # @created_at=2017-06-14 11:50:32 UTC,
201
+ # @correlation_id="b4d1e31d-9d1b-4ea1-a685-57936ce65a80">
202
+ ```
203
+
204
+ ### The Event Store
205
+
206
+ The event store is a persistent store of events.
207
+
208
+ EventSourcery currently supports a Postgres-based event store via the [event_sourcery-postgres gem](https://github.com/envato/event_sourcery-postgres).
209
+
210
+ For more information about the `EventStore` API refer to [the postgres event store](https://github.com/envato/event_sourcery-postgres/blob/master/lib/event_sourcery/postgres/event_store.rb) or the [in memory event store in this repo](lib/event_sourcery/event_store/memory.rb)
211
+
212
+ #### Storing Events
213
+
214
+ Naturally, it provides the ability to store events. The event store is append-only and immutable. The events in the store form a time-ordered sequence which can be viewed as a stream of events.
215
+
216
+ `EventStore` clients can optionally provide an expected version of event when saving to the store. This provides a mechanism for `EventStore` clients to effectively serialise the processing they perform against an instance of an aggregate.
217
+
218
+ When used in this fashion the event store can be thought of as an event sink.
219
+
220
+ #### Reading Events
221
+
222
+ The `EventStore` also allows clients to read events. Clients can poll the store for events of specific types after a specific event ID. They can also subscribe to the event store to be notified when new events are added to the event store that match the above criteria.
223
+
224
+ When used in this fashion the event store can be thought of as an event source.
225
+
226
+ ### Aggregates and Command Handling
227
+
228
+ > An aggregate is a cluster of domain objects that can be treated as a single unit. Every transaction is scoped to a single aggregate. An aggregate will have one of its component objects be the aggregate root. Any references from outside the aggregate should only go to the aggregate root. The root can thus ensure the integrity of the aggregate as a whole.
229
+ >
230
+ > <cite>— [DDD Aggregate](http://martinfowler.com/bliki/DDD_Aggregate.html)</cite>
231
+
232
+ Clients execute domain transactions against the system by issuing commands against aggregate roots. The result of these commands is new events being saved to the event store.
233
+
234
+ A typical EventSourcery application will have one or more aggregate roots with multiple commands.
235
+
236
+ ### Event Processing
237
+
238
+ A central part of EventSourcery is the processing of events in the store. Event Sourcery provides the Event Stream Processor abstraction to support this.
239
+
240
+ ```
241
+ ┌─────────────┐ Subscribe to the event store
242
+ │Event Stream │ and take some action. Tracks
243
+ │ Processor │◀─ ─ ─ ─ ─its position in the stream in
244
+ │ │ a way that suits its needs.
245
+ └─────────────┘
246
+
247
+ ┌────────┴───────────┐
248
+ │ │
249
+ │ │
250
+ ┌─────────────┐ ┌─────────────┐
251
+ Listens for events and takes │ │ │ │ Listens for events and
252
+ action. Actions include ─▶│ Reactor │ │ Projector │◀─ ┐ projects data into a
253
+ emitting new events into the ─ ┘ │ │ │ │ ─ ─ projection.
254
+ store and/or triggering side └─────────────┘ └─────────────┘
255
+ effects in the world.
256
+ ```
257
+
258
+ A typical Event Sourcery application will have multiple projectors and reactors running as background processes.
259
+
260
+ #### Event Stream Processors
261
+
262
+ Event Stream Processors (ESPs) subscribe to an event store. They read events from the event store and take some action.
263
+
264
+ When newly created, an ESP will process the event stream from the beginning. When catching up like this an ESP can process events in batches (currently set to 1,000 events). This allows them to optimise processing as desired.
265
+
266
+ ESPs track the position in the event stream that they've processed in a way that suits them. This allows for them to optimise transaction handling in the case where they are catching up for example.
267
+
268
+ #### Projectors
269
+
270
+ A Projector is an EventStreamProcessor that listens for events and projects data into a projection. These projections are generally consumed on the read side of the CQRS world.
271
+
272
+ Projectors tend to be built for specific read-side needs and are generally specific to a single read case.
273
+
274
+ Modifying a projection is achieved by creating a new projector.
275
+
276
+ #### Reactors
277
+
278
+ A Reactor is an EventStreamProcessor that listens to events and emits events back into the store and/or trigger side effects in the world.
279
+
280
+ They typically record any external side effects they've triggered as events in the store.
281
+
282
+ #### Running Multiple ESPs
283
+
284
+ An EventSourcery application will typically have multiple ESPs running. EventSourcery provides a class called [ESPRunner](lib/event_sourcery/event_processing/esp_runner.rb) which can be used to run ESPs. It runs each ESP in a forked child process so each ESP can process the event store independently. You can find an example in [event_sourcery_todo_app](https://github.com/envato/event_sourcery_todo_app/blob/master/Rakefile).
285
+
286
+ Note that you may instead choose to run each ESP in their own process directly. The coordination of this is not currently provided by EventSourcery.
287
+
288
+ ### Typical Flow of State in an Event Sourcery Application
289
+
290
+ Below we see the typical flow of state in an Event Sourcery application (arrows indicate data flow). Note that steps 1 and 2 are not synchronous. This means Event Sourcery applications need to embrace [eventual consistency](https://en.wikipedia.org/wiki/Eventual_consistency).
291
+
292
+ ```
293
+
294
+ 1. Issue Command │ 2. Update Projection │ 3. Issue Query
295
+
296
+ │ │
297
+ │ ▲
298
+ │ │ │ │
299
+ │ │
300
+ │ │ │ F. Handle
301
+ B. Handle Query
302
+ Command │ │ │
303
+ │ │
304
+ │ │ │ │
305
+ ▼ ┌─────────────┐ │
306
+ ┌─────────────┐ │ │ │ │ ┌─────────────┐
307
+ │ │ ┌───────▶│ Projector │ │ │
308
+ ┌─▶│ Aggregate │ │ │ │ │ │ │Query Handler│
309
+ │ │ │ │ └─────────────┘ │ │
310
+ │ └─────────────┘ │ D. Read │ │ └─────────────┘
311
+ │ │ event E. Update ▲
312
+ A. Load C. Emit │ │ Projection │ │
313
+ state from Event │ │ G. Read
314
+ events │ │ │ │ │ Projection
315
+ │ ▼ │ ▼ │
316
+ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │
317
+ │ │ │ │ │ │ │
318
+ └──│ Event Store │───┼───┘ │ Projection │────┼───────────────┘
319
+ │ │ │ │
320
+ └─────────────┘ │ └─────────────┘ │
321
+
322
+ │ │
323
+
324
+ ```
325
+
326
+ #### 1. Handling a Command
327
+
328
+ A command comes into the application and is routed to a command handler. The command handler initialises an aggregate and loads up its state from events in the store. The command handler then defers to the aggregate to handle the command. It then stores any new events raised by the aggregate into the event store.
329
+
330
+ ```ruby
331
+ class AddTodoCommandHandler
332
+ def handle(id:, title:, description:)
333
+ # The repository provides access to the event store for saving and loading aggregates
334
+ repository = EventSourcery::Repository.new(
335
+ event_source: EventSourcery.config.event_source,
336
+ event_sink: EventSourcery.config.event_sink,
337
+ )
338
+
339
+ # Load up the aggregate from events in the store
340
+ aggregate = repository.load(TodoAggregate, id)
341
+
342
+ # Defer to the aggregate to execute the add command.
343
+ # This may raise new events in the aggregate which we'll need to save.
344
+ aggregate.add(title, description)
345
+
346
+ # Save any newly raised events back into the event store
347
+ repository.save(aggregate)
348
+ end
349
+ end
350
+ ```
351
+
352
+ #### 2. Updating a Projection
353
+
354
+ Projecting is process of converting (or collecting) a stream of events into a structural representation. You can think of the process as a fold over a sequence of events. You can think of a projection as a read model that is generally persisted somewhere like a database table.
355
+
356
+ A projector is a process that listens for new events in the event store. When it sees a new event it cares about it updates its projection.
357
+
358
+ ```ruby
359
+ class OutstandingTodosProjector
360
+ include EventSourcery::Postgres::Projector
361
+
362
+ projector_name :outstanding_todos
363
+
364
+ # Define our database table projection
365
+ table :outstanding_todos do
366
+ column :todo_id, 'UUID NOT NULL'
367
+ column :title, :text
368
+ column :description, :text
369
+ end
370
+
371
+ # Handle TodoAdded events by adding the todo to our projection
372
+ project TodoAdded do |event|
373
+ table.insert(
374
+ todo_id: event.aggregate_id,
375
+ title: event.body['title'],
376
+ description: event.body['description'],
377
+ )
378
+ end
379
+
380
+ # Handle TodoCompleted events by removing the todo from our projection
381
+ project TodoCompleted, TodoAbandoned do |event|
382
+ table.where(todo_id: event.aggregate_id).delete
383
+ end
384
+ end
385
+ ```
386
+
387
+ #### 3. Handling a Query
388
+
389
+ A query comes into the application and is routed to a query handler. The query handler queries the projection directly and returns the result.
390
+
391
+ ```ruby
392
+ module OutstandingTodos
393
+ class QueryHandler
394
+ def handle
395
+ EventSourceryTodoApp.projections_database[:outstanding_todos].all
396
+ end
397
+ end
398
+ end
399
+ ```