Class: Yes::Core::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/yes/core/configuration.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

Initializes a new configuration instance with nested hashes for class storage.



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/yes/core/configuration.rb', line 104

def initialize
  @registered_classes = Hash.new do |h, k|
    h[k] = Hash.new { |h2, k2| h2[k2] = {} }
  end
  @aggregate_shortcuts = false
  @super_admin_check = ->(_auth_data) { false }
  @cerbos_principal_data_builder = lambda { |auth_data|
    { id: auth_data[:identity_id], roles: [], attributes: {} }
  }
  @cerbos_url = ENV.fetch('CERBOS_URL', 'cerbos-cluster-ip-service:3593')
  @cerbos_tls = true
  @cerbos_commands_authorizer_include_metadata = false
  @cerbos_read_authorizer_include_metadata = false
  @cerbos_read_authorizer_actions = %w[read]
  @cerbos_read_authorizer_resource_id_prefix = 'read-'
  @cerbos_read_authorizer_principal_anonymous_id = 'anonymous'
  @logger = nil
  @error_reporter = nil
  @payload_store_client = nil
  @process_commands_inline = true
  @command_notifier_classes = []
  @otl_tracer = nil
  @raise_on_missing_handler_method = defined?(Rails) ? Rails.env.local? : false
  @subscriptions_heartbeat_url = nil
  @subscriptions_heartbeat_interval = 30
  @service_name = ENV.fetch('SERVICE_NAME', nil)
  @service_version = ENV.fetch('APP_VERSION', '')
  @auth_adapter = nil
end

Instance Attribute Details

#aggregate_shortcutsBoolean

Returns Enable aggregate shortcuts in Rails console (default: false).

Returns:

  • (Boolean)

    Enable aggregate shortcuts in Rails console (default: false)



26
27
28
# File 'lib/yes/core/configuration.rb', line 26

def aggregate_shortcuts
  @aggregate_shortcuts
end

#auth_adapter#call?

Returns Authentication adapter for API controllers. Must respond to #authenticate(request) (returns auth data hash, raises on failure), #verify_token(token) and #error_classes (returns array of error classes).

