Class: Karafka::Core::Configurable::Node

Inherits:
Object
  • Object
show all
Defined in:
lib/karafka/core/configurable/node.rb

Overview

Single non-leaf node This is a core component for the configurable settings

The idea here is simple: we collect settings (leafs) and children (nodes) information and we only compile/initialize the values prior to user running the ‘#configure` API. This API needs to run prior to using the result stuff even if there is nothing to configure

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(node_name, nestings = ->(_) {}, evaluate: true) ⇒ Node

Returns a new instance of Node.

Parameters:

  • node_name (Symbol)

    node name

  • nestings (Proc) (defaults to: ->(_) {})

    block for nested settings

  • evaluate (Boolean) (defaults to: true)

    when false, skip evaluating the nestings block. Used by deep_dup to avoid re-creating children that will be overwritten immediately.



65
66
67
68
69
70
71
72
73
# File 'lib/karafka/core/configurable/node.rb', line 65

def initialize(node_name, nestings = ->(_) {}, evaluate: true)
  @node_name = node_name
  @children = []
  @nestings = nestings
  @compiled = false
  @configs_refs = {}
  @local_defs = {}
  instance_eval(&nestings) if evaluate
end

Instance Attribute Details

#childrenObject

We need to be able to redefine children for deep copy



16
17
18
# File 'lib/karafka/core/configurable/node.rb', line 16

def children
  @children
end

#nestingsObject (readonly)

Returns the value of attribute nestings.



13
14
15
# File 'lib/karafka/core/configurable/node.rb', line 13

def nestings
  @nestings
end

#node_nameObject (readonly)

Returns the value of attribute node_name.



13
14
15
# File 'lib/karafka/core/configurable/node.rb', line 13

def node_name
  @node_name
end

Class Method Details

.newObject

Builds each node through its own anonymous subclass. Since setting values are mirrored into instance variables for fast access and each node layout carries a different set of them, instantiating nodes directly from this class would grow its object shape variations past the Ruby limit, degrading ivar access for all nodes. A subclass per layout keeps shape variations per class minimal (late ‘setting` calls after inheritance or runtime `register` calls may add a few more, staying well under the limit). `#deep_dup` reuses the subclass of its template, so duplicated configs share shapes as well.



56
57
58
# File 'lib/karafka/core/configurable/node.rb', line 56

def new(...)
  equal?(Node) ? Class.new(self).new(...) : super
end

Instance Method Details

#compileObject

Note:

It runs once, after things are compiled, they will not be recompiled again

Converts the settings definitions into end children



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/karafka/core/configurable/node.rb', line 188

def compile
  @children.each do |value|
    # Do not redefine something that was already set during compilation
    # This will allow us to reconfigure things and skip override with defaults
    skippable = @configs_refs.key?(value.node_name) || (value.is_a?(Leaf) && value.compiled?)
    lazy_leaf = value.is_a?(Leaf) && value.lazy?

    # Do not create accessor for leafs that are lazy as they will get a custom method
    # created instead
    build_accessors(value) unless lazy_leaf

    next if skippable

    initialized = if value.is_a?(Leaf)
      value.compiled = true

      if value.constructor && value.lazy?
        false
      elsif value.constructor
        call_constructor(value)
      else
        value.default
      end
    else
      value.compile
      value
    end

    if lazy_leaf && !initialized
      build_dynamic_accessor(value)
    else
      config_write(value.node_name, initialized)
    end
  end

  @compiled = true
end

#configure {|_self| ... } ⇒ Node

Allows for the configuration and setup of the settings

Compile settings, allow for overrides via yielding

Yields:

  • (_self)

Yield Parameters:

Returns:

  • (Node)

    returns self after configuration



104
105
106
107
108
# File 'lib/karafka/core/configurable/node.rb', line 104

def configure
  compile if !@compiled || node_name == :root
  yield(self) if block_given?
  self
end

#deep_dupNode

Deep copies all the children nodes to allow us for templates building on a class level and non-side-effect usage on an instance/inherited.

Returns:

  • (Node)

    duplicated node



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/karafka/core/configurable/node.rb', line 138

def deep_dup
  # Same-layout nodes reuse the class of their template so they share object shapes
  dupped = self.class.new(node_name, nestings, evaluate: false)

  children.each do |value|
    dupped.children << if value.is_a?(Leaf)
      # After inheritance we need to reload the state so the leafs are recompiled again
      value = value.dup
      value.compiled = false
      value
    else
      value.deep_dup
    end
  end

  dupped
end

#register(name, value) ⇒ Object

Registers a key-value pair as a setting on an already-compiled node without going through the static ‘setting` DSL. Useful for dynamic registries (e.g. named clusters) where the keys are not known at class-load time.

Unlike ‘setting`, which is designed to be called at class-definition time, `register` is safe to call at runtime because it:

- appends a pre-compiled Leaf so `deep_dup` and `to_h` include it
- sets `@configs_refs` directly so the reader accessor returns the value immediately
- builds reader/writer accessors via the same `build_accessors` path

Raises ‘ArgumentError` if the name is already registered to prevent silent overwrites.

Parameters:

  • name (Symbol, String)

    setting name

  • value (Object)

    the setting value assigned immediately; also used as the default when the node is deep-duped and recompiled on a new instance

Raises:

  • (ArgumentError)

    when the name is already taken or reserved for the node internal state



173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/karafka/core/configurable/node.rb', line 173

def register(name, value)
  name = name.to_sym

  prevent_reserved_names!(name)

  raise ArgumentError, "#{name} is already registered" if @configs_refs.key?(name)

  leaf = Leaf.new(name, value, nil, true, false)
  @children << leaf
  build_accessors(leaf)
  config_write(name, value)
end

#setting(node_name, default: nil, constructor: nil, lazy: false, &block) ⇒ Object

Allows for a single leaf or nested node definition

Parameters:

  • node_name (Symbol, String)

    setting or nested node name

  • default (Object) (defaults to: nil)

    default value

  • constructor (#call, nil) (defaults to: nil)

    callable or nil

  • lazy (Boolean) (defaults to: false)

    is this a lazy leaf

  • block (Proc)

    block for nested settings

Raises:

  • (ArgumentError)

    when the name is reserved for the node internal state



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/karafka/core/configurable/node.rb', line 83

def setting(node_name, default: nil, constructor: nil, lazy: false, &block)
  # Symbolize at definition time (same as `#register`) so the config store, accessors,
  # `#to_h` and the compile state checks all agree on the key type also when a String
  # name is provided
  node_name = node_name.to_sym

  prevent_reserved_names!(node_name)

  @children << if block
    Node.new(node_name, block)
  else
    Leaf.new(node_name, default, constructor, false, lazy)
  end

  compile
end

#to_hHash

Returns frozen config hash representation.

Returns:

  • (Hash)

    frozen config hash representation



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/karafka/core/configurable/node.rb', line 111

def to_h
  config = {}

  @children.each do |value|
    config[value.node_name] = if value.is_a?(Leaf)
      result = if @configs_refs.key?(value.node_name)
        @configs_refs[value.node_name]
      elsif value.constructor
        value.constructor.call
      elsif value.default
        value.default
      end

      # We need to check if value is not a result node for cases
      # where we merge additional config
      result.is_a?(Node) ? result.to_h : result
    else
      value.to_h
    end
  end

  config.freeze
end