read_model-projection 0.3.0.0.pre1

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 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: []