Returns:

  • (#call, nil)

    Authentication adapter for API controllers. Must respond to #authenticate(request) (returns auth data hash, raises on failure), #verify_token(token) and #error_classes (returns array of error classes).



101
102
103
# File 'lib/yes/core/configuration.rb', line 101

def auth_adapter
  @auth_adapter
end

#cerbos_commands_authorizer_include_metadataBoolean

Returns Whether to include metadata in Cerbos command authorizer responses.

Returns:

  • (Boolean)

    Whether to include metadata in Cerbos command authorizer responses



49
50
51
# File 'lib/yes/core/configuration.rb', line 49

def 
  @cerbos_commands_authorizer_include_metadata
end

#cerbos_principal_data_builder#call

Returns A callable that receives auth_data and returns a principal data hash for Cerbos (commands).

Returns:

  • (#call)

    A callable that receives auth_data and returns a principal data hash for Cerbos (commands)



32
33
34
# File 'lib/yes/core/configuration.rb', line 32

def cerbos_principal_data_builder
  @cerbos_principal_data_builder
end

#cerbos_read_authorizer_actionsArray<String>

Returns Default actions for Cerbos read authorizer.

Returns:

  • (Array<String>)

    Default actions for Cerbos read authorizer



55
56
57
# File 'lib/yes/core/configuration.rb', line 55

def cerbos_read_authorizer_actions
  @cerbos_read_authorizer_actions
end

#cerbos_read_authorizer_include_metadataBoolean

Returns Whether to include metadata in Cerbos read authorizer responses.

Returns:

  • (Boolean)

    Whether to include metadata in Cerbos read authorizer responses



52
53
54
# File 'lib/yes/core/configuration.rb', line 52

def 
  @cerbos_read_authorizer_include_metadata
end

#cerbos_read_authorizer_principal_anonymous_idString

Returns Anonymous principal ID for Cerbos read authorizer.

Returns:

  • (String)

    Anonymous principal ID for Cerbos read authorizer



81
82
83
# File 'lib/yes/core/configuration.rb', line 81

def cerbos_read_authorizer_principal_anonymous_id
  @cerbos_read_authorizer_principal_anonymous_id
end

#cerbos_read_authorizer_resource_id_prefixString

Returns Prefix for Cerbos read authorizer resource ids.

Returns:

  • (String)

    Prefix for Cerbos read authorizer resource ids



58
59
60
# File 'lib/yes/core/configuration.rb', line 58

def cerbos_read_authorizer_resource_id_prefix
  @cerbos_read_authorizer_resource_id_prefix
end

#cerbos_read_principal_data_builderObject



38
39
40
# File 'lib/yes/core/configuration.rb', line 38

def cerbos_read_principal_data_builder
  @cerbos_read_principal_data_builder || @cerbos_principal_data_builder
end

#cerbos_tlsBoolean

Returns Whether to use TLS for Cerbos connections (default: true).

Returns:

  • (Boolean)

    Whether to use TLS for Cerbos connections (default: true)



46
47
48
# File 'lib/yes/core/configuration.rb', line 46

def cerbos_tls
  @cerbos_tls
end

#cerbos_urlString

Returns URL of the Cerbos server.

Returns:

  • (String)

    URL of the Cerbos server



43
44
45
# File 'lib/yes/core/configuration.rb', line 43

def cerbos_url
  @cerbos_url
end

#command_notifier_classesArray<Class>

Returns Command notifier classes to instantiate for batch notifications.

Returns:

  • (Array<Class>)

    Command notifier classes to instantiate for batch notifications



75
76
77
# File 'lib/yes/core/configuration.rb', line 75

def command_notifier_classes
  @command_notifier_classes
end

#error_reporter#call?

Returns A callable error reporter responding to #call(error, context:). When nil, errors are only logged. Example: ->(error, context:) { Sentry.capture_exception(error, extra: context) }.

Returns:

  • (#call, nil)

    A callable error reporter responding to #call(error, context:). When nil, errors are only logged. Example: ->(error, context:) { Sentry.capture_exception(error, extra: context) }



66
67
68
# File 'lib/yes/core/configuration.rb', line 66

def error_reporter
  @error_reporter
end

#loggerObject

Returns Logger instance.

Returns:

  • (Object)

    Logger instance



61
62
63
# File 'lib/yes/core/configuration.rb', line 61

def logger
  @logger
end

#otl_tracerObject?

Returns OpenTelemetry tracer instance. When nil, all tracing is no-op.

Returns:

  • (Object, nil)

    OpenTelemetry tracer instance. When nil, all tracing is no-op.



78
79
80
# File 'lib/yes/core/configuration.rb', line 78

def otl_tracer
  @otl_tracer
end

#payload_store_clientObject?

Returns Payload store client for resolving large payload references.

Returns:

  • (Object, nil)

    Payload store client for resolving large payload references



69
70
71
# File 'lib/yes/core/configuration.rb', line 69

def payload_store_client
  @payload_store_client
end

#process_commands_inlineBoolean

Returns Whether to process commands inline (synchronously) or via ActiveJob.

Returns:

  • (Boolean)

    Whether to process commands inline (synchronously) or via ActiveJob



72
73
74
# File 'lib/yes/core/configuration.rb', line 72

def process_commands_inline
  @process_commands_inline
end

#raise_on_missing_handler_methodBoolean

Returns Whether to raise on missing handler methods in aggregate state.

Returns:

  • (Boolean)

    Whether to raise on missing handler methods in aggregate state



84
85
86
# File 'lib/yes/core/configuration.rb', line 84

def raise_on_missing_handler_method
  @raise_on_missing_handler_method
end

#service_nameString

Returns Service name for telemetry and identification.

Returns:

  • (String)

    Service name for telemetry and identification



93
94
95
# File 'lib/yes/core/configuration.rb', line 93

def service_name
  @service_name
end

#service_versionString

Returns Service version for telemetry.

Returns:

  • (String)

    Service version for telemetry



96
97
98
# File 'lib/yes/core/configuration.rb', line 96

def service_version
  @service_version
end

#subscriptions_heartbeat_intervalInteger

Returns Interval in seconds between heartbeat pings (default: 30).

Returns:

  • (Integer)

    Interval in seconds between heartbeat pings (default: 30)



90
91
92
# File 'lib/yes/core/configuration.rb', line 90

def subscriptions_heartbeat_interval
  @subscriptions_heartbeat_interval
end

#subscriptions_heartbeat_urlString?

Returns URL for subscription heartbeat pings (default: nil, disables heartbeat).

Returns:

  • (String, nil)

    URL for subscription heartbeat pings (default: nil, disables heartbeat)



87
88
89
# File 'lib/yes/core/configuration.rb', line 87

def subscriptions_heartbeat_url
  @subscriptions_heartbeat_url
end

#super_admin_check#call

Returns A callable that receives auth_data and returns boolean indicating super admin status.

Returns:

  • (#call)

    A callable that receives auth_data and returns boolean indicating super admin status



29
30
31
# File 'lib/yes/core/configuration.rb', line 29

def super_admin_check
  @super_admin_check
end

Instance Method Details

#aggregate_class(context_name, aggregate_name, action_name, type) ⇒ Class?

Retrieve a registered class for a given aggregate, action, and type

Examples:

Get a command class

command_class = aggregate_class(:authentication, :user, :create, :command)

Get a read model class

read_model_class = aggregate_class(:authentication, :user, nil, :read_model)

Parameters:

  • context_name (Symbol, String)

    The context for the aggregate

  • aggregate_name (Symbol, String)

    The name of the aggregate

  • action_name (Symbol, String, nil)

    The name of the action (command/event), nil for read_model types

  • type (Symbol)

    The type of the class (:command, :event, :guard_evaluator, :read_model, :draft_read_model)

Returns:

  • (Class, nil)

    The registered class or nil if not found



283
284
285
286
287
288
289
# File 'lib/yes/core/configuration.rb', line 283

def aggregate_class(context_name, aggregate_name, action_name, type)
  if action_name.nil?
    @registered_classes.dig([context_name, aggregate_name], type)
  else
    @registered_classes.dig([context_name, aggregate_name], type, action_name)
  end
end

#all_read_model_class_namesArray<String>

Get all read model class names from registered aggregates

Examples:

read_model_classes = all_read_model_class_names
# Returns: ["UserReadModel", "UserChangesReadModel", "ProfileReadModel", ...]

Returns:

  • (Array<String>)

    Array of read model class names



333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'lib/yes/core/configuration.rb', line 333

def all_read_model_class_names
  list_all_registered_classes.keys.flat_map do |context_aggregate|
    context_name, aggregate_name = context_aggregate
    aggregate_class_name = "#{context_name.to_s.camelize}::#{aggregate_name.to_s.camelize}::Aggregate"

    begin
      aggregate_class = aggregate_class_name.constantize
      models = []

      # Add main read model if it exists
      models << aggregate_class.read_model_name.camelize.to_s if aggregate_class.respond_to?(:read_model_name) && aggregate_class.read_model_name

      # Add changes read model if aggregate is draftable
      models << aggregate_class.changes_read_model_name.camelize.to_s if aggregate_class.respond_to?(:changes_read_model_name) && aggregate_class.changes_read_model_name

      models
    rescue NameError
      # Skip if aggregate class doesn't exist
      []
    end
  end.compact.uniq
end

#all_read_model_classesArray<Class>

Get all read model classes (constantized)

Examples:

read_model_classes = all_read_model_classes
# Returns: [UserReadModel, UserChangesReadModel, ProfileReadModel, ...]

Returns:

  • (Array<Class>)

    Array of read model classes



361
362
363
364
365
366
367
# File 'lib/yes/core/configuration.rb', line 361

def all_read_model_classes
  all_read_model_class_names.filter_map do |class_name|
    class_name.constantize
  rescue NameError
    nil
  end
end

#all_read_model_table_namesArray<String>

Get all read model table names

Examples:

table_names = all_read_model_table_names
# Returns: ["user_read_models", "user_changes_read_models", "profile_read_models", ...]

Returns:

  • (Array<String>)

    Array of read model table names



427
428
429
# File 'lib/yes/core/configuration.rb', line 427

def all_read_model_table_names
  all_read_model_classes.map(&:table_name).uniq
end

#all_read_models_with_aggregate_classesArray<Hash>

Get all read model classes with their associated aggregate classes

Examples:

mappings = all_read_models_with_aggregate_classes
# Returns: [
#   { read_model_class: UserReadModel, aggregate_class: User::Aggregate, is_draft: false },
#   { read_model_class: UserChangesReadModel, aggregate_class: User::Aggregate, is_draft: true }
# ]

Returns:

  • (Array<Hash>)

    Array of hashes with read_model_class, aggregate_class, and is_draft flag



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# File 'lib/yes/core/configuration.rb', line 377

def all_read_models_with_aggregate_classes
  list_all_registered_classes.keys.flat_map do |context_aggregate|
    context_name, aggregate_name = context_aggregate
    aggregate_class_name = "#{context_name.to_s.camelize}::#{aggregate_name.to_s.camelize}::Aggregate"

    begin
      aggregate_class = aggregate_class_name.constantize
      models = []

      # Main read model (not draft)
      if aggregate_class.respond_to?(:read_model_name) && aggregate_class.read_model_name
        begin
          read_model_class = aggregate_class.read_model_name.camelize.constantize
          models << {
            read_model_class: read_model_class,
            aggregate_class: aggregate_class,
            is_draft: false
          }
        rescue NameError
          # Skip if read model class doesn't exist
        end
      end

      # Changes read model (draft)
      if aggregate_class.respond_to?(:changes_read_model_name) && aggregate_class.changes_read_model_name
        begin
          changes_model_class = aggregate_class.changes_read_model_name.camelize.constantize
          models << {
            read_model_class: changes_model_class,
            aggregate_class: aggregate_class,
            is_draft: true
          }
        rescue NameError
          # Skip if changes read model class doesn't exist
        end
      end

      models
    rescue NameError
      # Skip if aggregate class doesn't exist
      []
    end
  end.compact
end

#command_event_mapping(context_name, aggregate_name, command_name) ⇒ Array<Symbol, String>

Retrieve the event names associated with a specific command

Examples:

event_names = command_event_mapping(:authentication, :user, :create)

Parameters:

  • context_name (Symbol, String)

    The context for the aggregate

  • aggregate_name (Symbol, String)

    The name of the aggregate

  • command_name (Symbol, String)

    The name of the command

Returns:

  • (Array<Symbol, String>)

    An array of event names associated with the command, or an empty array if none



256
257
258
# File 'lib/yes/core/configuration.rb', line 256

def command_event_mapping(context_name, aggregate_name, command_name)
  command_event_mappings(context_name, aggregate_name)[command_name] || []
end

#command_event_mappings(context_name, aggregate_name) ⇒ Hash

Retrieve all command-to-event mappings for a specific aggregate

Examples:

mappings = command_event_mappings(:authentication, :user)

Parameters:

  • context_name (Symbol, String)

    The context for the aggregate

  • aggregate_name (Symbol, String)

    The name of the aggregate

Returns:

  • (Hash)

    A hash where keys are command names and values are arrays of event names



244
245
246
247
# File 'lib/yes/core/configuration.rb', line 244

def command_event_mappings(context_name, aggregate_name)
  key = [context_name, aggregate_name]
  @registered_classes[key][:command_event_mappings] || {}
end

#event_classes_for_command(context_name, aggregate_name, command_name) ⇒ Array<Class>

Retrieve the actual event classes associated with a specific command

Examples:

event_classes = event_classes_for_command(:authentication, :user, :create)

Parameters:

  • context_name (Symbol, String)

    The context for the aggregate

  • aggregate_name (Symbol, String)

    The name of the aggregate

  • command_name (Symbol, String)

    The name of the command

Returns:

  • (Array<Class>)

    An array of event classes associated with the command



267
268
269
270
271
# File 'lib/yes/core/configuration.rb', line 267

def event_classes_for_command(context_name, aggregate_name, command_name)
  command_event_mapping(context_name, aggregate_name, command_name).map do |event_name|
    aggregate_class(context_name, aggregate_name, event_name, :event)
  end
end

#guard_evaluator_class(context_name, aggregate_name, command_name) ⇒ Class?

Retrieve a guard evaluator class for a specific command

Examples:

evaluator = guard_evaluator_class(:authentication, :user, :create)

Parameters:

  • context_name (Symbol, String)

    The context for the aggregate

  • aggregate_name (Symbol, String)

    The name of the aggregate

  • command_name (Symbol, String)

    The name of the command

Returns:

  • (Class, nil)

    The registered guard evaluator class or nil if not found



308
309
310
# File 'lib/yes/core/configuration.rb', line 308

def guard_evaluator_class(context_name, aggregate_name, command_name)
  aggregate_class(context_name, aggregate_name, command_name.to_s.underscore.to_sym, :guard_evaluator)
end

#list_aggregate_classes(context_name, aggregate_name) ⇒ Hash

List all registered classes for a specific aggregate in a context

Examples:

classes = list_aggregate_classes("Authentication", "User)

Parameters:

  • context_name (Symbol, String)

    The context for the aggregate

  • aggregate_name (Symbol, String)

    The name of the aggregate

Returns:

  • (Hash)

    A hash of registered classes grouped by type



297
298
299
# File 'lib/yes/core/configuration.rb', line 297

def list_aggregate_classes(context_name, aggregate_name)
  @registered_classes[[context_name, aggregate_name]]
end

#list_all_registered_classesHash

List all registered classes across all aggregates and contexts

Examples:

all_classes = list_all_registered_classes
# Returns:
# {
#   [:authentication, :user] => {
#     command: { create: CreateUserCommand },
#     event: { created: UserCreatedEvent },
#     guard_evaluator: { create: CreateUserGuardEvaluator }
#   }
# }

Returns:

  • (Hash)

    A complete hash of all registered classes



324
325
326
# File 'lib/yes/core/configuration.rb', line 324

def list_all_registered_classes
  @registered_classes
end

#register_aggregate_authorizer_class(context_name, aggregate_name, klass) ⇒ Object

Register an aggregate authorizer class for a specific aggregate

Examples:

register_aggregate_authorizer_class(:authentication, :user, UserAuthorizer)

Parameters:

  • context_name (Symbol, String)

    The context for the aggregate

  • aggregate_name (Symbol, String)

    The name of the aggregate

  • klass (Class)

    The authorizer class to register



209
210
211
212
# File 'lib/yes/core/configuration.rb', line 209

def register_aggregate_authorizer_class(context_name, aggregate_name, klass)
  key = [context_name, aggregate_name]
  @registered_classes[key][:aggregate_authorizer] = klass
end

#register_aggregate_class(context_name, aggregate_name, action_name, type, klass) ⇒ Object

Register a class for a specific aggregate and type

Examples:

Register a command class

register_aggregate_class(:authentication, :user, :create, :command, CreateUserCommand)

Parameters:

  • context_name (Symbol, String)

    The context for the aggregate

  • aggregate_name (Symbol, String)

    The name of the aggregate

  • action_name (Symbol, String)

    The name of the command/event

  • type (Symbol)

    The type (:command, :event, or :guard_evaluator)

  • klass (Class)

    The class to register



142
143
144
145
# File 'lib/yes/core/configuration.rb', line 142

def register_aggregate_class(context_name, aggregate_name, action_name, type, klass)
  key = [context_name, aggregate_name]
  @registered_classes[key][type][action_name] = klass
end

#register_command_authorizer_class(context_name, aggregate_name, command_name, klass) ⇒ Object

Register a command authorizer class for a specific aggregate

Examples:

register_command_authorizer_class(:sales, :user, :create, CreateUserAuthorizer)

Parameters:

  • context_name (Symbol, String)

    The context for the aggregate

  • aggregate_name (Symbol, String)

    The name of the aggregate

  • command_name (Symbol, String)

    The name of the command

  • klass (Class)

    The authorizer class to register



221
222
223
# File 'lib/yes/core/configuration.rb', line 221

def register_command_authorizer_class(context_name, aggregate_name, command_name, klass)
  register_aggregate_class(context_name, aggregate_name, command_name, :authorizer, klass)
end

#register_command_class(context_name, aggregate_name, command_name, klass) ⇒ Object

Register a command class for a specific aggregate

Examples:

register_command_class(:authentication, :user, :create, CreateUserCommand)

Parameters:

  • context_name (Symbol, String)

    The context for the aggregate

  • aggregate_name (Symbol, String)

    The name of the aggregate

  • command_name (Symbol, String)

    The name of the command

  • klass (Class)

    The class to register



177
178
179
# File 'lib/yes/core/configuration.rb', line 177

def register_command_class(context_name, aggregate_name, command_name, klass)
  register_aggregate_class(context_name, aggregate_name, command_name, :command, klass)
end

#register_command_events(context_name, aggregate_name, command_name, event_names) ⇒ Object

Register the event(s) associated with a specific command

Examples:

register_command_events(:authentication, :user, :create, [:user_created, :welcome_email_sent])

Parameters:

  • context_name (Symbol, String)

    The context for the aggregate

  • aggregate_name (Symbol, String)

    The name of the aggregate

  • command_name (Symbol, String)

    The name of the command

  • event_names (Array<Symbol, String>)

    An array of event names



232
233
234
235
236
# File 'lib/yes/core/configuration.rb', line 232

def register_command_events(context_name, aggregate_name, command_name, event_names)
  key = [context_name, aggregate_name]
  mappings = @registered_classes[key][:command_event_mappings] ||= {}
  mappings[command_name] = event_names
end

#register_event_class(context_name, aggregate_name, event_name, klass) ⇒ Object

Register an event class for a specific aggregate

Examples:

register_event_class(:authentication, :user, :created, UserCreatedEvent)

Parameters:

  • context_name (Symbol, String)

    The context for the aggregate

  • aggregate_name (Symbol, String)

    The name of the aggregate

  • event_name (Symbol, String)

    The name of the event

  • klass (Class)

    The class to register



188
189
190
# File 'lib/yes/core/configuration.rb', line 188

def register_event_class(context_name, aggregate_name, event_name, klass)
  register_aggregate_class(context_name, aggregate_name, event_name, :event, klass)
end

#register_guard_evaluator_class(context_name, aggregate_name, command_name, klass) ⇒ Object

Register a guard evaluator class for a specific aggregate

Examples:

register_guard_evaluator_class(:authentication, :user, :create, CreateUserGuardEvaluator)

Parameters:

  • context_name (Symbol, String)

    The context for the aggregate

  • aggregate_name (Symbol, String)

    The name of the aggregate

  • command_name (Symbol, String)

    The name of the command

  • klass (Class)

    The class to register



199
200
201
# File 'lib/yes/core/configuration.rb', line 199

def register_guard_evaluator_class(context_name, aggregate_name, command_name, klass)
  register_aggregate_class(context_name, aggregate_name, command_name, :guard_evaluator, klass)
end

#register_read_model_class(context_name, aggregate_name, klass, draft: false) ⇒ Object

Register a read model class for a specific aggregate

Examples:

register_read_model_class(:authentication, :user, UserReadModel)

Parameters:

  • context_name (Symbol, String)

    The context for the aggregate

  • aggregate_name (Symbol, String)

    The name of the aggregate

  • klass (Class)

    The class to register



153
154
155
156
157
# File 'lib/yes/core/configuration.rb', line 153

def register_read_model_class(context_name, aggregate_name, klass, draft: false)
  key = [context_name, aggregate_name]
  read_model_key = draft ? :draft_read_model : :read_model
  @registered_classes[key][read_model_key] = klass
end

#register_read_model_filter_class(context_name, aggregate_name, klass) ⇒ Object

Register a read model filter class for a specific aggregate

Examples:

register_read_model_filter_class(:authentication, :user, UserReadModelFilter)

Parameters:

  • context_name (Symbol, String)

    The context for the aggregate

  • aggregate_name (Symbol, String)

    The name of the aggregate

  • klass (Class)

    The class to register



165
166
167
168
# File 'lib/yes/core/configuration.rb', line 165

def register_read_model_filter_class(context_name, aggregate_name, klass)
  key = [context_name, aggregate_name]
  @registered_classes[key][:read_model_filter] = klass
end