akasha 0.3.0 → 0.4.0.pre.edge.pre.5
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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +3 -0
- data/.travis.yml +21 -26
- data/CHANGELOG.md +28 -7
- data/Gemfile.lock +13 -12
- data/README.md +6 -6
- data/Rakefile +16 -0
- data/akasha.gemspec +8 -3
- data/examples/sinatra/app.rb +1 -1
- data/lib/akasha/aggregate.rb +2 -1
- data/lib/akasha/async_event_router.rb +12 -2
- data/lib/akasha/checkpoint/http_event_store_checkpoint.rb +2 -0
- data/lib/akasha/event.rb +4 -0
- data/lib/akasha/event_router_base.rb +3 -1
- data/lib/akasha/repository.rb +14 -10
- data/lib/akasha/storage/http_event_store.rb +11 -26
- data/lib/akasha/storage/http_event_store/client.rb +7 -4
- data/lib/akasha/storage/http_event_store/exceptions.rb +27 -0
- data/lib/akasha/storage/http_event_store/projection_manager.rb +39 -17
- data/lib/akasha/storage/http_event_store/stream.rb +11 -6
- data/lib/akasha/storage/memory_event_store.rb +7 -2
- data/lib/akasha/version.rb +1 -1
- metadata +21 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ef5ba87a94db893bc51fa6e9473ce73007ef1cd
|
4
|
+
data.tar.gz: 8c83ff0c79d88bd3dd939c11ed7bde46ad1be4de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a787f8c35533063aeacb5071e1dfa6db0b79a892816a3c98d83eb99da9954f0b2c2138df325d1a0c0c37aed53cefed7fb0ba39c71ae38a8e000b89bba1a03db
|
7
|
+
data.tar.gz: 31e9a1d1fa1abc7833a4302955554e5b4b34f3555f16118fca09b450fb187abe85dd845ced94ec361b634ddaca1ff955ab1997d1c0e3e2da2e9624b14dca1595
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -5,6 +5,7 @@ Metrics/AbcSize:
|
|
5
5
|
Metrics/BlockLength:
|
6
6
|
Exclude:
|
7
7
|
- spec/**/*_spec.rb
|
8
|
+
- spec/**/*_shared_examples.rb
|
8
9
|
- akasha.gemspec
|
9
10
|
Metrics/ClassLength:
|
10
11
|
Max: 110
|
@@ -14,3 +15,5 @@ Metrics/MethodLength:
|
|
14
15
|
Max: 15
|
15
16
|
Rails/Output:
|
16
17
|
Enabled: false
|
18
|
+
Style/FormatStringToken:
|
19
|
+
Enabled: false
|
data/.travis.yml
CHANGED
@@ -1,41 +1,36 @@
|
|
1
|
-
# Enables Travis to use their new container-based infrastructure
|
2
1
|
sudo: false
|
3
2
|
|
4
|
-
# Integration tests are using docker-compose
|
5
3
|
services:
|
6
|
-
|
4
|
+
- docker
|
7
5
|
|
8
|
-
# Build for Ruby
|
9
6
|
language: ruby
|
10
7
|
|
11
|
-
# Enables caching for bundler
|
12
8
|
cache: bundler
|
13
9
|
|
14
|
-
# Passes arguments to bundle install (http://gembundler.com/man/bundle-install.1.html)
|
15
|
-
# bundler_args:
|
16
|
-
|
17
10
|
before_install:
|
18
|
-
|
19
|
-
|
11
|
+
- gem update --system
|
12
|
+
- gem update bundler
|
13
|
+
- bundle update
|
20
14
|
|
21
|
-
# Specify which ruby versions you wish to run your tests on, each version will be used
|
22
15
|
matrix:
|
23
16
|
include:
|
24
|
-
|
25
|
-
|
26
|
-
|
17
|
+
- rvm: 2.3.6
|
18
|
+
# - rvm: 2.5.1
|
19
|
+
# - env: INTEGRATION_TESTS=true
|
27
20
|
|
28
|
-
# Define how to run your tests.
|
29
21
|
script:
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
# Disable email notifications
|
22
|
+
- |
|
23
|
+
# Run either integration tests or specs.
|
24
|
+
case "$INTEGRATION_TESTS" in
|
25
|
+
true) bin/integration-tests.sh ;;
|
26
|
+
*) bundle exec rspec --tag ~integration ;;
|
27
|
+
esac
|
28
|
+
- bundle exec rake rubocop
|
29
|
+
|
40
30
|
notifications:
|
41
|
-
|
31
|
+
disabled: true
|
32
|
+
|
33
|
+
deploy:
|
34
|
+
provider: rubygems
|
35
|
+
api_key:
|
36
|
+
secure: EGspR+X6oNBQMMZO3QRXT6RW5tglTj3X9/9HiPJQDCnAnBCWxrxBdvrlhf4sMSrcfuxXt7mGeXaoD/sOrHS7BOmVE6SrJJhJgph8Uv3EnfEE4V8LswjGXRfzf+6g6PxGAR2pcaYdGfyQVb0wLvAFArjs7aBg+8+fisP+ffwi6ahPd2YzUNb0Oh4J0rvmqGt6KCXR9z/4OKzNyweHmXLM+PfZr5uRs8NT70tx+n8zo5YrgCk5t8odQTchsKWGkFF8dzP3mzjQTt3d0MW0rkykgjd+rFCVmLA1L4aErkLAQxNM8YogsrS7hptwTF390vhL0yTyRQpY8fQU+U8igwtP3XcoXw5cngdZDR3vFUo7RgxJiIVESPRGJ8uTkxMzX7rkyDNAcLj82cT2etouKgAxK9G5/nvC6IEVF6x3AkH93gruNZEHj2BRXKYIhL3o6u6CeUuA9KM3jQI5FeAKzQ19yf8wk+wu/I3pK83evMtSQ07cDHOgzdHjZjoUOBZlshrKSkkByBS+ueJmZRg2flTtGkxpy0hzgHjQ12HFOPYrTKEICMgGKhAohUQoUji/+iHAzaRZygfqJKLXJKPAR0JGumvEqCuZfcvL+T+Cg4R2RZLbdvF9YN42aVvSJNTS6/lA5J0+1cm+9XCZFYX0NR9CD85wfzU9zBHh0obYt61zgvI=
|
data/CHANGELOG.md
CHANGED
@@ -1,22 +1,43 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## Version 0.4.0.edge3
|
4
|
+
|
5
|
+
* Fixed issue for passsing Handlers to EventRouter via constructor, when they aren't wrapped in array.
|
6
|
+
|
7
|
+
## Version 0.4.0.edge2
|
8
|
+
|
9
|
+
* Control the maximum number of retries in case of network related failures. [#14](https://github.com/bilus/akasha/pull/14)
|
10
|
+
Example:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
store = Akasha::Storage::HttpEventStore.new(..., max_retries: 10)
|
14
|
+
```
|
15
|
+
|
16
|
+
|
17
|
+
## Version 0.4.0.edge1
|
18
|
+
|
19
|
+
* Optional namespacing for aggregate/projection streams and events allowing for isolation
|
20
|
+
between applications. [#12](https://github.com/bilus/akasha/pull/12)
|
21
|
+
* Fix Unhandled events in stream break aggregate loading. [Issue #5](https://github.com/bilus/akasha/issues/5)
|
22
|
+
|
23
|
+
|
3
24
|
## Version 0.3.0
|
4
25
|
|
5
|
-
* Asynchronous event listeners (`AsyncEventRouter`).
|
6
|
-
* Simplified initialization of event- and command routers.
|
26
|
+
* Asynchronous event listeners (`AsyncEventRouter`). [#9](https://github.com/bilus/akasha/pull/9)
|
27
|
+
* Simplified initialization of event- and command routers. [#10](https://github.com/bilus/akasha/pull/10)
|
7
28
|
* Remove dependency on the `http_event_store` gem.
|
8
|
-
* `Event#metadata` is no longer OpenStruct.
|
29
|
+
* `Event#metadata` is no longer OpenStruct. [#10](https://github.com/bilus/akasha/pull/10)
|
9
30
|
|
10
31
|
## Version 0.2.0
|
11
32
|
|
12
|
-
* Synchronous event listeners (see `examples/sinatra/app.rb`).
|
13
|
-
* HTTP-based Eventstore storage.
|
33
|
+
* Synchronous event listeners (see `examples/sinatra/app.rb`). [#4](https://github.com/bilus/akasha/pull/4)
|
34
|
+
* HTTP-based Eventstore storage. [#7](https://github.com/bilus/akasha/pull/7)
|
14
35
|
|
15
36
|
|
16
37
|
## Version 0.1.0
|
17
38
|
|
18
|
-
* Cleaner syntax for adding events to changesets: `changeset.append(:it_happened, foo: 'bar')`.
|
19
|
-
* Support for command routing (`Akasha::CommandRouter`).
|
39
|
+
* Cleaner syntax for adding events to changesets: `changeset.append(:it_happened, foo: 'bar')`. [#1](https://github.com/bilus/akasha/pull/1)
|
40
|
+
* Support for command routing (`Akasha::CommandRouter`). [#2](https://github.com/bilus/akasha/pull/2)
|
20
41
|
|
21
42
|
|
22
43
|
## Version 0.0.1
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
akasha (0.
|
4
|
+
akasha (0.4.0.pre.edge.pre.5)
|
5
5
|
corefines (~> 1.11)
|
6
6
|
faraday (~> 0.15)
|
7
7
|
faraday_middleware
|
@@ -12,7 +12,7 @@ PATH
|
|
12
12
|
GEM
|
13
13
|
remote: https://rubygems.org/
|
14
14
|
specs:
|
15
|
-
ast (2.
|
15
|
+
ast (2.4.0)
|
16
16
|
byebug (10.0.2)
|
17
17
|
coderay (1.1.2)
|
18
18
|
corefines (1.11.0)
|
@@ -24,12 +24,13 @@ GEM
|
|
24
24
|
faraday_middleware (0.12.2)
|
25
25
|
faraday (>= 0.7.4, < 1.0)
|
26
26
|
ffi (1.9.25)
|
27
|
+
jaro_winkler (1.5.1)
|
27
28
|
method_source (0.9.0)
|
28
29
|
multipart-post (2.0.0)
|
29
|
-
parallel (1.12.
|
30
|
-
parser (2.
|
31
|
-
ast (~> 2.
|
32
|
-
powerpack (0.1.
|
30
|
+
parallel (1.12.1)
|
31
|
+
parser (2.5.1.0)
|
32
|
+
ast (~> 2.4.0)
|
33
|
+
powerpack (0.1.2)
|
33
34
|
pry (0.11.3)
|
34
35
|
coderay (~> 1.1.0)
|
35
36
|
method_source (~> 0.9.0)
|
@@ -37,8 +38,7 @@ GEM
|
|
37
38
|
byebug (~> 10.0)
|
38
39
|
pry (~> 0.10)
|
39
40
|
rack (2.0.5)
|
40
|
-
rainbow (
|
41
|
-
rake
|
41
|
+
rainbow (3.0.0)
|
42
42
|
rake (10.5.0)
|
43
43
|
retries (0.0.5)
|
44
44
|
rspec (3.7.0)
|
@@ -56,18 +56,19 @@ GEM
|
|
56
56
|
rspec-support (3.7.1)
|
57
57
|
rspec-wait (0.0.9)
|
58
58
|
rspec (>= 3, < 4)
|
59
|
-
rubocop (0.
|
59
|
+
rubocop (0.57.2)
|
60
|
+
jaro_winkler (~> 1.5.1)
|
60
61
|
parallel (~> 1.10)
|
61
|
-
parser (>= 2.
|
62
|
+
parser (>= 2.5)
|
62
63
|
powerpack (~> 0.1)
|
63
|
-
rainbow (>= 2.2.2, <
|
64
|
+
rainbow (>= 2.2.2, < 4.0)
|
64
65
|
ruby-progressbar (~> 1.7)
|
65
66
|
unicode-display_width (~> 1.0, >= 1.0.1)
|
66
67
|
ruby-progressbar (1.9.0)
|
67
68
|
timecop (0.9.1)
|
68
69
|
typhoeus (1.3.0)
|
69
70
|
ethon (>= 0.9.0)
|
70
|
-
unicode-display_width (1.
|
71
|
+
unicode-display_width (1.4.0)
|
71
72
|
|
72
73
|
PLATFORMS
|
73
74
|
ruby
|
data/README.md
CHANGED
@@ -30,23 +30,23 @@ This library itself makes no assumptions about any web framework, you can use it
|
|
30
30
|
- [x] HTTP Eventstore storage backend
|
31
31
|
- [x] Event#id for better idempotence (validate this claim)
|
32
32
|
- [x] Async EventHandlers (storing cursors in Eventstore, configurable durability guarantees)
|
33
|
-
- [x] Uniform
|
33
|
+
- [x] Uniform interface for Client -- use Event.
|
34
34
|
- [x] Rewrite Client
|
35
35
|
- [x] Refactor Client code
|
36
36
|
- [x] Take care of created_at/updated_at (saved_at?)
|
37
37
|
- [x] Tests for HttpEventStore
|
38
38
|
- [x] Projections
|
39
39
|
- [x] Test for AsyncEventRouter using events not aggregate
|
40
|
-
- [x] BUG: Projection reorders events
|
40
|
+
- [x] BUG: Projection reorders events (need to use fromAll after all)
|
41
41
|
- [x] Simplify AsyncEventRouter init
|
42
42
|
- [x] SyncEventRouter => EventRouter
|
43
43
|
- [x] Metadata not persisted
|
44
44
|
- [x] Refactoring & simplification.
|
45
45
|
- [x] Hash-based event and command router
|
46
|
-
- [x]
|
47
|
-
- [x]
|
48
|
-
|
49
|
-
- [
|
46
|
+
- [x] Assymetry between data and metadata
|
47
|
+
- [x] Faster shutdown
|
48
|
+
- [x] Namespacing for events and aggregates and the projection
|
49
|
+
- [x] Way to control the number of retries in face of network failures
|
50
50
|
- [ ] Version-based concurrency
|
51
51
|
- [ ] Telemetry (Dogstatsd)
|
52
52
|
- [ ] Socket-based Eventstore storage backend
|
data/Rakefile
CHANGED
@@ -2,6 +2,22 @@ require 'bundler/gem_tasks'
|
|
2
2
|
require 'rspec/core/rake_task'
|
3
3
|
require 'rubocop/rake_task'
|
4
4
|
|
5
|
+
namespace 'ci' do
|
6
|
+
task 'tag' do
|
7
|
+
gemspecs = Dir[File.join(__dir__, '{,*}.gemspec')]
|
8
|
+
raise 'No gemspec found' unless gemspecs.size == 1
|
9
|
+
spec_path = gemspecs.first
|
10
|
+
gemspec = Bundler.load_gemspec(spec_path)
|
11
|
+
version_tag = "v#{gemspec.version}"
|
12
|
+
return if `git tag`.split(/\n/).include?(version_tag)
|
13
|
+
`git tag #{version_tag}`
|
14
|
+
`git push --tags`
|
15
|
+
end
|
16
|
+
|
17
|
+
task 'release' => ['build', 'release:guard_clean', 'release:rubygem_push', 'ci:tag'] do
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
5
21
|
RuboCop::RakeTask.new(:rubocop) do |task|
|
6
22
|
task.options = ['--rails', '--display-cop-names']
|
7
23
|
end
|
data/akasha.gemspec
CHANGED
@@ -1,11 +1,15 @@
|
|
1
|
-
|
1
|
+
# rubocop:disable Style/ExpandPathArguments
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
4
|
require 'akasha/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = 'akasha'
|
8
|
+
# rubocop:disable Gemspec/DuplicatedAssignment
|
8
9
|
spec.version = Akasha::VERSION
|
10
|
+
spec.version = "#{spec.version}-#{ENV['TRAVIS_BUILD_NUMBER']}" \
|
11
|
+
if ENV['TRAVIS'] && spec.version.include('edge')
|
12
|
+
# rubocop:enable Gemspec/DuplicatedAssignment
|
9
13
|
spec.authors = ['Marcin Bilski']
|
10
14
|
spec.email = ['marcin@tooploox.com']
|
11
15
|
|
@@ -23,12 +27,12 @@ Gem::Specification.new do |spec|
|
|
23
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
28
|
spec.require_paths = ['lib']
|
25
29
|
|
30
|
+
spec.add_dependency 'corefines', '~>1.11'
|
26
31
|
spec.add_dependency 'faraday', '~> 0.15'
|
27
32
|
spec.add_dependency 'faraday_middleware'
|
28
|
-
spec.add_dependency 'typhoeus', '~> 1.3'
|
29
33
|
spec.add_dependency 'rack', '~> 2.0'
|
30
34
|
spec.add_dependency 'retries', '~> 0.0'
|
31
|
-
spec.add_dependency '
|
35
|
+
spec.add_dependency 'typhoeus', '~> 1.3'
|
32
36
|
|
33
37
|
spec.add_development_dependency 'bundler', '~> 1.16'
|
34
38
|
spec.add_development_dependency 'pry-byebug'
|
@@ -38,3 +42,4 @@ Gem::Specification.new do |spec|
|
|
38
42
|
spec.add_development_dependency 'rubocop', '~> 0.50'
|
39
43
|
spec.add_development_dependency 'timecop', '~> 0.9'
|
40
44
|
end
|
45
|
+
# rubocop:enable Style/ExpandPathArguments
|
data/examples/sinatra/app.rb
CHANGED
data/lib/akasha/aggregate.rb
CHANGED
@@ -9,13 +9,16 @@ module Akasha
|
|
9
9
|
DEFAULT_PAGE_SIZE = 20
|
10
10
|
DEFAULT_PROJECTION_STREAM = 'AsyncEventRouter'.freeze
|
11
11
|
DEFAULT_CHECKPOINT_STRATEGY = Akasha::Checkpoint::HttpEventStoreCheckpoint
|
12
|
+
STREAM_NAME_SEP = '-'.freeze
|
12
13
|
|
13
|
-
def connect!(repository, projection_name:
|
14
|
+
def connect!(repository, projection_name: nil,
|
14
15
|
checkpoint_strategy: DEFAULT_CHECKPOINT_STRATEGY,
|
15
16
|
page_size: DEFAULT_PAGE_SIZE, poll: DEFAULT_POLL_SECONDS)
|
17
|
+
projection_name = projection_name(repository) if projection_name.nil?
|
16
18
|
projection_stream = repository.store.streams[projection_name]
|
17
19
|
checkpoint = checkpoint_strategy.is_a?(Class) ? checkpoint_strategy.new(projection_stream) : checkpoint_strategy
|
18
|
-
repository.merge_all_by_event(into: projection_name,
|
20
|
+
repository.merge_all_by_event(into: projection_name,
|
21
|
+
only: registered_event_names)
|
19
22
|
Thread.new do
|
20
23
|
run_forever(projection_stream, checkpoint, page_size, poll)
|
21
24
|
end
|
@@ -40,5 +43,12 @@ module Akasha
|
|
40
43
|
end
|
41
44
|
end
|
42
45
|
end
|
46
|
+
|
47
|
+
def projection_name(repository)
|
48
|
+
parts = []
|
49
|
+
parts << repository.namespace unless repository.namespace.nil?
|
50
|
+
parts << DEFAULT_PROJECTION_STREAM
|
51
|
+
parts.join(STREAM_NAME_SEP)
|
52
|
+
end
|
43
53
|
end
|
44
54
|
end
|
@@ -14,10 +14,12 @@ module Akasha
|
|
14
14
|
raise UnsupportedStorageError, "Storage does not support checkpoints: #{stream.class}"
|
15
15
|
end
|
16
16
|
|
17
|
+
# rubocop:disable Naming/MemoizedInstanceVariableName
|
17
18
|
# Returns the most recently stored next position.
|
18
19
|
def latest
|
19
20
|
@next_position ||= (read_position || 0)
|
20
21
|
end
|
22
|
+
# rubocop:enable Naming/MemoizedInstanceVariableName
|
21
23
|
|
22
24
|
# Returns the next position, conditionally storing it (based on the configurable interval).
|
23
25
|
def ack(position)
|
data/lib/akasha/event.rb
CHANGED
@@ -3,7 +3,9 @@ module Akasha
|
|
3
3
|
class EventRouterBase
|
4
4
|
def initialize(routes = {})
|
5
5
|
@routes = Hash.new { |hash, key| hash[key] = [] }
|
6
|
-
|
6
|
+
# Support both array of listeners and a single listener
|
7
|
+
normalized_routes = routes.map { |command, listeners| [command, Array(listeners)] }.to_h
|
8
|
+
@routes.merge!(normalized_routes)
|
7
9
|
end
|
8
10
|
|
9
11
|
# Registers a new event listener, derived from
|
data/lib/akasha/repository.rb
CHANGED
@@ -3,14 +3,15 @@ module Akasha
|
|
3
3
|
# Not meant to be used directly (see aggregate/syntax_helpers.rb)
|
4
4
|
# See specs for usage.
|
5
5
|
class Repository
|
6
|
-
attr_reader :store
|
6
|
+
attr_reader :store, :namespace
|
7
7
|
|
8
8
|
STREAM_NAME_SEP = '-'.freeze
|
9
9
|
|
10
10
|
# Creates a new repository using the underlying `store` (e.g. `MemoryEventStore`).
|
11
|
-
def initialize(store)
|
11
|
+
def initialize(store, namespace: nil)
|
12
12
|
@store = store
|
13
13
|
@subscribers = []
|
14
|
+
@namespace = namespace
|
14
15
|
end
|
15
16
|
|
16
17
|
# Loads an aggregate identified by `id` and `klass` from the repository.
|
@@ -31,8 +32,9 @@ module Akasha
|
|
31
32
|
# Saves an aggregate to the repository, appending events to the corresponding stream.
|
32
33
|
def save_aggregate(aggregate)
|
33
34
|
changeset = aggregate.changeset
|
34
|
-
|
35
|
-
|
35
|
+
events = changeset.events.map { |event| event.with_metadata(namespace: @namespace) }
|
36
|
+
stream(aggregate.class, changeset.aggregate_id).write_events(events)
|
37
|
+
notify_subscribers(changeset.aggregate_id, events)
|
36
38
|
end
|
37
39
|
|
38
40
|
# Subscribes to event streams passing either a lambda or a block.
|
@@ -54,25 +56,27 @@ module Akasha
|
|
54
56
|
# `into` - name of the new stream
|
55
57
|
# `only` - array of event names
|
56
58
|
def merge_all_by_event(into:, only:)
|
57
|
-
@store.merge_all_by_event(into: into, only: only)
|
59
|
+
@store.merge_all_by_event(into: into, only: only, namespace: @namespace)
|
58
60
|
end
|
59
61
|
|
60
62
|
private
|
61
63
|
|
62
64
|
def stream_name(aggregate_klass, aggregate_id)
|
63
|
-
|
65
|
+
parts = []
|
66
|
+
parts << @namespace if @namespace
|
67
|
+
parts << aggregate_klass
|
68
|
+
parts << aggregate_id
|
69
|
+
parts.join(STREAM_NAME_SEP)
|
64
70
|
end
|
65
71
|
|
66
72
|
def stream(aggregate_klass, aggregate_id)
|
67
73
|
@store.streams[stream_name(aggregate_klass, aggregate_id)]
|
68
74
|
end
|
69
75
|
|
70
|
-
def notify_subscribers(
|
71
|
-
id = aggregate.changeset.aggregate_id
|
72
|
-
events = aggregate.changeset.events
|
76
|
+
def notify_subscribers(aggregate_id, events)
|
73
77
|
@subscribers.each do |subscriber|
|
74
78
|
events.each do |event|
|
75
|
-
subscriber.call(
|
79
|
+
subscriber.call(aggregate_id, event)
|
76
80
|
end
|
77
81
|
end
|
78
82
|
end
|
@@ -1,34 +1,17 @@
|
|
1
1
|
require_relative 'http_event_store/client'
|
2
2
|
require_relative 'http_event_store/stream'
|
3
|
+
require_relative 'http_event_store/exceptions'
|
3
4
|
|
4
5
|
module Akasha
|
5
6
|
module Storage
|
6
7
|
# HTTP-based interface to Eventstore (https://geteventstore.com)
|
7
8
|
class HttpEventStore
|
8
|
-
#
|
9
|
-
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
# Base class for HTTP errors.
|
14
|
-
class HttpError < Error
|
15
|
-
attr_reader :status_code
|
16
|
-
|
17
|
-
def initialize(status_code)
|
18
|
-
@status_code = status_code
|
19
|
-
super("Unexpected HTTP response: #{@status_code}")
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
# 4xx HTTP status code.
|
24
|
-
HttpClientError = Class.new(HttpError)
|
25
|
-
# 5xx HTTP status code.
|
26
|
-
HttpServerError = Class.new(HttpError)
|
27
|
-
|
28
|
-
# Creates a new event store client, connecting to the specified host and port
|
29
|
-
# using an optional username and password.
|
30
|
-
def initialize(host: 'localhost', port: 2113, username: nil, password: nil)
|
9
|
+
# Creates a new event store client, connecting to the specified `host` and `port`
|
10
|
+
# using an optional `username` and `password`.
|
11
|
+
# Use the `max_retries` option to choose how many times to retry in case of network failures.
|
12
|
+
def initialize(host: 'localhost', port: 2113, username: nil, password: nil, max_retries: 0)
|
31
13
|
@client = Client.new(host: host, port: port, username: username, password: password)
|
14
|
+
@max_retries = max_retries
|
32
15
|
end
|
33
16
|
|
34
17
|
# Returns a Hash of streams. You can retrieve a Stream instance corresponding
|
@@ -40,7 +23,7 @@ module Akasha
|
|
40
23
|
|
41
24
|
# Shortcut for accessing streams by their names.
|
42
25
|
def [](stream_name)
|
43
|
-
Stream.new(@client, stream_name)
|
26
|
+
Stream.new(@client, stream_name, max_retries: @max_retries)
|
44
27
|
end
|
45
28
|
|
46
29
|
# Merges all streams into one, filtering the resulting stream
|
@@ -50,8 +33,10 @@ module Akasha
|
|
50
33
|
# Arguments:
|
51
34
|
# `into` - name of the new stream
|
52
35
|
# `only` - array of event names
|
53
|
-
|
54
|
-
|
36
|
+
# `namespace` - optional namespace; if provided, the resulting stream will
|
37
|
+
# only contain events with the same `metadata[:namespace]`
|
38
|
+
def merge_all_by_event(into:, only:, namespace: nil)
|
39
|
+
@client.merge_all_by_event(into, only, namespace: namespace, max_retries: @max_retries)
|
55
40
|
end
|
56
41
|
end
|
57
42
|
end
|
@@ -56,9 +56,12 @@ module Akasha
|
|
56
56
|
# Arguments:
|
57
57
|
# `name` - name of the projection stream
|
58
58
|
# `event_names` - array of event names
|
59
|
-
|
59
|
+
# `namespace` - optional namespace; if provided, the resulting stream will
|
60
|
+
# only contain events with the same metadata.namespace
|
61
|
+
# `max_retries` - how many times to retry in case of network failures
|
62
|
+
def merge_all_by_event(name, event_names, namespace: nil, max_retries: 0)
|
60
63
|
retrying_on_network_failures(max_retries) do
|
61
|
-
ProjectionManager.new(self).merge_all_by_event(name, event_names)
|
64
|
+
ProjectionManager.new(self).merge_all_by_event(name, event_names, namespace: namespace)
|
62
65
|
end
|
63
66
|
end
|
64
67
|
|
@@ -70,9 +73,9 @@ module Akasha
|
|
70
73
|
end
|
71
74
|
|
72
75
|
# Updates stream metadata.
|
73
|
-
def retry_write_metadata(stream_name, metadata)
|
76
|
+
def retry_write_metadata(stream_name, metadata, max_retries: 0)
|
74
77
|
event = Akasha::Event.new(:stream_metadata_changed, SecureRandom.uuid, metadata)
|
75
|
-
retry_append_to_stream("#{stream_name}/metadata", [event])
|
78
|
+
retry_append_to_stream("#{stream_name}/metadata", [event], max_retries: max_retries)
|
76
79
|
end
|
77
80
|
|
78
81
|
# Issues a generic request against the API.
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Akasha
|
2
|
+
module Storage
|
3
|
+
class HttpEventStore
|
4
|
+
# Base class for all HTTP Event store errors.
|
5
|
+
Error = Class.new(RuntimeError)
|
6
|
+
|
7
|
+
# Stream name contains invalid characters.
|
8
|
+
InvalidStreamNameError = Class.new(Error)
|
9
|
+
|
10
|
+
# Base class for HTTP errors.
|
11
|
+
class HttpError < Error
|
12
|
+
attr_reader :status_code
|
13
|
+
|
14
|
+
def initialize(status_code)
|
15
|
+
@status_code = status_code
|
16
|
+
super("Unexpected HTTP response: #{@status_code}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# 4xx HTTP status code.
|
21
|
+
HttpClientError = Class.new(HttpError)
|
22
|
+
|
23
|
+
# 5xx HTTP status code.
|
24
|
+
HttpServerError = Class.new(HttpError)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -14,32 +14,54 @@ module Akasha
|
|
14
14
|
# Arguments:
|
15
15
|
# `name` - name of the projection stream
|
16
16
|
# `event_names` - array of event names
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
# `namespace` - optional namespace; if provided, the resulting stream will
|
18
|
+
# only contain events with the same metadata.namespace
|
19
|
+
def merge_all_by_event(name, event_names, namespace: nil)
|
20
|
+
attempt_create_projection(name, event_names, namespace) ||
|
21
|
+
update_projection(name, event_names, namespace)
|
20
22
|
end
|
21
23
|
|
22
24
|
private
|
23
25
|
|
24
|
-
|
25
|
-
|
26
|
+
# rubocop:disable Metrics/MethodLength
|
27
|
+
def projection_javascript(name, events, namespace)
|
28
|
+
callback_fmt = if namespace.nil?
|
29
|
+
<<~JS
|
30
|
+
'%{en}': function(s, e) {
|
31
|
+
linkTo('%{name}', e)
|
32
|
+
}
|
33
|
+
JS
|
34
|
+
else
|
35
|
+
<<~JS
|
36
|
+
'%{en}': function(s, e) {
|
37
|
+
if (e['metadata'] !== null && e['metadata']['namespace'] === '%{namespace}') {
|
38
|
+
linkTo('%{name}', e)
|
39
|
+
}
|
40
|
+
}
|
41
|
+
JS
|
42
|
+
end
|
43
|
+
callbacks = events.map { |en| format(callback_fmt, en: en, name: name, namespace: namespace) }
|
44
|
+
|
26
45
|
# Alternative code using internal indexing.
|
27
46
|
# It's broken though because it reorders events for aggregates (because the streams
|
28
47
|
# it uses are per-event). An alternative would be to use aggregates as streams
|
29
48
|
# to pull from.
|
30
49
|
# et_streams = events.map { |en| "\"$et-#{en}\"" }
|
31
50
|
# "fromStreams([#{et_streams.join(', ')}]).when({ #{callbacks.join(', ')} });"
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
51
|
+
<<~JS
|
52
|
+
// This is hard to find, so I'm leaving it here:
|
53
|
+
// options({
|
54
|
+
// reorderEvents: true,
|
55
|
+
// processingLag: 100 //time in ms
|
56
|
+
// });
|
57
|
+
fromAll().when({
|
58
|
+
#{callbacks.join(', ')}
|
59
|
+
});
|
60
|
+
JS
|
40
61
|
end
|
62
|
+
# rubocop:enable Metrics/MethodLength
|
41
63
|
|
42
|
-
def attempt_create_projection(name, event_names)
|
64
|
+
def attempt_create_projection(name, event_names, namespace)
|
43
65
|
create_options = {
|
44
66
|
name: name,
|
45
67
|
emit: :yes,
|
@@ -48,7 +70,7 @@ module Akasha
|
|
48
70
|
}
|
49
71
|
query_string = Rack::Utils.build_query(create_options)
|
50
72
|
@client.request(:post, "/projections/continuous?#{query_string}",
|
51
|
-
projection_javascript(name, event_names),
|
73
|
+
projection_javascript(name, event_names, namespace),
|
52
74
|
'Content-Type' => 'application/javascript')
|
53
75
|
true
|
54
76
|
rescue HttpClientError => e
|
@@ -56,9 +78,9 @@ module Akasha
|
|
56
78
|
raise
|
57
79
|
end
|
58
80
|
|
59
|
-
def update_projection(name, event_names)
|
81
|
+
def update_projection(name, event_names, namespace)
|
60
82
|
@client.request(:put, "/projection/#{name}/query?emit=yet",
|
61
|
-
projection_javascript(name, event_names),
|
83
|
+
projection_javascript(name, event_names, namespace),
|
62
84
|
'Content-Type' => 'application/javascript')
|
63
85
|
end
|
64
86
|
end
|
@@ -5,15 +5,20 @@ module Akasha
|
|
5
5
|
class Stream
|
6
6
|
attr_reader :name
|
7
7
|
|
8
|
-
|
8
|
+
# Create a stream object for accessing a ES stream.
|
9
|
+
# Does not create the underlying stream itself.
|
10
|
+
# Use the `max_retries` option to choose how many times to retry in case
|
11
|
+
def initialize(client, stream_name, max_retries: 0)
|
9
12
|
@client = client
|
10
13
|
@name = stream_name
|
14
|
+
@max_retries = max_retries
|
11
15
|
end
|
12
16
|
|
13
|
-
# Appends events to the stream.
|
17
|
+
# Appends `events` to the stream.
|
18
|
+
# of network failures.
|
14
19
|
def write_events(events)
|
15
20
|
return if events.empty?
|
16
|
-
@client.retry_append_to_stream(@name, events)
|
21
|
+
@client.retry_append_to_stream(@name, events, max_retries: @max_retries)
|
17
22
|
end
|
18
23
|
|
19
24
|
# Reads events from the stream starting from `start` inclusive.
|
@@ -31,18 +36,18 @@ module Akasha
|
|
31
36
|
position += events.size
|
32
37
|
end
|
33
38
|
else
|
34
|
-
@client.retry_read_events_forward(@name, start, page_size, poll)
|
39
|
+
@client.retry_read_events_forward(@name, start, page_size, poll, max_retries: @max_retries)
|
35
40
|
end
|
36
41
|
end
|
37
42
|
|
38
43
|
# Reads stream metadata.
|
39
44
|
def metadata
|
40
|
-
@client.retry_read_metadata(@name)
|
45
|
+
@client.retry_read_metadata(@name, max_retries: @max_retries)
|
41
46
|
end
|
42
47
|
|
43
48
|
# Updates stream metadata.
|
44
49
|
def metadata=(metadata)
|
45
|
-
@client.retry_write_metadata(@name, metadata)
|
50
|
+
@client.retry_write_metadata(@name, metadata, max_retries: @max_retries)
|
46
51
|
end
|
47
52
|
end
|
48
53
|
end
|
@@ -26,9 +26,14 @@ module Akasha
|
|
26
26
|
# Arguments:
|
27
27
|
# `new_stream_name` - name of the new stream
|
28
28
|
# `only` - array of event names
|
29
|
-
|
29
|
+
# `namespace` - optional namespace; if provided, the resulting stream will
|
30
|
+
# only contain events with the same metadata.namespace
|
31
|
+
def merge_all_by_event(into:, only:, namespace: nil)
|
30
32
|
new_stream = Stream.new do |new_events|
|
31
|
-
new_events.select
|
33
|
+
new_events.select do |event|
|
34
|
+
(namespace.nil? || namespace == event.metadata[:namespace]) &&
|
35
|
+
only.include?(event.name)
|
36
|
+
end
|
32
37
|
end
|
33
38
|
@streams[into] = new_stream
|
34
39
|
@projections << new_stream
|
data/lib/akasha/version.rb
CHANGED
metadata
CHANGED
@@ -1,57 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: akasha
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0.pre.edge.pre.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marcin Bilski
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-07-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: corefines
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '1.11'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '1.11'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: faraday
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
33
|
+
version: '0.15'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
40
|
+
version: '0.15'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: faraday_middleware
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '0'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rack
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,19 +81,19 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0.0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: typhoeus
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '1.
|
89
|
+
version: '1.3'
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '1.
|
96
|
+
version: '1.3'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: bundler
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -235,6 +235,7 @@ files:
|
|
235
235
|
- lib/akasha/storage/http_event_store.rb
|
236
236
|
- lib/akasha/storage/http_event_store/client.rb
|
237
237
|
- lib/akasha/storage/http_event_store/event_serializer.rb
|
238
|
+
- lib/akasha/storage/http_event_store/exceptions.rb
|
238
239
|
- lib/akasha/storage/http_event_store/projection_manager.rb
|
239
240
|
- lib/akasha/storage/http_event_store/response_handler.rb
|
240
241
|
- lib/akasha/storage/http_event_store/stream.rb
|
@@ -256,9 +257,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
256
257
|
version: '0'
|
257
258
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
258
259
|
requirements:
|
259
|
-
- - "
|
260
|
+
- - ">"
|
260
261
|
- !ruby/object:Gem::Version
|
261
|
-
version:
|
262
|
+
version: 1.3.1
|
262
263
|
requirements: []
|
263
264
|
rubyforge_project:
|
264
265
|
rubygems_version: 2.6.11
|