active_projection 0.5.2 → 0.5.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0250b8914b32ef0061eaf8a9f32683d12182e536
4
- data.tar.gz: 21ed583c9e15ae256996bacd4230c9ae4b3f5d15
3
+ metadata.gz: b77647433fbea824ae8677e63891935e731f517c
4
+ data.tar.gz: 739f2e01e001a89ec1ecffbd96be77c9067b45ec
5
5
  SHA512:
6
- metadata.gz: 7b107890a963279d3d1884a5b0bbe109240dc1f1fcda356cd13d401e311beacb62c41cd1e5ae4914e12fc6f127d6e3c8fe59ecf762700f6dacf961bf17016c09
7
- data.tar.gz: a6b4677d68994653d8ba0839e59e315dcfd660f70cecb2619ed7ee418ffdfd0e732302082923b49b44f6baab12da253bcdda07103a4293bd43b932283ed4604c
6
+ metadata.gz: 4f3712e937fc43d98700dd7e14fd713b34c2e27081de26dbb16e0a9818c1f2341ebe86275d0c7d3be2a1602e6d210c126845f70d0d931fd8a385acd1fd764de1
7
+ data.tar.gz: e64c810df7a0c3668b6000e61c385ba465dd94241540aed0f42cb108a3bf0cfe70ffcccd55db95fd353857cd058a4c18477ef676918870adcd758590a489a7fc
@@ -1,20 +1,20 @@
1
- Copyright (c) 2013 Andreas Reischuck
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
1
+ Copyright (c) 2013 Andreas Reischuck
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
- # Active Projection
2
-
3
- Contains everything necessary to build and run projection servers for Rails Disco.
4
-
5
- Projections process events into database models that are optimized for the Rails views.
6
-
7
- Have a look at the [rails-disco](https://github.com/hicknhack-software/rails-disco/wiki) documentation on Github for more details.
1
+ # Active Projection
2
+
3
+ Contains everything necessary to build and run projection servers for Rails Disco.
4
+
5
+ Projections process events into database models that are optimized for the Rails views.
6
+
7
+ Have a look at the [rails-disco](https://github.com/hicknhack-software/rails-disco/wiki) documentation on Github for more details.
@@ -8,7 +8,7 @@ module ActiveProjection
8
8
  projections.values.map(&:last_id)
9
9
  end
10
10
 
11
- def self.get_last_id(id)
11
+ def self.last_id(id)
12
12
  projections[id].last_id
13
13
  end
14
14
 
@@ -16,11 +16,11 @@ module ActiveProjection
16
16
  projections[id].update! last_id: last_id
17
17
  end
18
18
 
19
- def self.set_broken(id)
19
+ def self.mark_broken(id)
20
20
  projections[id].update! solid: false
21
21
  end
22
22
 
23
- def self.get_all_broken
23
+ def self.all_broken
24
24
  projections.values.reject(&:solid).map(&:class_name)
25
25
  end
26
26
 
@@ -45,7 +45,7 @@ module ActiveProjection
45
45
 
46
46
  private
47
47
 
48
- def self.initialize
48
+ def initialize
49
49
  end
50
50
 
51
51
  cattr_accessor :projections do
@@ -1,5 +1,5 @@
1
- module ActiveProjection
2
- class Projection < ActiveRecord::Base
3
- self.table_name = 'projections'
4
- end
5
- end
1
+ module ActiveProjection
2
+ class Projection < ActiveRecord::Base
3
+ self.table_name = 'projections'
4
+ end
5
+ end
@@ -1,45 +1,45 @@
1
- module ActiveProjection
2
- class ProjectionRepository
3
- def self.last_ids
4
- Projection.all.to_a.map { |p| p.last_id }
5
- end
6
-
7
- def self.get_last_id(id)
8
- Projection.find(id).last_id
9
- end
10
-
11
- def self.set_last_id(id, last_id)
12
- Projection.find(id).update! last_id: last_id
13
- end
14
-
15
- def self.set_broken(id)
16
- Projection.find(id).update! solid: false
17
- end
18
-
19
- def self.get_all_broken
20
- Projection.where(solid: false).to_a.map { |p| p.class_name }
21
- end
22
-
23
- def self.solid?(id)
24
- Projection.find(id).solid
25
- end
26
-
27
- def self.ensure_exists(projection_class)
28
- Projection.transaction do
29
- Projection.find_or_create_by! class_name: projection_class
30
- end
31
- end
32
-
33
- def self.ensure_solid(projection_class)
34
- Projection.transaction do
35
- projection = Projection.find_or_initialize_by class_name: projection_class
36
- projection.update! solid: true
37
- end
38
- end
39
-
40
- private
41
-
42
- def self.initialize
43
- end
44
- end
45
- end
1
+ module ActiveProjection
2
+ class ProjectionRepository
3
+ def self.last_ids
4
+ Projection.all.to_a.map { |p| p.last_id }
5
+ end
6
+
7
+ def self.last_id(id)
8
+ Projection.find(id).last_id
9
+ end
10
+
11
+ def self.set_last_id(id, last_id)
12
+ Projection.find(id).update! last_id: last_id
13
+ end
14
+
15
+ def self.mark_broken(id)
16
+ Projection.find(id).update! solid: false
17
+ end
18
+
19
+ def self.all_broken
20
+ Projection.where(solid: false).to_a.map { |p| p.class_name }
21
+ end
22
+
23
+ def self.solid?(id)
24
+ Projection.find(id).solid
25
+ end
26
+
27
+ def self.ensure_exists(projection_class)
28
+ Projection.transaction do
29
+ Projection.find_or_create_by! class_name: projection_class
30
+ end
31
+ end
32
+
33
+ def self.ensure_solid(projection_class)
34
+ Projection.transaction do
35
+ projection = Projection.find_or_initialize_by class_name: projection_class
36
+ projection.update! solid: true
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def initialize
43
+ end
44
+ end
45
+ end
@@ -1,10 +1,10 @@
1
- class CreateProjections < ActiveRecord::Migration
2
- def change
3
- create_table :projections do |t|
4
- t.string :class_name
5
- t.integer :last_id, default: 0
6
- t.boolean :solid, default: true
7
- end
8
- add_index :projections, :class_name
9
- end
10
- end
1
+ class CreateProjections < ActiveRecord::Migration
2
+ def change
3
+ create_table :projections do |t|
4
+ t.string :class_name
5
+ t.integer :last_id, default: 0
6
+ t.boolean :solid, default: true
7
+ end
8
+ add_index :projections, :class_name
9
+ end
10
+ end
@@ -1,17 +1,17 @@
1
- require 'active_event'
2
- require 'active_support/core_ext'
3
- require 'active_projection/version'
4
-
5
- module ActiveProjection
6
- extend ActiveSupport::Autoload
7
-
8
- autoload :Autoload
9
- autoload :EventClient
10
- autoload :ProjectionType
11
- autoload :ProjectionTypeRegistry
12
- autoload :Server
13
-
14
- autoload :Projection, (File.expand_path '../../app/models/active_projection/projection', __FILE__)
15
- autoload :ProjectionRepository, (File.expand_path '../../app/models/active_projection/projection_repository', __FILE__)
16
- autoload :CachedProjectionRepository, (File.expand_path '../../app/models/active_projection/cached_projection_repository', __FILE__)
17
- end
1
+ require 'active_event'
2
+ require 'active_support/core_ext'
3
+ require 'active_projection/version'
4
+
5
+ module ActiveProjection
6
+ extend ActiveSupport::Autoload
7
+
8
+ autoload :Autoload
9
+ autoload :EventClient
10
+ autoload :ProjectionType
11
+ autoload :ProjectionTypeRegistry
12
+ autoload :Server
13
+
14
+ autoload :Projection, (File.expand_path '../../app/models/active_projection/projection', __FILE__)
15
+ autoload :ProjectionRepository, (File.expand_path '../../app/models/active_projection/projection_repository', __FILE__)
16
+ autoload :CachedProjectionRepository, (File.expand_path '../../app/models/active_projection/cached_projection_repository', __FILE__)
17
+ end
@@ -1,9 +1,11 @@
1
- module ActiveProjection
2
- module Autoload
3
- include ActiveEvent::Support::Autoload
4
- private
5
- def self.dir_names
6
- %W(app/models app/projections)
7
- end
8
- end
9
- end
1
+ module ActiveProjection
2
+ module Autoload
3
+ include ActiveEvent::Support::Autoload
4
+
5
+ private
6
+
7
+ def self.dir_names
8
+ %w(app/models app/projections)
9
+ end
10
+ end
11
+ end
@@ -1,171 +1,171 @@
1
- require 'singleton'
2
- require 'bunny'
3
-
4
- module ActiveProjection
5
- class EventClient
6
- include Singleton
7
-
8
- def self.start(options)
9
- instance.configure(options).start
10
- end
11
-
12
- def configure(options)
13
- raise 'Unsupported! Cannot configure running client' if running
14
- self.options = options
15
- self
16
- end
17
-
18
- def start
19
- run_once do
20
- prepare
21
- sync_projections
22
- listen_for_events
23
- listen_for_replayed_events
24
- request_missing_events
25
- event_channel.work_pool.join
26
- end
27
- rescue Interrupt
28
- LOGGER.info 'Catching Interrupt'
29
- rescue Exception => e
30
- LOGGER.error e.message
31
- LOGGER.error e.backtrace.join("\n")
32
- raise
33
- end
34
-
35
- private
36
-
37
- attr_accessor :options
38
- attr_accessor :running # true once start was called
39
- attr_accessor :current # true after missing events are processed
40
- attr_accessor :delay_queue # stores events while processing missing events
41
-
42
- def run_once
43
- raise 'Unsupported! Connot start a running client' if running
44
- self.running = true
45
- yield
46
- ensure
47
- self.running = false
48
- end
49
-
50
- def prepare
51
- self.delay_queue = []
52
- init_database_connection
53
- event_connection.start
54
- end
55
-
56
- def init_database_connection
57
- ActiveRecord::Base.establish_connection options[:projection_database]
58
- end
59
-
60
- def sync_projections
61
- server_projections.each do |projection|
62
- CachedProjectionRepository.ensure_solid(projection.class.name)
63
- end
64
- end
65
-
66
- def server_projections
67
- @server_projections ||= ProjectionTypeRegistry.projections.drop(WORKER_NUMBER).each_slice(WORKER_COUNT).map(&:first)
68
- end
69
-
70
- def listen_for_events
71
- event_queue.subscribe do |delivery_info, properties, body|
72
- if current
73
- event_received properties, body
74
- else
75
- delay_queue << [properties, body]
76
- end
77
- end
78
- end
79
-
80
- def listen_for_replayed_events
81
- replay_queue.subscribe do |delivery_info, properties, body|
82
- if 'replay_done' == body
83
- replay_done
84
- else
85
- event_received properties, body
86
- end
87
- end
88
- end
89
-
90
- def request_missing_events
91
- send_request_for(CachedProjectionRepository.last_ids.min || 0)
92
- end
93
-
94
- def send_projection_notification(event_id, projection, error = nil)
95
- message = {event: event_id, projection: projection.class.name}
96
- message.merge! error: "#{error.class.name}: #{error.message}", backtrace: error.backtrace if error
97
- server_side_events_exchange.publish message.to_json
98
- end
99
-
100
- def send_request_for(id)
101
- resend_request_exchange.publish id.to_s, routing_key: 'resend_request'
102
- end
103
-
104
- def replay_done
105
- LOGGER.debug 'All replayed events received'
106
- broken_projections = CachedProjectionRepository.get_all_broken
107
- LOGGER.error "These projections are still broken: #{broken_projections.join(", ")}" unless broken_projections.empty?
108
- replay_queue.unbind(resend_exchange)
109
- self.current = true
110
- flush_delay_queue
111
- end
112
-
113
- def flush_delay_queue
114
- delay_queue.each { |properties, body| event_received properties, body }
115
- self.delay_queue = []
116
- end
117
-
118
- def event_received(properties, body)
119
- RELOADER.execute_if_updated
120
- LOGGER.debug "Received #{properties.type} with #{body}"
121
- headers = properties.headers.deep_symbolize_keys!
122
- event = ActiveEvent::EventType.create_instance properties.type, JSON.parse(body).deep_symbolize_keys!
123
- process_event headers, event
124
- end
125
-
126
- def process_event(headers, event)
127
- server_projections.select { |p| p.evaluate headers }.each do |projection|
128
- begin
129
- ActiveRecord::Base.transaction do
130
- projection.invoke event, headers
131
- end
132
- send_projection_notification headers[:id], projection
133
- rescue Exception => e
134
- send_projection_notification headers[:id], projection, e
135
- end
136
- end
137
- end
138
-
139
- def replay_queue
140
- @replay_queue ||= event_channel.queue('', auto_delete: true).bind(resend_exchange)
141
- end
142
-
143
- def event_queue
144
- @event_queue ||= event_channel.queue('', auto_delete: true).bind(event_exchange)
145
- end
146
-
147
- def event_connection
148
- @event_server ||= Bunny.new URI::Generic.build(options[:event_connection]).to_s
149
- end
150
-
151
- def event_channel
152
- @event_channel ||= event_connection.create_channel
153
- end
154
-
155
- def event_exchange
156
- @event_exchange ||= event_channel.fanout options[:event_exchange]
157
- end
158
-
159
- def resend_exchange
160
- @resend_exchange ||= event_channel.fanout "resend_#{options[:event_exchange]}"
161
- end
162
-
163
- def resend_request_exchange
164
- @resend_request_exchange ||= event_channel.direct "resend_request_#{options[:event_exchange]}"
165
- end
166
-
167
- def server_side_events_exchange
168
- @server_side_events_exchange ||= event_channel.fanout "server_side_#{options[:event_exchange]}"
169
- end
170
- end
171
- end
1
+ require 'singleton'
2
+ require 'bunny'
3
+
4
+ module ActiveProjection
5
+ class EventClient
6
+ include Singleton
7
+
8
+ def self.start(options)
9
+ instance.configure(options).start
10
+ end
11
+
12
+ def configure(options)
13
+ fail 'Unsupported! Cannot configure running client' if running
14
+ self.options = options
15
+ self
16
+ end
17
+
18
+ def start
19
+ run_once do
20
+ prepare
21
+ sync_projections
22
+ listen_for_events
23
+ listen_for_replayed_events
24
+ request_missing_events
25
+ event_channel.work_pool.join
26
+ end
27
+ rescue Interrupt
28
+ LOGGER.info 'Catching Interrupt'
29
+ rescue => e
30
+ LOGGER.error e.message
31
+ LOGGER.error e.backtrace.join("\n")
32
+ raise
33
+ end
34
+
35
+ private
36
+
37
+ attr_accessor :options
38
+ attr_accessor :running # true once start was called
39
+ attr_accessor :current # true after missing events are processed
40
+ attr_accessor :delay_queue # stores events while processing missing events
41
+
42
+ def run_once
43
+ fail 'Unsupported! Connot start a running client' if running
44
+ self.running = true
45
+ yield
46
+ ensure
47
+ self.running = false
48
+ end
49
+
50
+ def prepare
51
+ self.delay_queue = []
52
+ init_database_connection
53
+ event_connection.start
54
+ end
55
+
56
+ def init_database_connection
57
+ ActiveRecord::Base.establish_connection options[:projection_database]
58
+ end
59
+
60
+ def sync_projections
61
+ server_projections.each do |projection|
62
+ CachedProjectionRepository.ensure_solid(projection.class.name)
63
+ end
64
+ end
65
+
66
+ def server_projections
67
+ @server_projections ||= ProjectionTypeRegistry.projections.drop(WORKER_NUMBER).each_slice(WORKER_COUNT).map(&:first)
68
+ end
69
+
70
+ def listen_for_events
71
+ event_queue.subscribe do |_delivery_info, properties, body|
72
+ if current
73
+ event_received properties, body
74
+ else
75
+ delay_queue << [properties, body]
76
+ end
77
+ end
78
+ end
79
+
80
+ def listen_for_replayed_events
81
+ replay_queue.subscribe do |_delivery_info, properties, body|
82
+ if 'replay_done' == body
83
+ replay_done
84
+ else
85
+ event_received properties, body
86
+ end
87
+ end
88
+ end
89
+
90
+ def request_missing_events
91
+ send_request_for(CachedProjectionRepository.last_ids.min || 0)
92
+ end
93
+
94
+ def send_projection_notification(event_id, projection, error = nil)
95
+ message = {event: event_id, projection: projection.class.name}
96
+ message.merge! error: "#{error.class.name}: #{error.message}", backtrace: error.backtrace if error
97
+ server_side_events_exchange.publish message.to_json
98
+ end
99
+
100
+ def send_request_for(id)
101
+ resend_request_exchange.publish id.to_s, routing_key: 'resend_request'
102
+ end
103
+
104
+ def replay_done
105
+ LOGGER.debug 'All replayed events received'
106
+ broken_projections = CachedProjectionRepository.all_broken
107
+ LOGGER.error "These projections are still broken: #{broken_projections.join(', ')}" unless broken_projections.empty?
108
+ replay_queue.unbind(resend_exchange)
109
+ self.current = true
110
+ flush_delay_queue
111
+ end
112
+
113
+ def flush_delay_queue
114
+ delay_queue.each { |properties, body| event_received properties, body }
115
+ self.delay_queue = []
116
+ end
117
+
118
+ def event_received(properties, body)
119
+ RELOADER.execute_if_updated
120
+ LOGGER.debug "Received #{properties.type} with #{body}"
121
+ headers = properties.headers.deep_symbolize_keys!
122
+ event = ActiveEvent::EventType.create_instance properties.type, JSON.parse(body).deep_symbolize_keys!
123
+ process_event headers, event
124
+ end
125
+
126
+ def process_event(headers, event)
127
+ server_projections.select { |p| p.evaluate headers }.each do |projection|
128
+ begin
129
+ ActiveRecord::Base.transaction do
130
+ projection.invoke event, headers
131
+ end
132
+ send_projection_notification headers[:id], projection
133
+ rescue => e
134
+ send_projection_notification headers[:id], projection, e
135
+ end
136
+ end
137
+ end
138
+
139
+ def replay_queue
140
+ @replay_queue ||= event_channel.queue('', auto_delete: true).bind(resend_exchange)
141
+ end
142
+
143
+ def event_queue
144
+ @event_queue ||= event_channel.queue('', auto_delete: true).bind(event_exchange)
145
+ end
146
+
147
+ def event_connection
148
+ @event_server ||= Bunny.new URI::Generic.build(options[:event_connection]).to_s
149
+ end
150
+
151
+ def event_channel
152
+ @event_channel ||= event_connection.create_channel
153
+ end
154
+
155
+ def event_exchange
156
+ @event_exchange ||= event_channel.fanout options[:event_exchange]
157
+ end
158
+
159
+ def resend_exchange
160
+ @resend_exchange ||= event_channel.fanout "resend_#{options[:event_exchange]}"
161
+ end
162
+
163
+ def resend_request_exchange
164
+ @resend_request_exchange ||= event_channel.direct "resend_request_#{options[:event_exchange]}"
165
+ end
166
+
167
+ def server_side_events_exchange
168
+ @server_side_events_exchange ||= event_channel.fanout "server_side_#{options[:event_exchange]}"
169
+ end
170
+ end
171
+ end