event_sourcery 0.13.0 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +28 -16
- data/event_sourcery.gemspec +3 -3
- data/lib/event_sourcery/event.rb +19 -1
- data/lib/event_sourcery/rspec/event_store_shared_examples.rb +1 -1
- data/lib/event_sourcery/version.rb +1 -1
- metadata +4 -15
- data/.gitignore +0 -37
- data/.rspec +0 -3
- data/.travis.yml +0 -8
- data/Gemfile +0 -5
- data/Rakefile +0 -6
- data/bin/console +0 -6
- data/bin/setup +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a6cabf34f27962b52571d80fb0a505186cef6e69
|
4
|
+
data.tar.gz: 3f07fcaf03c74190b2531099f1b4ca10d8312355
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0a25a29c4d626ae5a179668b4663dc20dc81799948ee38506ce26b3e7b10d7c31e483baf8978507e9cb199b7eda616051b7d8c90e05b5d6af99713430a4fcb83
|
7
|
+
data.tar.gz: 334f2eb26deba53e6459c00caffec74403ee039fdee14d68c45243f889f64690d20082fb98e1006e50ceefaa647d8770090f9d25785b39ddbe9efe97bca01a67
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
|
+
|
9
|
+
## [0.14.0] - 2016-6-21
|
10
|
+
### Added
|
11
|
+
- Added `Event#to_h` method. This returns a hash of the event attributes.
|
12
|
+
- Added `Event#with` method. This provides a way to create a new event
|
13
|
+
identical to the old event except for the provided changes.
|
14
|
+
- `Event#initialize` accepts `aggregate_id` parameter that either is
|
15
|
+
a strings or responds to `to_str`.
|
16
|
+
|
17
|
+
## [0.13.0] - 2016-6-16
|
8
18
|
### Added
|
9
19
|
- The core Event class accepts `causation_id` to allow event stores to
|
10
20
|
add support for tracking causation ids with events.
|
data/README.md
CHANGED
@@ -8,7 +8,8 @@ A framework for building event sourced, CQRS applications.
|
|
8
8
|
|
9
9
|
- [Development Status](#development-status)
|
10
10
|
- [Goals](#goals)
|
11
|
-
- [
|
11
|
+
- [Related Repositories](#related-repositories)
|
12
|
+
- [Getting Started](#getting-started)
|
12
13
|
- [Configuration](#configuration)
|
13
14
|
- [Development](#development)
|
14
15
|
- [Dependencies](#dependencies)
|
@@ -26,7 +27,7 @@ A framework for building event sourced, CQRS applications.
|
|
26
27
|
- [Projectors](#projectors)
|
27
28
|
- [Reactors](#reactors)
|
28
29
|
- [Running Multiple ESPs](#running-multiple-esps)
|
29
|
-
- [Typical Flow of State in an
|
30
|
+
- [Typical Flow of State in an EventSourcery Application](#typical-flow-of-state-in-an-eventsourcery-application)
|
30
31
|
- [1. Handling a Command](#1-handling-a-command)
|
31
32
|
- [2. Updating a Projection](#2-updating-a-projection)
|
32
33
|
- [3. Handling a Query](#3-handling-a-query)
|
@@ -43,15 +44,21 @@ The goal of EventSourcery is to make it easier to build event sourced, CQRS appl
|
|
43
44
|
|
44
45
|
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
|
|
46
|
-
##
|
47
|
+
## Related Repositories
|
47
48
|
|
48
|
-
EventSourcery
|
49
|
+
- EventSourcery's Postgres-based event store implementation: [event_sourcery-postgres](https://github.com/envato/event_sourcery-postgres).
|
50
|
+
- Example EventSourcery application: [event_sourcery_todo_app](https://github.com/envato/event_sourcery_todo_app).
|
51
|
+
- An opinionated CLI tool for building event sourced Ruby services with EventSourcery: [event_sourcery_generators](https://github.com/envato/event_sourcery_generators).
|
49
52
|
|
50
|
-
|
53
|
+
## Getting Started
|
54
|
+
|
55
|
+
The [example EventSourcery application](https://github.com/envato/event_sourcery_todo_app) is intended to illustrate concepts in EventSourcery, how they relate to each other, and how to use them in practice. If you'd like a succinct look at the library in practice take a look at that.
|
56
|
+
|
57
|
+
Otherwise you will generally need to add both event_sourcery and [event_sourcery-postgres](https://github.com/envato/event_sourcery-postgres) to your application.
|
51
58
|
|
52
59
|
## Configuration
|
53
60
|
|
54
|
-
There are several ways to configure
|
61
|
+
There are several ways to configure EventSourcery to your liking. The following presents some examples:
|
55
62
|
|
56
63
|
```ruby
|
57
64
|
EventSourcery.configure do |config|
|
@@ -59,7 +66,7 @@ EventSourcery.configure do |config|
|
|
59
66
|
# One might set up Rollbar here.
|
60
67
|
config.on_event_processor_error = proc { |exception, processor_name| … }
|
61
68
|
|
62
|
-
# Enable
|
69
|
+
# Enable EventSourcery logging.
|
63
70
|
config.logger = Logger.new('logs/my_event_sourcery_app.log')
|
64
71
|
|
65
72
|
# Customize how event body attributes are serialized
|
@@ -162,7 +169,7 @@ Below is a high level view of a CQRS, event-sourced web application built using
|
|
162
169
|
|
163
170
|
### Events
|
164
171
|
|
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.
|
172
|
+
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. Together they form the source of truth for our application's state.
|
166
173
|
|
167
174
|
Events are targeted at an aggregate via an `aggregate_id` and have the following attributes.
|
168
175
|
|
@@ -235,7 +242,7 @@ A typical EventSourcery application will have one or more aggregate roots with m
|
|
235
242
|
|
236
243
|
### Event Processing
|
237
244
|
|
238
|
-
A central part of EventSourcery is the processing of events in the store.
|
245
|
+
A central part of EventSourcery is the processing of events in the store. EventSourcery provides the Event Stream Processor abstraction to support this.
|
239
246
|
|
240
247
|
```
|
241
248
|
┌─────────────┐ Subscribe to the event store
|
@@ -255,7 +262,7 @@ store and/or triggering side └─────────────┘
|
|
255
262
|
effects in the world.
|
256
263
|
```
|
257
264
|
|
258
|
-
A typical
|
265
|
+
A typical EventSourcery application will have multiple projectors and reactors running as background processes.
|
259
266
|
|
260
267
|
#### Event Stream Processors
|
261
268
|
|
@@ -279,15 +286,17 @@ A Reactor is an EventStreamProcessor that listens to events and emits events bac
|
|
279
286
|
|
280
287
|
They typically record any external side effects they've triggered as events in the store.
|
281
288
|
|
289
|
+
Reactors can be used to build [process managers or sagas](https://msdn.microsoft.com/en-us/library/jj591569.aspx).
|
290
|
+
|
282
291
|
#### Running Multiple ESPs
|
283
292
|
|
284
293
|
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
294
|
|
286
295
|
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
296
|
|
288
|
-
### Typical Flow of State in an
|
297
|
+
### Typical Flow of State in an EventSourcery Application
|
289
298
|
|
290
|
-
Below we see the typical flow of state in an
|
299
|
+
Below we see the typical flow of state in an EventSourcery application (arrows indicate data flow). Note that steps 1 and 2 are not synchronous. This means EventSourcery applications need to embrace [eventual consistency](https://en.wikipedia.org/wiki/Eventual_consistency).
|
291
300
|
|
292
301
|
```
|
293
302
|
|
@@ -351,7 +360,9 @@ end
|
|
351
360
|
|
352
361
|
#### 2. Updating a Projection
|
353
362
|
|
354
|
-
|
363
|
+
You can think of projections as read-only models. They are created and updated by projectors and show different views over the events that are the source of truth for our application state. Projections are typically stored as database tables.
|
364
|
+
|
365
|
+
Projecting is process of converting (or collecting) a stream of events into these database tables. You can think of this process as a fold over a sequence of events.
|
355
366
|
|
356
367
|
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
368
|
|
@@ -361,14 +372,14 @@ class OutstandingTodosProjector
|
|
361
372
|
|
362
373
|
projector_name :outstanding_todos
|
363
374
|
|
364
|
-
#
|
375
|
+
# Database table that forms the projection.
|
365
376
|
table :outstanding_todos do
|
366
377
|
column :todo_id, 'UUID NOT NULL'
|
367
378
|
column :title, :text
|
368
379
|
column :description, :text
|
369
380
|
end
|
370
381
|
|
371
|
-
# Handle TodoAdded events by adding the todo to our projection
|
382
|
+
# Handle TodoAdded events by adding the todo to our projection.
|
372
383
|
project TodoAdded do |event|
|
373
384
|
table.insert(
|
374
385
|
todo_id: event.aggregate_id,
|
@@ -377,7 +388,7 @@ class OutstandingTodosProjector
|
|
377
388
|
)
|
378
389
|
end
|
379
390
|
|
380
|
-
# Handle TodoCompleted events by removing the todo from our projection
|
391
|
+
# Handle TodoCompleted events by removing the todo from our projection.
|
381
392
|
project TodoCompleted, TodoAbandoned do |event|
|
382
393
|
table.where(todo_id: event.aggregate_id).delete
|
383
394
|
end
|
@@ -390,6 +401,7 @@ A query comes into the application and is routed to a query handler. The query h
|
|
390
401
|
|
391
402
|
```ruby
|
392
403
|
module OutstandingTodos
|
404
|
+
# Query handler that queries the projection table.
|
393
405
|
class QueryHandler
|
394
406
|
def handle
|
395
407
|
EventSourceryTodoApp.projections_database[:outstanding_todos].all
|
data/event_sourcery.gemspec
CHANGED
@@ -6,14 +6,14 @@ require 'event_sourcery/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = 'event_sourcery'
|
8
8
|
spec.version = EventSourcery::VERSION
|
9
|
-
spec.authors = ['
|
10
|
-
spec.email = ['
|
9
|
+
spec.authors = ['Envato']
|
10
|
+
spec.email = ['rubygems@envato.com']
|
11
11
|
|
12
12
|
spec.summary = 'Event Sourcing Library'
|
13
13
|
spec.description = ''
|
14
14
|
spec.homepage = 'https://github.com/envato/event_sourcery'
|
15
15
|
|
16
|
-
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(\.|Gemfile|Rakefile|bin/|script/|spec/)}) }
|
17
17
|
spec.bindir = 'exe'
|
18
18
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
19
|
spec.require_paths = ['lib']
|
data/lib/event_sourcery/event.rb
CHANGED
@@ -21,7 +21,7 @@ module EventSourcery
|
|
21
21
|
causation_id: nil)
|
22
22
|
@id = id
|
23
23
|
@uuid = uuid && uuid.downcase
|
24
|
-
@aggregate_id = aggregate_id
|
24
|
+
@aggregate_id = aggregate_id && aggregate_id.to_str
|
25
25
|
@type = self.class.type || type.to_s
|
26
26
|
@body = body ? EventSourcery::EventBodySerializer.serialize(body) : {}
|
27
27
|
@version = version ? Integer(version) : nil
|
@@ -45,5 +45,23 @@ module EventSourcery
|
|
45
45
|
def <=>(other)
|
46
46
|
id <=> other.id if other.is_a? Event
|
47
47
|
end
|
48
|
+
|
49
|
+
def with(**attributes)
|
50
|
+
self.class.new(**to_h.merge!(attributes))
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_h
|
54
|
+
{
|
55
|
+
id: id,
|
56
|
+
uuid: uuid,
|
57
|
+
aggregate_id: aggregate_id,
|
58
|
+
type: type,
|
59
|
+
body: body,
|
60
|
+
version: version,
|
61
|
+
created_at: created_at,
|
62
|
+
correlation_id: correlation_id,
|
63
|
+
causation_id: causation_id,
|
64
|
+
}
|
65
|
+
end
|
48
66
|
end
|
49
67
|
end
|
@@ -188,7 +188,7 @@ RSpec.shared_examples 'an event store' do
|
|
188
188
|
RSpec.shared_examples 'gets events for a specific aggregate id' do
|
189
189
|
before do
|
190
190
|
event_store.sink(new_event(aggregate_id: aggregate_id, type: 'item_added', body: { 'my' => 'body' }))
|
191
|
-
event_store.sink(new_event(aggregate_id: aggregate_id))
|
191
|
+
event_store.sink(new_event(aggregate_id: double(to_str: aggregate_id)))
|
192
192
|
event_store.sink(new_event(aggregate_id: SecureRandom.uuid))
|
193
193
|
end
|
194
194
|
|
metadata
CHANGED
@@ -1,16 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: event_sourcery
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
8
|
-
- Tao Guo
|
9
|
-
- Sebastian von Conrad
|
7
|
+
- Envato
|
10
8
|
autorequire:
|
11
9
|
bindir: exe
|
12
10
|
cert_chain: []
|
13
|
-
date: 2017-06-
|
11
|
+
date: 2017-06-21 00:00:00.000000000 Z
|
14
12
|
dependencies:
|
15
13
|
- !ruby/object:Gem::Dependency
|
16
14
|
name: bundler
|
@@ -84,24 +82,15 @@ dependencies:
|
|
84
82
|
version: '0'
|
85
83
|
description: ''
|
86
84
|
email:
|
87
|
-
-
|
88
|
-
- tao.guo@envato.com
|
89
|
-
- sebastian.von.conrad@envato.com
|
85
|
+
- rubygems@envato.com
|
90
86
|
executables: []
|
91
87
|
extensions: []
|
92
88
|
extra_rdoc_files: []
|
93
89
|
files:
|
94
|
-
- ".gitignore"
|
95
|
-
- ".rspec"
|
96
|
-
- ".travis.yml"
|
97
90
|
- CHANGELOG.md
|
98
91
|
- CODE_OF_CONDUCT.md
|
99
|
-
- Gemfile
|
100
92
|
- LICENSE.txt
|
101
93
|
- README.md
|
102
|
-
- Rakefile
|
103
|
-
- bin/console
|
104
|
-
- bin/setup
|
105
94
|
- event_sourcery.gemspec
|
106
95
|
- lib/event_sourcery.rb
|
107
96
|
- lib/event_sourcery/aggregate_root.rb
|
data/.gitignore
DELETED
@@ -1,37 +0,0 @@
|
|
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
DELETED
data/.travis.yml
DELETED
data/Gemfile
DELETED
data/Rakefile
DELETED
data/bin/console
DELETED