realm-core 0.7.0
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 +7 -0
- data/lib/realm-core.rb +6 -0
- data/lib/realm.rb +22 -0
- data/lib/realm/action_handler.rb +84 -0
- data/lib/realm/action_handler/result.rb +32 -0
- data/lib/realm/builder.rb +93 -0
- data/lib/realm/command_handler.rb +23 -0
- data/lib/realm/config.rb +57 -0
- data/lib/realm/container.rb +68 -0
- data/lib/realm/context.rb +37 -0
- data/lib/realm/dependency.rb +24 -0
- data/lib/realm/dispatcher.rb +74 -0
- data/lib/realm/domain_resolver.rb +59 -0
- data/lib/realm/error.rb +64 -0
- data/lib/realm/event.rb +56 -0
- data/lib/realm/event_factory.rb +42 -0
- data/lib/realm/event_handler.rb +102 -0
- data/lib/realm/event_router.rb +100 -0
- data/lib/realm/event_router/gateway.rb +49 -0
- data/lib/realm/event_router/internal_loop_gateway.rb +50 -0
- data/lib/realm/health_status.rb +46 -0
- data/lib/realm/mixins/aggregate_member.rb +25 -0
- data/lib/realm/mixins/context_injection.rb +49 -0
- data/lib/realm/mixins/controller.rb +53 -0
- data/lib/realm/mixins/decorator.rb +33 -0
- data/lib/realm/mixins/dependency_injection.rb +52 -0
- data/lib/realm/mixins/reactive.rb +32 -0
- data/lib/realm/mixins/repository_helper.rb +43 -0
- data/lib/realm/multi_worker.rb +30 -0
- data/lib/realm/persistence.rb +54 -0
- data/lib/realm/persistence/repository_query_handler_adapter.rb +24 -0
- data/lib/realm/plugin.rb +20 -0
- data/lib/realm/query_handler.rb +8 -0
- data/lib/realm/runtime.rb +61 -0
- data/lib/realm/runtime/session.rb +33 -0
- data/lib/realm/types.rb +9 -0
- metadata +217 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Realm
|
4
|
+
class EventRouter
|
5
|
+
class Gateway
|
6
|
+
def self.auto_register_on_init
|
7
|
+
false
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(event_factory:, namespace: :default, runtime: nil, **)
|
11
|
+
@namespace = namespace
|
12
|
+
@event_factory = event_factory
|
13
|
+
@runtime = runtime
|
14
|
+
end
|
15
|
+
|
16
|
+
def register(handler_class)
|
17
|
+
handler_class.event_types.each do |event_type|
|
18
|
+
add_listener(event_type, handler_class.bind_runtime(@runtime))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_listener(event_type, listener)
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
def trigger(event_type, attributes = {})
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
def worker(*)
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def cleanup
|
35
|
+
# do nothing
|
36
|
+
end
|
37
|
+
|
38
|
+
def queues
|
39
|
+
[]
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def create_event(event_type, attributes = {})
|
45
|
+
@event_factory.create_event(event_type, **attributes)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './gateway'
|
4
|
+
|
5
|
+
module Realm
|
6
|
+
class EventRouter
|
7
|
+
class InternalLoopGateway < Gateway
|
8
|
+
def self.auto_register_on_init
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(isolated: false, **)
|
13
|
+
super
|
14
|
+
@listener_map = {}
|
15
|
+
@isolated = isolated
|
16
|
+
gateways << self
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_listener(event_type, listener)
|
20
|
+
(@listener_map[event_type] ||= []) << listener
|
21
|
+
end
|
22
|
+
|
23
|
+
def trigger(event_type, attributes = {})
|
24
|
+
create_event(event_type, attributes).tap do |event|
|
25
|
+
gateways.each { |gateway| gateway.handle(event_type, event) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def purge!
|
30
|
+
gateways.clear
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def handle(event_type, event)
|
36
|
+
find_listeners(event_type).each { |listener| listener.(event) }
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def find_listeners(event_type)
|
42
|
+
@listener_map.fetch_values(event_type, :any) { [] }.flatten
|
43
|
+
end
|
44
|
+
|
45
|
+
def gateways
|
46
|
+
@isolated ? (@gateways ||= []) : (@@gateways ||= []) # rubocop:disable Style/ClassVars
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/object/blank'
|
4
|
+
|
5
|
+
module Realm
|
6
|
+
class HealthStatus
|
7
|
+
CODES = %i[green yellow red].freeze
|
8
|
+
attr_reader :code, :issues
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def [](code, *issues)
|
12
|
+
new(code, issues.flatten)
|
13
|
+
end
|
14
|
+
|
15
|
+
def from_issues(issues)
|
16
|
+
new(issues.blank? ? :green : :red, issues)
|
17
|
+
end
|
18
|
+
|
19
|
+
def combine(component_map)
|
20
|
+
code_index = component_map.values.map { |i| CODES.index(i.code) }.max
|
21
|
+
new(CODES[code_index || 0], [], component_map)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def for_component(*names)
|
26
|
+
@component_map.dig(*names)
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_h
|
30
|
+
hash = { status: @code }
|
31
|
+
hash[:issues] = @issues if @issues.present?
|
32
|
+
hash[:components] = @component_map.transform_values(&:to_h) if @component_map.present?
|
33
|
+
hash
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def initialize(code, issues = [], component_map = {})
|
39
|
+
raise ArgumentError, "Invalid status code #{code}" unless CODES.include?(code)
|
40
|
+
|
41
|
+
@code = code
|
42
|
+
@issues = issues.freeze
|
43
|
+
@component_map = component_map.freeze
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Realm
|
4
|
+
module Mixins
|
5
|
+
module AggregateMember
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
def aggregate
|
11
|
+
self.class.aggregate
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def aggregate
|
16
|
+
@aggregate ||= begin
|
17
|
+
module_chain = name.split('::')
|
18
|
+
domain_index = module_chain.index('Domain')
|
19
|
+
domain_index && module_chain[domain_index + 1].underscore.to_sym
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'realm/error'
|
4
|
+
|
5
|
+
module Realm
|
6
|
+
module Mixins
|
7
|
+
module ContextInjection
|
8
|
+
def self.included(base)
|
9
|
+
base.extend(ClassMethods)
|
10
|
+
base.prepend(Initializer)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def inject(*names, &block)
|
15
|
+
names.each do |name|
|
16
|
+
define_method(name) do
|
17
|
+
raise Realm::DependencyMissing, name unless context.key?(name)
|
18
|
+
|
19
|
+
return context[name] unless block
|
20
|
+
|
21
|
+
var = "@#{name}"
|
22
|
+
return instance_variable_get(var) if instance_variable_defined?(var)
|
23
|
+
|
24
|
+
instance_variable_set(var, block.(context[name]))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module Initializer
|
31
|
+
def initialize(*args, context: nil, **kwargs)
|
32
|
+
@context = context || context_from_root_module || {}
|
33
|
+
super(*args, **kwargs)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def context_from_root_module
|
39
|
+
root_module = self.class.module_parents[-2]
|
40
|
+
root_module.realm.context if root_module.respond_to?(:realm)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
attr_reader :context
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/module/introspection'
|
4
|
+
require 'active_support/core_ext/class/attribute'
|
5
|
+
|
6
|
+
module Realm
|
7
|
+
module Mixins
|
8
|
+
module Controller
|
9
|
+
def self.included(base)
|
10
|
+
base.class_attribute(:aggregate_name)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
end
|
13
|
+
|
14
|
+
def domain_runtime
|
15
|
+
@domain_runtime ||= root_domain_runtime.session(domain_context)
|
16
|
+
end
|
17
|
+
|
18
|
+
def domain_context
|
19
|
+
{}
|
20
|
+
end
|
21
|
+
|
22
|
+
def query(identifier, params = {})
|
23
|
+
domain_runtime.query(get_dispatchable(identifier), params)
|
24
|
+
end
|
25
|
+
|
26
|
+
def run(identifier, params = {})
|
27
|
+
domain_runtime.run(get_dispatchable(identifier), params)
|
28
|
+
end
|
29
|
+
|
30
|
+
def run_as_job(identifier, params = {})
|
31
|
+
domain_runtime.run_as_job(get_dispatchable(identifier), params)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def get_dispatchable(identifier)
|
37
|
+
return identifier if identifier.respond_to?(:call)
|
38
|
+
|
39
|
+
[aggregate_name, identifier].compact.join('.')
|
40
|
+
end
|
41
|
+
|
42
|
+
def root_domain_runtime
|
43
|
+
self.class.module_parents[-2].realm
|
44
|
+
end
|
45
|
+
|
46
|
+
module ClassMethods
|
47
|
+
def with_aggregate(name)
|
48
|
+
self.aggregate_name = name
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Realm
|
4
|
+
module Mixins
|
5
|
+
module Decorator
|
6
|
+
def self.[](decorated) # rubocop:disable Metrics/MethodLength
|
7
|
+
Module.new do
|
8
|
+
def method_missing(...)
|
9
|
+
_decorated.send(...)
|
10
|
+
end
|
11
|
+
|
12
|
+
def respond_to_missing?(...)
|
13
|
+
_decorated.respond_to?(...)
|
14
|
+
end
|
15
|
+
|
16
|
+
if decorated.to_s[0] == '@'
|
17
|
+
define_method :initialize do |value|
|
18
|
+
instance_variable_set(decorated, value)
|
19
|
+
end
|
20
|
+
|
21
|
+
define_method :_decorated do
|
22
|
+
instance_variable_get(decorated)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
define_method :_decorated do
|
26
|
+
send(decorated)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'realm/dependency'
|
4
|
+
|
5
|
+
module Realm
|
6
|
+
module Mixins
|
7
|
+
module DependencyInjection
|
8
|
+
def self.included(base)
|
9
|
+
base.extend(ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def new(*args, **kwargs, &block)
|
14
|
+
instance = allocate
|
15
|
+
deps.each { |d| define_dependency_method(instance, kwargs, d) }
|
16
|
+
kwargs_without_dependencies = kwargs.reject { |k, _| deps.any? { |d| d.name == k } }
|
17
|
+
instance.send(:initialize, *args, **kwargs_without_dependencies, &block)
|
18
|
+
instance
|
19
|
+
end
|
20
|
+
|
21
|
+
def inject(*dependables, **options)
|
22
|
+
deps.concat(dependables.map { |d| Dependency.new(d, **options) })
|
23
|
+
end
|
24
|
+
|
25
|
+
def dependencies
|
26
|
+
deps.freeze
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def deps
|
32
|
+
@deps ||= []
|
33
|
+
end
|
34
|
+
|
35
|
+
def define_dependency_method(instance, kwargs, spec)
|
36
|
+
dependency = kwargs[spec.name]
|
37
|
+
instance.singleton_class.class_eval do
|
38
|
+
define_method(spec.name) do
|
39
|
+
return dependency unless spec.lazy?
|
40
|
+
|
41
|
+
var = "@#{spec.name}"
|
42
|
+
return instance_variable_get(var) if instance_variable_defined?(var)
|
43
|
+
|
44
|
+
instance_variable_set(var, dependency.respond_to?(:call) ? dependency.call : dependency)
|
45
|
+
end
|
46
|
+
protected spec.name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'realm/error'
|
4
|
+
|
5
|
+
module Realm
|
6
|
+
module Mixins
|
7
|
+
module Reactive
|
8
|
+
protected
|
9
|
+
|
10
|
+
def run(command, params = {})
|
11
|
+
parts = command.to_s.split('.')
|
12
|
+
parts.prepend(aggregate) if parts.size == 1 && respond_to?(:aggregate) && aggregate
|
13
|
+
@runtime.run(parts.join('.'), params.to_h)
|
14
|
+
end
|
15
|
+
|
16
|
+
def trigger(event_type, attributes = {})
|
17
|
+
attributes = attributes.to_h
|
18
|
+
head = { origin: origin(caller_locations(1, 1)) }.merge(attributes.fetch(:head, {}))
|
19
|
+
final_attrs = attributes.merge(head: head)
|
20
|
+
final_attrs[:cause] ||= context[:cause] if context.key?(:cause)
|
21
|
+
@runtime.trigger(event_type, final_attrs)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Detects the class and method from which this event is triggered
|
27
|
+
def origin(backtrace)
|
28
|
+
[self.class.name, backtrace[0].to_s.match(/`([^']+)'/)&.then { |m| "##{m[1]}" }].join
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'realm/error'
|
4
|
+
|
5
|
+
module Realm
|
6
|
+
module Mixins
|
7
|
+
module RepositoryHelper
|
8
|
+
class OnlyOneWriteRepo < Realm::Error['You can have only one read/write repo per handler']; end
|
9
|
+
class InjectingRepoOutsideAggregate < Realm::Error['Cannot auto inject repository outside of an aggregate']; end
|
10
|
+
|
11
|
+
def self.included(base)
|
12
|
+
base.extend(ClassMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
protected
|
17
|
+
|
18
|
+
def use_repo(*names, readonly: self < Realm::QueryHandler)
|
19
|
+
raise OnlyOneWriteRepo if !readonly && (names.size > 1 || defined?(@write_repo_injected))
|
20
|
+
|
21
|
+
names << default_repo_name if names.empty?
|
22
|
+
names.each { |name| inject_repo(name, readonly) }
|
23
|
+
@write_repo_injected = true unless readonly
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def inject_repo(name, readonly)
|
29
|
+
repo_name = "#{name}_repo"
|
30
|
+
return inject(repo_name) unless readonly
|
31
|
+
|
32
|
+
inject(repo_name) { |repo| repo.respond_to?(:readonly) ? repo.readonly : repo }
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_repo_name
|
36
|
+
raise InjectingRepoOutsideAggregate unless respond_to?(:aggregate)
|
37
|
+
|
38
|
+
aggregate
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|