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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0f61dae355ea08d78da40bc227540ce47635d09ac0f8645d3f70e1e53201ff54
|
4
|
+
data.tar.gz: 57bfe266b33d2f7744b87af84b14a2bd0d7a3c25c0bfc6b452457488067ad181
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3568eca1b26cd974a6d860e233cb50d4911e49bd7f61f4d6c43d865e78cbded0d48dd40e6331fa8d46ce3eb8906a7093c797ce340e16e33c4fad441085199c72
|
7
|
+
data.tar.gz: cca39336c0d199d46254e15293439b9e81f401b8e4a586f68eb90658a5e027888f4bce1911dad21dbbd6fb2a6c4c68e4380d47935d1492c962e84a092496f8de
|
data/lib/realm-core.rb
ADDED
data/lib/realm.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Dir[File.join(File.dirname(__FILE__), 'realm', '**', '*.rb')].sort.each do |f|
|
4
|
+
require f
|
5
|
+
end
|
6
|
+
|
7
|
+
module Realm
|
8
|
+
class << self
|
9
|
+
# Setup realm in test/console
|
10
|
+
def setup(root_module, **options)
|
11
|
+
config = Realm::Config.new(root_module: root_module, **options)
|
12
|
+
Realm::Builder.setup(config)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Bind realm in service/engine
|
16
|
+
def bind(root_module, **options)
|
17
|
+
setup(root_module, **options).tap do |builder|
|
18
|
+
root_module.define_singleton_method(:realm) { builder.runtime }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry-validation'
|
4
|
+
require 'active_support/core_ext/module/delegation'
|
5
|
+
require 'realm/error'
|
6
|
+
require 'realm/mixins/context_injection'
|
7
|
+
require 'realm/mixins/repository_helper'
|
8
|
+
require 'realm/mixins/aggregate_member'
|
9
|
+
require_relative 'action_handler/result'
|
10
|
+
|
11
|
+
module Realm
|
12
|
+
class ActionHandler
|
13
|
+
extend Mixins::ContextInjection::ClassMethods
|
14
|
+
include Mixins::AggregateMember
|
15
|
+
include Mixins::RepositoryHelper
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_reader :contracts
|
19
|
+
|
20
|
+
def call(action: :handle, params: {}, runtime: nil)
|
21
|
+
new(runtime: runtime).(action: action, params: params)
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def require_permission(*names)
|
27
|
+
# TODO: implement
|
28
|
+
end
|
29
|
+
|
30
|
+
def contract(&block)
|
31
|
+
@method_contract = Class.new(Dry::Validation::Contract, &block).new
|
32
|
+
end
|
33
|
+
|
34
|
+
def contract_schema(&block)
|
35
|
+
contract { schema(&block) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def contract_params(&block)
|
39
|
+
contract { params(&block) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def contract_json(&block)
|
43
|
+
contract { json(&block) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def method_added(method_name)
|
47
|
+
super
|
48
|
+
return unless defined?(@method_contract)
|
49
|
+
|
50
|
+
@contracts ||= {}
|
51
|
+
@contracts[method_name] = @method_contract
|
52
|
+
remove_instance_variable(:@method_contract)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize(runtime: nil)
|
57
|
+
@runtime = runtime
|
58
|
+
end
|
59
|
+
|
60
|
+
def call(action: :handle, params: {})
|
61
|
+
# TODO: check permissions
|
62
|
+
raise CannotHandleAction.new(self, action) unless respond_to?(action)
|
63
|
+
|
64
|
+
safe_params = validate(action, params.to_h)
|
65
|
+
send(action, **safe_params)
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
delegate :context, to: :@runtime
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def validate(action, params)
|
75
|
+
contract = self.class.contracts && self.class.contracts[action]
|
76
|
+
return params unless contract
|
77
|
+
|
78
|
+
result = contract.(params)
|
79
|
+
raise Realm::InvalidParams, result if result.failure?
|
80
|
+
|
81
|
+
result.to_h
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Realm
|
4
|
+
class ActionHandler
|
5
|
+
# Tuple of label and value
|
6
|
+
class Result < Array
|
7
|
+
def self.[](first, second = nil)
|
8
|
+
return new(first, second).freeze if first.is_a?(Symbol) || first.is_a?(Realm::Event)
|
9
|
+
|
10
|
+
new(second || :ok, first).freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
def label
|
14
|
+
self[0]
|
15
|
+
end
|
16
|
+
|
17
|
+
def event
|
18
|
+
label if label.is_a?(Realm::Event)
|
19
|
+
end
|
20
|
+
|
21
|
+
def value
|
22
|
+
self[1]
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def initialize(label, value)
|
28
|
+
super([label, value])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/string'
|
4
|
+
require 'realm/container'
|
5
|
+
require 'realm/runtime'
|
6
|
+
require 'realm/domain_resolver'
|
7
|
+
require 'realm/persistence'
|
8
|
+
require 'realm/dispatcher'
|
9
|
+
require 'realm/event_router'
|
10
|
+
require 'realm/plugin'
|
11
|
+
|
12
|
+
module Realm
|
13
|
+
class Builder
|
14
|
+
def self.setup(config)
|
15
|
+
new(config).setup
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(config)
|
19
|
+
@config = config
|
20
|
+
end
|
21
|
+
|
22
|
+
def setup
|
23
|
+
logger.info("Setting up #{cfg.root_module} realm")
|
24
|
+
register_domain_resolver
|
25
|
+
register_event_router
|
26
|
+
register_runtime
|
27
|
+
register_logger
|
28
|
+
register_dependencies
|
29
|
+
setup_plugins
|
30
|
+
config_persistence
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def runtime
|
35
|
+
@container.resolve(Runtime)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def register_domain_resolver
|
41
|
+
container.register_factory(DomainResolver, constantize(cfg.domain_module))
|
42
|
+
end
|
43
|
+
|
44
|
+
def register_event_router
|
45
|
+
return if cfg.event_gateways.empty?
|
46
|
+
|
47
|
+
container.register_factory(EventRouter, cfg.event_gateways, prefix: cfg.prefix)
|
48
|
+
end
|
49
|
+
|
50
|
+
def register_runtime
|
51
|
+
container.register_factory(Runtime, container)
|
52
|
+
end
|
53
|
+
|
54
|
+
def register_logger
|
55
|
+
container.register(:logger, logger)
|
56
|
+
end
|
57
|
+
|
58
|
+
def register_dependencies
|
59
|
+
container.register_all(**cfg.dependencies)
|
60
|
+
end
|
61
|
+
|
62
|
+
def setup_plugins
|
63
|
+
Plugin.descendants.each do |plugin|
|
64
|
+
plugin.setup(cfg, container) if cfg.plugins.include?(plugin.plugin_name)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def config_persistence
|
69
|
+
return unless cfg.persistence_gateway.present?
|
70
|
+
|
71
|
+
Persistence.setup(container, cfg.persistence_gateway[:repositories])
|
72
|
+
end
|
73
|
+
|
74
|
+
def constantize(*parts)
|
75
|
+
return parts[0] unless parts[0].is_a?(String)
|
76
|
+
|
77
|
+
parts.join('::').safe_constantize
|
78
|
+
end
|
79
|
+
|
80
|
+
def cfg
|
81
|
+
@config
|
82
|
+
end
|
83
|
+
|
84
|
+
def container
|
85
|
+
@container ||= Container.new
|
86
|
+
end
|
87
|
+
|
88
|
+
def logger
|
89
|
+
@logger ||= cfg.logger || (defined?(Rails) && Rails.logger) ||
|
90
|
+
Logger.new($stdout, level: ENV.fetch('LOG_LEVEL', :info).to_sym)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/module/delegation'
|
4
|
+
require 'active_support/core_ext/string'
|
5
|
+
require 'realm/action_handler'
|
6
|
+
require 'realm/mixins/reactive'
|
7
|
+
|
8
|
+
module Realm
|
9
|
+
class CommandHandler < Realm::ActionHandler
|
10
|
+
include Mixins::Reactive
|
11
|
+
|
12
|
+
def call(*)
|
13
|
+
gateway = context[:rom]&.gateways&.dig(:default)
|
14
|
+
gateway ? gateway.transaction { super } : super
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def result(first, second = nil)
|
20
|
+
Result[first, second]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/realm/config.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/string'
|
4
|
+
require 'dry-initializer'
|
5
|
+
|
6
|
+
module Realm
|
7
|
+
class Config
|
8
|
+
extend Dry::Initializer
|
9
|
+
|
10
|
+
option :root_module
|
11
|
+
option :database_url, default: proc {}
|
12
|
+
option :prefix, default: proc {}
|
13
|
+
option :namespace, default: proc { root_module.to_s.underscore }
|
14
|
+
option :domain_module, default: proc { "#{root_module}::Domain" }
|
15
|
+
option :engine_class, default: proc { "#{root_module}::Engine" }
|
16
|
+
option :engine_path, default: proc { engine_class&.to_s&.safe_constantize&.root }
|
17
|
+
option :logger, default: proc {}
|
18
|
+
option :plugins, default: proc { [] }, reader: false
|
19
|
+
option :dependencies, default: proc { {} }
|
20
|
+
option :persistence_gateway, default: proc { database_url && { type: :rom, url: database_url } }, reader: false
|
21
|
+
option :event_gateway, default: proc {}, reader: false
|
22
|
+
option :event_gateways, default: proc {
|
23
|
+
@event_gateway ? { default: { **@event_gateway, default: true } } : {}
|
24
|
+
}
|
25
|
+
|
26
|
+
def plugins
|
27
|
+
Array(@plugins)
|
28
|
+
end
|
29
|
+
|
30
|
+
def persistence_gateway
|
31
|
+
return {} unless @persistence_gateway
|
32
|
+
|
33
|
+
class_path = engine_path && "#{engine_path}/app/persistence/#{namespace}"
|
34
|
+
repos_path = class_path && "#{class_path}/repositories"
|
35
|
+
repos_module = "#{root_module}::Repositories"
|
36
|
+
{
|
37
|
+
root_module: root_module,
|
38
|
+
class_path: class_path,
|
39
|
+
repos_path: repos_path,
|
40
|
+
repos_module: repos_module,
|
41
|
+
migration_path: engine_path && "#{engine_path}/db/migrate",
|
42
|
+
repositories: repositories(repos_path, repos_module),
|
43
|
+
}.merge(@persistence_gateway)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def repositories(repos_path, repos_module)
|
49
|
+
return [] unless repos_path
|
50
|
+
|
51
|
+
Dir[File.join(repos_path, '**', '*.rb')].each_with_object([]) do |filename, all|
|
52
|
+
matches = %r{^#{repos_path}/(.+)\.rb$}.match(filename)
|
53
|
+
all << "#{repos_module}::#{matches[1].camelize}".constantize if matches
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry-container'
|
4
|
+
require 'active_support/core_ext/string'
|
5
|
+
require 'active_support/core_ext/object/try'
|
6
|
+
require 'realm/error'
|
7
|
+
|
8
|
+
module Realm
|
9
|
+
class Container
|
10
|
+
include Dry::Container::Mixin
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
def self.[](object)
|
14
|
+
object.is_a?(Container) ? object : Container.new(object)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(hash = {})
|
18
|
+
register_all(hash)
|
19
|
+
end
|
20
|
+
|
21
|
+
def register(key, contents = nil, options = {}, &block)
|
22
|
+
options[:klass] ||= contents.class if contents && !contents.is_a?(::Hash)
|
23
|
+
super(key, contents, options, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def register_all(hash)
|
27
|
+
hash.each_pair do |key, value|
|
28
|
+
register(key, value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def register_factory(klass, *args, as: nil, memoize: true, **kwargs) # rubocop:disable Naming/MethodParameterName
|
33
|
+
register(as || klass, klass: klass, memoize: memoize) do
|
34
|
+
create(klass, *args, **kwargs)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def create(klass, *args, **kwargs)
|
39
|
+
(klass.try(:dependencies) || []).each do |d|
|
40
|
+
fn = -> { resolve_dependable(sanitize_dependable(d.dependable), d.optional?) }
|
41
|
+
kwargs[d.name] = d.lazy? ? fn : fn.call
|
42
|
+
end
|
43
|
+
klass.new(*args, **kwargs)
|
44
|
+
end
|
45
|
+
|
46
|
+
def [](key)
|
47
|
+
resolve(key) if key?(key)
|
48
|
+
end
|
49
|
+
|
50
|
+
def resolve_all(klass)
|
51
|
+
_container.each_with_object([]) do |(_, item), all|
|
52
|
+
all << item.call if item.options[:klass] <= klass
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def sanitize_dependable(dependable)
|
59
|
+
dependable.is_a?(String) && dependable.match(/^[A-Z]/) ? dependable.constantize : dependable
|
60
|
+
end
|
61
|
+
|
62
|
+
def resolve_dependable(dependable, optional)
|
63
|
+
raise DependencyMissing, dependable unless optional || key?(dependable)
|
64
|
+
|
65
|
+
self[dependable]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'realm/container'
|
4
|
+
|
5
|
+
module Realm
|
6
|
+
class Context
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
def initialize(*containers)
|
10
|
+
@containers = containers.map { |c| Container[c] }
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](name)
|
14
|
+
@containers.each do |container|
|
15
|
+
return container[name] if container.key?(name)
|
16
|
+
end
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def key?(name)
|
21
|
+
@containers.any? { |container| container.key?(name) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def merge(container_like)
|
25
|
+
container_like.blank? ? self : self.class.new(container_like, *@containers)
|
26
|
+
end
|
27
|
+
|
28
|
+
def each(&block)
|
29
|
+
@containers.each { |container| container.each(&block) }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Just for testing
|
33
|
+
def override!(container)
|
34
|
+
@containers.prepend(container)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/string'
|
4
|
+
|
5
|
+
module Realm
|
6
|
+
class Dependency
|
7
|
+
attr_reader :dependable, :name
|
8
|
+
|
9
|
+
def initialize(dependable, as: nil, optional: false, lazy: false) # rubocop:disable Naming/MethodParameterName
|
10
|
+
@dependable = dependable
|
11
|
+
@name = as || dependable.to_s.demodulize.underscore.to_sym
|
12
|
+
@optional = optional
|
13
|
+
@lazy = lazy
|
14
|
+
end
|
15
|
+
|
16
|
+
def optional?
|
17
|
+
@optional
|
18
|
+
end
|
19
|
+
|
20
|
+
def lazy?
|
21
|
+
@lazy
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|