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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4078cc01d0c132a3b675ed21de42246880643c49
4
- data.tar.gz: 0562b5f9607dea81f47720c680d3f5517c5190e0
3
+ metadata.gz: 4ef5ba87a94db893bc51fa6e9473ce73007ef1cd
4
+ data.tar.gz: 8c83ff0c79d88bd3dd939c11ed7bde46ad1be4de
5
5
  SHA512:
6
- metadata.gz: 114a107b63f0f25d9e21b5a00c8a4277885ca705ff7d324199a89e54004c41f410380608d7ba994e8d2f0e3dab220c8a09443c56ef36bc69c551f061d23b3366
7
- data.tar.gz: 4ef66f8424b38a10ac6b8b4095e9a42f985babf00966d5e7d8928cc8fd588768cf1ee45baa516fe094a8b717ee560d44e5f5856028aa6af97eee806b88c3c1ca
6
+ metadata.gz: 4a787f8c35533063aeacb5071e1dfa6db0b79a892816a3c98d83eb99da9954f0b2c2138df325d1a0c0c37aed53cefed7fb0ba39c71ae38a8e000b89bba1a03db
7
+ data.tar.gz: 31e9a1d1fa1abc7833a4302955554e5b4b34f3555f16118fca09b450fb187abe85dd845ced94ec361b634ddaca1ff955ab1997d1c0e3e2da2e9624b14dca1595
data/.gitignore CHANGED
@@ -1 +1,3 @@
1
1
  /pkg
2
+ /.bundle
3
+ /vendor
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
- - docker
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
- - gem update --system
19
- - gem update bundler
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
- - rvm: 2.3.6
25
- - rvm: 2.5.1
26
- - env: INTEGRATION_TESTS=true
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
- # Run either integration tests or specs.
32
- case "$INTEGRATION_TESTS" in
33
- true) bin/integration-tests.sh ;;
34
- *) bundle exec rspec --tag ~integration ;;
35
- esac
36
- # Check if conventions are being followed.
37
- - bundle exec rake rubocop
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
- disabled: true
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.3.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.3.0)
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.0)
30
- parser (2.4.0.0)
31
- ast (~> 2.2)
32
- powerpack (0.1.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 (2.2.2)
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.50.0)
59
+ rubocop (0.57.2)
60
+ jaro_winkler (~> 1.5.1)
60
61
  parallel (~> 1.10)
61
- parser (>= 2.3.3.1, < 3.0)
62
+ parser (>= 2.5)
62
63
  powerpack (~> 0.1)
63
- rainbow (>= 2.2.2, < 3.0)
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.3.0)
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 intetrface for Client -- use Event.
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] Do we need EventListener class? Yes.
47
- - [x] Assymetry between data and metadata.
48
- - [x] Faster shutdown.
49
- - [ ] Namespacing for events and aggregates and the projection
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 'corefines', '~>1.11'
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
@@ -57,7 +57,7 @@ class MyAkashaApp
57
57
  username: 'admin',
58
58
  password: 'changeit'
59
59
  ),
60
- namespace: :my_app
60
+ namespace: :'sinatra.example.akasha.bilus.io'
61
61
  )
62
62
  Akasha::Aggregate.connect!(repository)
63
63
 
@@ -29,7 +29,8 @@ module Akasha
29
29
  # Used by Repository.
30
30
  def apply_events(events)
31
31
  events.each do |event|
32
- send(event_handler(event), event.data)
32
+ method_name = event_handler(event)
33
+ send(method_name, event.data) if respond_to?(method_name)
33
34
  end
34
35
  end
35
36
 
@@ -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: DEFAULT_PROJECTION_STREAM,
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, only: registered_event_names)
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
@@ -20,5 +20,9 @@ module Akasha
20
20
  data == other.data &&
21
21
  metadata == other.metadata
22
22
  end
23
+
24
+ def with_metadata(metadata)
25
+ Event.new(@name, @id, @metadata.merge(metadata), **@data)
26
+ end
23
27
  end
24
28
  end
@@ -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
- @routes.merge!(routes)
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
@@ -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
- stream(aggregate.class, changeset.aggregate_id).write_events(changeset.events)
35
- notify_subscribers(aggregate)
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
- "#{aggregate_klass}#{STREAM_NAME_SEP}#{aggregate_id}"
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(aggregate)
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(id, event)
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
- # Base class for all HTTP Event store errors.
9
- Error = Class.new(RuntimeError)
10
- # Stream name contains invalid characters.
11
- InvalidStreamNameError = Class.new(Error)
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
- def merge_all_by_event(into:, only:)
54
- @client.merge_all_by_event(into, only)
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
- def merge_all_by_event(name, event_names, max_retries: 0)
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
- def merge_all_by_event(name, event_names)
18
- attempt_create_projection(name, event_names) ||
19
- update_projection(name, event_names)
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
- def projection_javascript(name, events)
25
- callbacks = events.map { |en| "\"#{en}\": function(s,e) { linkTo('#{name}', e) }" }
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
- // This is hard to find, so I'm leaving it here:
34
- // options({
35
- // reorderEvents: true,
36
- // processingLag: 100 //time in ms
37
- // });
38
- fromAll().when({ #{callbacks.join(', ')} });
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
- def initialize(client, stream_name)
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
- def merge_all_by_event(into:, only:)
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 { |event| only.include?(event.name) }
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
@@ -1,3 +1,3 @@
1
1
  module Akasha
2
- VERSION = '0.3.0'.freeze
2
+ VERSION = '0.4.0-edge-5'.freeze
3
3
  end
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.3.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-06-18 00:00:00.000000000 Z
11
+ date: 2018-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: faraday
14
+ name: corefines
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.15'
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: '0.15'
26
+ version: '1.11'
27
27
  - !ruby/object:Gem::Dependency
28
- name: faraday_middleware
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: typhoeus
42
+ name: faraday_middleware
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '1.3'
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: '1.3'
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: corefines
84
+ name: typhoeus
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '1.11'
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.11'
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: '0'
262
+ version: 1.3.1
262
263
  requirements: []
263
264
  rubyforge_project:
264
265
  rubygems_version: 2.6.11