akasha 0.3.0 → 0.4.0.pre.edge.pre.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|