read_model-projection 0.3.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d5346a6345c8ec9a3961a493d07898cf206308d3
4
+ data.tar.gz: 0d61e85492220e49111c1f407d6c30ab0e40d036
5
+ SHA512:
6
+ metadata.gz: 665bea13fd5cf79dc05edaac91c1276db854297942e9f5b432d2d3adbbef37679e54627c8c7b255547769b518310b583f736c8034f27acad4375dccda28eb53b
7
+ data.tar.gz: 26424909dd9aaf252bd590ab743232966013620c6708cc9d59626a021a575d2b0f5dc5ff77981a0f4537d368a9cb3adfcbc263076148a8d014d192a7d4139c44
@@ -0,0 +1,14 @@
1
+ require 'consumer'
2
+ require 'entity_cache'
3
+ require 'entity_projection'
4
+ require 'messaging'
5
+
6
+ require 'postgresql/connector'
7
+ require 'read_model/entity/sequel'
8
+
9
+ require 'read_model/projection/log'
10
+
11
+ require 'read_model/projection/connection'
12
+ require 'read_model/projection/position/setup_schema'
13
+ require 'read_model/projection/position/store'
14
+ require 'read_model/projection/project'
@@ -0,0 +1,5 @@
1
+ module ReadModel
2
+ module Projection
3
+ Connection = ReadModel::Entity::Sequel::Connection
4
+ end
5
+ end
@@ -0,0 +1,64 @@
1
+ require 'event_source/event_store/http'
2
+
3
+ module ReadModel
4
+ module Projection
5
+ module Consumer
6
+ class EventStore
7
+ include ::Consumer
8
+
9
+ attr_accessor :entity_class
10
+ attr_accessor :projection_class
11
+ attr_accessor :projection_name
12
+ attr_accessor :stream_name
13
+
14
+ dependency :project, Project
15
+
16
+ handle do |event_data|
17
+ project.(event_data)
18
+ end
19
+
20
+ def self.build(stream_name, entity_class:, projection_class:, projection_name:, position_store: nil, batch_size: nil, session: nil)
21
+ stream = EventSource::Stream.build stream_name
22
+
23
+ instance = new stream
24
+
25
+ instance.entity_class = entity_class
26
+ instance.position_update_interval = 1
27
+ instance.projection_class = projection_class
28
+ instance.projection_name = projection_name
29
+
30
+ instance.configure(
31
+ session: session,
32
+ batch_size: batch_size,
33
+ position_store: position_store
34
+ )
35
+
36
+ instance
37
+ end
38
+
39
+ def configure(session: nil, batch_size: nil, position_store: nil)
40
+ Position::Store.configure(
41
+ self,
42
+ projection_name,
43
+ stream.name,
44
+ position_store: position_store
45
+ )
46
+
47
+ get = EventSource::EventStore::HTTP::Get.configure(
48
+ self,
49
+ batch_size: batch_size,
50
+ session: session
51
+ )
52
+
53
+ Project.configure self, entity_class, projection_class, get: get
54
+ end
55
+
56
+ def call(*)
57
+ entity_class.db.transaction do
58
+ super
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,14 @@
1
+ require 'messaging/controls'
2
+ require 'read_model/entity/sequel/controls'
3
+
4
+ require 'read_model/projection/controls/message'
5
+
6
+ require 'read_model/projection/controls/category'
7
+ require 'read_model/projection/controls/entity'
8
+ require 'read_model/projection/controls/entity/projection'
9
+ require 'read_model/projection/controls/event_data'
10
+ require 'read_model/projection/controls/name'
11
+ require 'read_model/projection/controls/position_store/fails'
12
+ require 'read_model/projection/controls/project'
13
+ require 'read_model/projection/controls/setup_schema'
14
+ require 'read_model/projection/controls/stream_name'
@@ -0,0 +1,7 @@
1
+ module ReadModel
2
+ module Projection
3
+ module Controls
4
+ Category = EventSource::Controls::Category
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ module ReadModel
2
+ module Projection
3
+ module Controls
4
+ module Entity
5
+ def self.example(entity_id: nil)
6
+ entity_id ||= Identifier::UUID::Random.get
7
+
8
+ entity = Example.new
9
+ entity.id = entity_id
10
+ entity.some_attribute = SecureRandom.hex 7
11
+ entity
12
+ end
13
+
14
+ Example = ReadModel::Entity::Sequel::Controls::Entity::Example
15
+
16
+ Association = ReadModel::Entity::Sequel::Controls::Entity::Association
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ module ReadModel
2
+ module Projection
3
+ module Controls
4
+ module Entity
5
+ module Projection
6
+ class Example
7
+ include EntityProjection
8
+
9
+ apply Message::Example do |message|
10
+ entity.some_attribute = message.some_attribute
11
+
12
+ entity.example_association.find_or_new id: message.association_id do |associated_entity|
13
+ associated_entity.association_attribute = message.association_attribute
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ module ReadModel
2
+ module Projection
3
+ module Controls
4
+ module EventData
5
+ def self.example(entity_id: nil, category: nil, association_id: nil, position: nil, global_position: nil)
6
+ entity_id ||= Identifier::UUID::Random.get
7
+ association_id ||= Identifier::UUID::Random.get
8
+ position ||= 0
9
+ global_position ||= position
10
+
11
+ data = {
12
+ :entity_id => entity_id,
13
+ :some_attribute => SecureRandom.hex(7),
14
+ :association_id => association_id,
15
+ :association_attribute => SecureRandom.hex(7)
16
+ }
17
+
18
+ stream_name = StreamName.example(
19
+ id: entity_id,
20
+ randomize_category: false,
21
+ category: category
22
+ )
23
+
24
+ event_data = EventSource::Controls::EventData::Read.example(
25
+ id: entity_id,
26
+ type: Message::Example.message_type,
27
+ data: data
28
+ )
29
+ event_data.stream_name = stream_name
30
+ event_data.position = position
31
+ event_data.global_position = global_position
32
+ event_data
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,25 @@
1
+ module ReadModel
2
+ module Projection
3
+ module Controls
4
+ module Message
5
+ def self.example(id: nil, **arguments)
6
+ event_data = EventData.example **arguments
7
+
8
+ message = Messaging::Message::Import.(event_data, Example)
9
+ message.id = id unless id.nil?
10
+ message
11
+ end
12
+
13
+ class Example
14
+ include Messaging::Message
15
+
16
+ attribute :entity_id, String
17
+ attribute :some_attribute, String
18
+
19
+ attribute :association_id, String
20
+ attribute :association_attribute, String
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ module ReadModel
2
+ module Projection
3
+ module Controls
4
+ module Name
5
+ def self.example(random: nil)
6
+ random = SecureRandom.hex 7 if random.nil?
7
+
8
+ "some-projection-#{random}"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ module ReadModel
2
+ module Projection
3
+ module Controls
4
+ module PositionStore
5
+ module Fails
6
+ def self.example
7
+ Example.new
8
+ end
9
+
10
+ class Example
11
+ include ::Consumer::PositionStore
12
+
13
+ def get
14
+ end
15
+
16
+ def put(_)
17
+ raise Error
18
+ end
19
+ end
20
+
21
+ Error = Class.new StandardError
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,14 @@
1
+ module ReadModel
2
+ module Projection
3
+ module Controls
4
+ module Project
5
+ def self.example
6
+ entity_class = Entity::Example
7
+ projection_class = Entity::Projection::Example
8
+
9
+ ReadModel::Projection::Project.new entity_class, projection_class
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ module ReadModel
2
+ module Projection
3
+ module Controls
4
+ SetupSchema = ReadModel::Entity::Sequel::Controls::SetupSchema
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module ReadModel
2
+ module Projection
3
+ module Controls
4
+ StreamName = Messaging::Controls::StreamName
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ module ReadModel
2
+ module Projection
3
+ class Log < ::Log
4
+ def tag!(tags)
5
+ tags << :read_model_projection
6
+ tags << :read_model
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,21 @@
1
+ module ReadModel
2
+ module Projection
3
+ module Position
4
+ module SetupSchema
5
+ def self.call(drop_tables: nil)
6
+ drop_tables ||= false
7
+
8
+ create_table = drop_tables ? :create_table! : :create_table?
9
+
10
+ connection = Connection.get
11
+
12
+ connection.public_send create_table, :read_model_projection_positions do
13
+ String :projection, primary_key: true
14
+ String :stream_name, null: false
15
+ Integer :position, null: false
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,64 @@
1
+ module ReadModel
2
+ module Projection
3
+ module Position
4
+ class Store
5
+ include Consumer::PositionStore
6
+ include Log::Dependency
7
+
8
+ configure_macro :position_store
9
+
10
+ initializer :projection_name, :stream_name
11
+
12
+ dependency :db_connection
13
+
14
+ def self.build(projection_name, stream_name, position_store: nil)
15
+ return position_store unless position_store.nil?
16
+
17
+ instance = new projection_name, stream_name
18
+ Connection.configure_connection instance
19
+ instance
20
+ end
21
+
22
+ def get
23
+ result = table.
24
+ select(:position, :stream_name).
25
+ where(:projection => projection_name).
26
+ first
27
+
28
+ unless result.nil?
29
+ unless result.fetch(:stream_name) == stream_name
30
+ error_message = "Stream name mismatch (Projection: #{projection_name}, Expected: #{stream_name}, Persisted: #{result[:stream_name]})"
31
+ logger.error { error_message }
32
+ raise StreamNameError, error_message
33
+ end
34
+
35
+ position = result.fetch :position
36
+ end
37
+
38
+ position
39
+ end
40
+
41
+ def put(position)
42
+ result = table.
43
+ insert_conflict(
44
+ :target => :projection,
45
+ :update => { :position => :excluded__position }
46
+ ).
47
+ insert(
48
+ :position => position,
49
+ :projection => projection_name,
50
+ :stream_name => stream_name
51
+ )
52
+
53
+ result
54
+ end
55
+
56
+ def table
57
+ db_connection[:read_model_projection_positions]
58
+ end
59
+
60
+ StreamNameError = Class.new StandardError
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,93 @@
1
+ module ReadModel
2
+ module Projection
3
+ class Project
4
+ include Log::Dependency
5
+
6
+ configure :project
7
+
8
+ dependency :cache, EntityCache
9
+ dependency :get, EventSource::Get
10
+
11
+ initializer :entity_class, a(:projection)
12
+
13
+ def self.build(entity_class, projection_class, get: nil)
14
+ instance = new entity_class, projection_class
15
+ instance.configure
16
+ instance.get = get unless get.nil?
17
+ instance
18
+ end
19
+
20
+ def configure
21
+ EntityCache.configure self, self.class, attr_name: :cache
22
+ end
23
+
24
+ def call(event_data, &supplemental_action)
25
+ logger.trace { "Handling event (#{LogText.attributes self, event_data})" }
26
+
27
+ entity_id = Messaging::StreamName.get_id event_data.stream_name
28
+
29
+ entity, projected_version = get_entity entity_id
30
+
31
+ iterator = EventSource::Iterator.build get, event_data.stream_name, position: projected_version.next
32
+
33
+ (projected_version.next..event_data.position).each do |position|
34
+ if position == event_data.position
35
+ read_event_data = event_data
36
+ else
37
+ read_event_data = iterator.next
38
+ end
39
+
40
+ project entity, read_event_data, &supplemental_action
41
+ end
42
+
43
+ if projected_version < event_data.position
44
+ cache.put entity_id, entity, event_data.position
45
+ end
46
+
47
+ if event_data.position < projected_version
48
+ error_message = "Event is far ahead of entity (#{LogText.attributes self, event_data}, Projected Version: #{projected_version})"
49
+ logger.error { error_message }
50
+ raise SynchronizationError, error_message
51
+ end
52
+
53
+ entity.save
54
+
55
+ logger.info { "Projected event (#{LogText.attributes self, event_data})" }
56
+
57
+ entity
58
+ end
59
+
60
+ def get_entity(entity_id)
61
+ cache_record = cache.get entity_id
62
+
63
+ if cache_record.nil?
64
+ entity = entity_class.get entity_id
65
+
66
+ return entity, -1
67
+ else
68
+ entity = cache_record.entity
69
+
70
+ return entity, cache_record.version
71
+ end
72
+ end
73
+
74
+ def project(entity, event_data, &supplemental_action)
75
+ projection.(entity, event_data)
76
+
77
+ supplemental_action.(event_data) if supplemental_action
78
+ end
79
+
80
+ SynchronizationError = Class.new StandardError
81
+
82
+ module LogText
83
+ def self.attributes(project, event_data)
84
+ entity_id = Messaging::StreamName.get_id event_data.stream_name
85
+
86
+ text = "Event Type: #{event_data.type}, Event Position: #{event_data.position}, Entity ID: #{entity_id}, Entity Class: #{project.entity_class}, Projection Class: #{project.projection}"
87
+
88
+ text
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
metadata ADDED
@@ -0,0 +1,173 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: read_model-projection
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0.0.pre1
5
+ platform: ruby
6
+ authors:
7
+ - Obsidian Software, Inc
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-01-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: evt-consumer
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: evt-entity_cache
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: evt-entity_projection
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: evt-messaging
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: postgresql-connector
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: read_model-entity-sequel
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: evt-messaging-event_store
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: test_bench
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: " "
126
+ email: developers@obsidianexchange.com
127
+ executables: []
128
+ extensions: []
129
+ extra_rdoc_files: []
130
+ files:
131
+ - lib/read_model/projection.rb
132
+ - lib/read_model/projection/connection.rb
133
+ - lib/read_model/projection/consumer/event_store.rb
134
+ - lib/read_model/projection/controls.rb
135
+ - lib/read_model/projection/controls/category.rb
136
+ - lib/read_model/projection/controls/entity.rb
137
+ - lib/read_model/projection/controls/entity/projection.rb
138
+ - lib/read_model/projection/controls/event_data.rb
139
+ - lib/read_model/projection/controls/message.rb
140
+ - lib/read_model/projection/controls/name.rb
141
+ - lib/read_model/projection/controls/position_store/fails.rb
142
+ - lib/read_model/projection/controls/project.rb
143
+ - lib/read_model/projection/controls/setup_schema.rb
144
+ - lib/read_model/projection/controls/stream_name.rb
145
+ - lib/read_model/projection/log.rb
146
+ - lib/read_model/projection/position/setup_schema.rb
147
+ - lib/read_model/projection/position/store.rb
148
+ - lib/read_model/projection/project.rb
149
+ homepage: https://github.com/obsidian-btc/read-model-publisher
150
+ licenses:
151
+ - Not licensed for public use
152
+ metadata: {}
153
+ post_install_message:
154
+ rdoc_options: []
155
+ require_paths:
156
+ - lib
157
+ required_ruby_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: 2.2.3
162
+ required_rubygems_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">"
165
+ - !ruby/object:Gem::Version
166
+ version: 1.3.1
167
+ requirements: []
168
+ rubyforge_project:
169
+ rubygems_version: 2.6.8
170
+ signing_key:
171
+ specification_version: 4
172
+ summary: Read model projection
173
+ test_files: []