cdc-core 0.1.0 → 0.1.1
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 +4 -4
- data/CHANGELOG.md +15 -7
- data/README.md +105 -19
- data/lib/cdc/core/composite_processor.rb +39 -14
- data/lib/cdc/core/errors.rb +9 -0
- data/lib/cdc/core/event_metadata.rb +4 -1
- data/lib/cdc/core/event_position.rb +48 -0
- data/lib/cdc/core/null_observer.rb +12 -0
- data/lib/cdc/core/observer.rb +123 -0
- data/lib/cdc/core/operation.rb +2 -0
- data/lib/cdc/core/ordering_key.rb +40 -0
- data/lib/cdc/core/ordering_policy.rb +106 -0
- data/lib/cdc/core/ordering_scope.rb +51 -0
- data/lib/cdc/core/pipeline.rb +23 -4
- data/lib/cdc/core/processor.rb +39 -0
- data/lib/cdc/core/processor_result.rb +96 -2
- data/lib/cdc/core/router.rb +91 -0
- data/lib/cdc/core/source_adapter.rb +41 -0
- data/lib/cdc/core/version.rb +1 -1
- data/lib/cdc/core.rb +12 -3
- data/sig/cdc/core/composite_processor.rbs +12 -1
- data/sig/cdc/core/errors.rbs +12 -0
- data/sig/cdc/core/event_position.rbs +21 -0
- data/sig/cdc/core/null_observer.rbs +8 -0
- data/sig/cdc/core/observer.rbs +25 -0
- data/sig/cdc/core/ordering_key.rbs +16 -0
- data/sig/cdc/core/ordering_policy.rbs +27 -0
- data/sig/cdc/core/ordering_scope.rbs +19 -0
- data/sig/cdc/core/pipeline.rbs +10 -1
- data/sig/cdc/core/processor.rbs +31 -0
- data/sig/cdc/core/processor_result.rbs +50 -1
- data/sig/cdc/core/router.rbs +24 -0
- data/sig/cdc/core/source_adapter.rbs +10 -0
- metadata +17 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CDC
|
|
4
|
+
module Core
|
|
5
|
+
# Canonical ordering scopes used by the CDC ecosystem.
|
|
6
|
+
#
|
|
7
|
+
# Ordering scopes describe how related events should be grouped when a
|
|
8
|
+
# runtime or sink needs to preserve relative order.
|
|
9
|
+
module OrderingScope
|
|
10
|
+
# Preserve exact stream order.
|
|
11
|
+
GLOBAL = :global
|
|
12
|
+
# Preserve transaction order and boundaries.
|
|
13
|
+
TRANSACTION = :transaction
|
|
14
|
+
# Preserve ordering per relation/table.
|
|
15
|
+
RELATION = :relation
|
|
16
|
+
# Preserve ordering per primary key.
|
|
17
|
+
PRIMARY_KEY = :primary_key
|
|
18
|
+
# Do not impose a strict ordering guarantee.
|
|
19
|
+
NONE = :none
|
|
20
|
+
|
|
21
|
+
# All supported ordering scopes.
|
|
22
|
+
SUPPORTED = Ractor.make_shareable([GLOBAL, TRANSACTION, RELATION, PRIMARY_KEY, NONE].freeze)
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
# Convert a scope-like value into a supported ordering scope symbol.
|
|
27
|
+
#
|
|
28
|
+
# @param scope [#to_sym] scope to normalize
|
|
29
|
+
# @return [Symbol] one of SUPPORTED
|
|
30
|
+
# @raise [InvalidOrderingScopeError] when the scope is not supported
|
|
31
|
+
def normalize(scope)
|
|
32
|
+
value = scope.to_sym
|
|
33
|
+
return value if SUPPORTED.include?(value)
|
|
34
|
+
|
|
35
|
+
raise InvalidOrderingScopeError, "unsupported CDC ordering scope: #{scope.inspect}"
|
|
36
|
+
rescue NoMethodError
|
|
37
|
+
raise InvalidOrderingScopeError, "unsupported CDC ordering scope: #{scope.inspect}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Check whether a scope-like value is supported.
|
|
41
|
+
#
|
|
42
|
+
# @param scope [#to_sym] scope to check
|
|
43
|
+
# @return [Boolean] true when the value normalizes to a supported scope
|
|
44
|
+
def supported?(scope)
|
|
45
|
+
SUPPORTED.include?(scope.to_sym)
|
|
46
|
+
rescue NoMethodError
|
|
47
|
+
false
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/cdc/core/pipeline.rb
CHANGED
|
@@ -10,15 +10,18 @@ module CDC
|
|
|
10
10
|
class Pipeline
|
|
11
11
|
# @return [#process] processor invoked for matching events
|
|
12
12
|
# @return [Array<Filter>] filters that must all match before processing
|
|
13
|
-
|
|
13
|
+
# @return [Observer] observer notified of dispatch events
|
|
14
|
+
attr_reader :processor, :filters, :observer
|
|
14
15
|
|
|
15
16
|
# Build a pipeline.
|
|
16
17
|
#
|
|
17
18
|
# @param processor [#process] processor for matching events
|
|
18
19
|
# @param filters [Array<Filter>] filters applied before processing
|
|
19
|
-
|
|
20
|
+
# @param observer [Observer, nil] instrumentation observer
|
|
21
|
+
def initialize(processor:, filters: [], observer: NullObserver::INSTANCE)
|
|
20
22
|
@processor = processor
|
|
21
23
|
@filters = filters.freeze
|
|
24
|
+
@observer = observer || NullObserver::INSTANCE
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
# Process one event through the pipeline.
|
|
@@ -26,11 +29,16 @@ module CDC
|
|
|
26
29
|
# @param event [ChangeEvent] event to process
|
|
27
30
|
# @return [ProcessorResult]
|
|
28
31
|
def process(event)
|
|
32
|
+
observer.dispatch_started(event)
|
|
29
33
|
return ProcessorResult.skipped(event, metadata: { reason: 'filtered' }) unless matches?(event)
|
|
30
34
|
|
|
31
|
-
normalize_result(processor.process(event), event)
|
|
35
|
+
result = normalize_result(processor.process(event), event)
|
|
36
|
+
observe_result(result)
|
|
37
|
+
result
|
|
32
38
|
rescue StandardError => e
|
|
33
|
-
ProcessorResult.failure(e, event:)
|
|
39
|
+
result = ProcessorResult.failure(e, event:, processor: processor.class.name)
|
|
40
|
+
observer.dispatch_failed(result)
|
|
41
|
+
result
|
|
34
42
|
end
|
|
35
43
|
|
|
36
44
|
# Process many events in order.
|
|
@@ -61,6 +69,17 @@ module CDC
|
|
|
61
69
|
|
|
62
70
|
result ? ProcessorResult.success(event) : ProcessorResult.skipped(event)
|
|
63
71
|
end
|
|
72
|
+
|
|
73
|
+
def observe_result(result)
|
|
74
|
+
case result.status
|
|
75
|
+
when :success
|
|
76
|
+
observer.dispatch_succeeded(result)
|
|
77
|
+
when :failure
|
|
78
|
+
observer.dispatch_failed(result)
|
|
79
|
+
when :skipped
|
|
80
|
+
observer.dispatch_skipped(result)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
64
83
|
end
|
|
65
84
|
end
|
|
66
85
|
end
|
data/lib/cdc/core/processor.rb
CHANGED
|
@@ -29,6 +29,45 @@ module CDC
|
|
|
29
29
|
self.class.ractor_safe?
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# Start the processor.
|
|
33
|
+
#
|
|
34
|
+
# Runtime layers can call this before dispatch begins. The default
|
|
35
|
+
# implementation is a no-op.
|
|
36
|
+
#
|
|
37
|
+
# @return [self]
|
|
38
|
+
def start
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Stop the processor.
|
|
43
|
+
#
|
|
44
|
+
# Runtime layers can call this during shutdown. The default implementation
|
|
45
|
+
# is a no-op.
|
|
46
|
+
#
|
|
47
|
+
# @return [self]
|
|
48
|
+
def stop
|
|
49
|
+
self
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Flush any buffered work.
|
|
53
|
+
#
|
|
54
|
+
# Runtime layers can call this before shutdown or checkpoints. The
|
|
55
|
+
# default implementation is a no-op.
|
|
56
|
+
#
|
|
57
|
+
# @return [self]
|
|
58
|
+
def flush
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Whether the processor is healthy and ready to accept work.
|
|
63
|
+
#
|
|
64
|
+
# The default implementation assumes the processor is healthy.
|
|
65
|
+
#
|
|
66
|
+
# @return [Boolean]
|
|
67
|
+
def healthy?
|
|
68
|
+
true
|
|
69
|
+
end
|
|
70
|
+
|
|
32
71
|
# Process one event.
|
|
33
72
|
#
|
|
34
73
|
# Subclasses must override this method.
|
|
@@ -8,6 +8,9 @@ module CDC
|
|
|
8
8
|
# successful processing, skipped events, and failures without relying on
|
|
9
9
|
# processor-specific return values.
|
|
10
10
|
class ProcessorResult
|
|
11
|
+
# Allowed result statuses.
|
|
12
|
+
VALID_STATUSES = Ractor.make_shareable(%i[success failure skipped].freeze)
|
|
13
|
+
|
|
11
14
|
# @return [Symbol] result status
|
|
12
15
|
# @return [ChangeEvent, nil] event associated with the result
|
|
13
16
|
# @return [Exception, nil] failure error, when status is :failure
|
|
@@ -25,9 +28,24 @@ module CDC
|
|
|
25
28
|
#
|
|
26
29
|
# @param error [Exception] processor error
|
|
27
30
|
# @param event [ChangeEvent, nil] event being processed
|
|
31
|
+
# @param reason [String, nil] human-readable failure reason
|
|
32
|
+
# @param retryable [Boolean, nil] whether the failure can be retried
|
|
33
|
+
# @param processor [String, nil] processor name associated with the failure
|
|
34
|
+
# @param failed_at [String, nil] timestamp for when the failure occurred
|
|
28
35
|
# @param metadata [Hash, EventMetadata] result metadata
|
|
29
36
|
# @return [ProcessorResult]
|
|
30
|
-
def self.failure(error, event: nil,
|
|
37
|
+
def self.failure(error, event: nil, reason: nil, retryable: nil, processor: nil, failed_at: nil,
|
|
38
|
+
metadata: nil)
|
|
39
|
+
base_metadata = metadata.nil? ? EventMetadata.new.to_h : metadata.to_h
|
|
40
|
+
failure_metadata = base_metadata.merge(
|
|
41
|
+
reason: reason || error.message,
|
|
42
|
+
retryable: retryable,
|
|
43
|
+
processor: processor || error.class.name,
|
|
44
|
+
failed_at: failed_at || Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S.%6NZ')
|
|
45
|
+
).compact
|
|
46
|
+
|
|
47
|
+
new(:failure, event:, error:, metadata: failure_metadata)
|
|
48
|
+
end
|
|
31
49
|
|
|
32
50
|
# Build a skipped result.
|
|
33
51
|
#
|
|
@@ -43,7 +61,7 @@ module CDC
|
|
|
43
61
|
# @param error [Exception, nil] associated failure
|
|
44
62
|
# @param metadata [Hash, EventMetadata] result metadata
|
|
45
63
|
def initialize(status, event: nil, error: nil, metadata: {})
|
|
46
|
-
@status = status
|
|
64
|
+
@status = normalize_status(status)
|
|
47
65
|
@event = event
|
|
48
66
|
@error = error
|
|
49
67
|
@metadata = metadata.is_a?(EventMetadata) ? metadata : EventMetadata.new(metadata)
|
|
@@ -58,6 +76,82 @@ module CDC
|
|
|
58
76
|
|
|
59
77
|
# @return [Boolean] true when status is :skipped
|
|
60
78
|
def skipped? = status == :skipped
|
|
79
|
+
|
|
80
|
+
# Human-readable failure reason, when present.
|
|
81
|
+
#
|
|
82
|
+
# @return [String, nil]
|
|
83
|
+
def failure_reason
|
|
84
|
+
metadata[:reason]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Whether the failure is retryable.
|
|
88
|
+
#
|
|
89
|
+
# @return [Boolean]
|
|
90
|
+
def retryable?
|
|
91
|
+
metadata[:retryable] == true
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Name of the processor associated with the failure, when present.
|
|
95
|
+
#
|
|
96
|
+
# @return [String, nil]
|
|
97
|
+
def processor_name
|
|
98
|
+
metadata[:processor]
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Timestamp for when the failure occurred, when present.
|
|
102
|
+
#
|
|
103
|
+
# @return [String, nil]
|
|
104
|
+
def failed_at
|
|
105
|
+
metadata[:failed_at]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Error class name, when present.
|
|
109
|
+
#
|
|
110
|
+
# @return [String, nil]
|
|
111
|
+
def error_class
|
|
112
|
+
error&.class&.name
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Error message, when present.
|
|
116
|
+
#
|
|
117
|
+
# @return [String, nil]
|
|
118
|
+
def error_message
|
|
119
|
+
error&.message
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Error backtrace, when present.
|
|
123
|
+
#
|
|
124
|
+
# @return [Array<String>]
|
|
125
|
+
def error_backtrace
|
|
126
|
+
Array(error&.backtrace)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Convert the result into a shareable hash.
|
|
130
|
+
#
|
|
131
|
+
# @return [Hash{String=>Object,nil}]
|
|
132
|
+
def to_h
|
|
133
|
+
payload = {
|
|
134
|
+
'status' => status,
|
|
135
|
+
'event' => event&.to_h,
|
|
136
|
+
'error_class' => error_class,
|
|
137
|
+
'error_message' => error_message,
|
|
138
|
+
'error_backtrace' => error_backtrace,
|
|
139
|
+
'metadata' => metadata.to_h
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
Ractor.make_shareable(payload.freeze)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
def normalize_status(status)
|
|
148
|
+
value = status.to_sym
|
|
149
|
+
return value if VALID_STATUSES.include?(value)
|
|
150
|
+
|
|
151
|
+
raise ArgumentError, "unsupported processor result status: #{status.inspect}"
|
|
152
|
+
rescue NoMethodError
|
|
153
|
+
raise ArgumentError, "unsupported processor result status: #{status.inspect}"
|
|
154
|
+
end
|
|
61
155
|
end
|
|
62
156
|
end
|
|
63
157
|
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CDC
|
|
4
|
+
module Core
|
|
5
|
+
# Routes CDC work items to the appropriate handler.
|
|
6
|
+
#
|
|
7
|
+
# Router keeps the dispatch vocabulary in core while leaving execution
|
|
8
|
+
# strategy to the caller. It can route single change events, transaction
|
|
9
|
+
# envelopes, and arrays of events.
|
|
10
|
+
class Router
|
|
11
|
+
# @return [#process] handler for individual change events
|
|
12
|
+
# @return [#process, nil] handler for transaction envelopes
|
|
13
|
+
# @return [Observer] observer notified of dispatch events
|
|
14
|
+
attr_reader :processor, :transaction_processor, :observer
|
|
15
|
+
|
|
16
|
+
# Build a router.
|
|
17
|
+
#
|
|
18
|
+
# @param processor [#process] handler for change events
|
|
19
|
+
# @param transaction_processor [#process, nil] handler for transaction envelopes
|
|
20
|
+
# @param observer [Observer, nil] instrumentation observer
|
|
21
|
+
def initialize(processor:, transaction_processor: nil, observer: NullObserver::INSTANCE)
|
|
22
|
+
@processor = processor
|
|
23
|
+
@transaction_processor = transaction_processor
|
|
24
|
+
@observer = observer || NullObserver::INSTANCE
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Process a CDC work item.
|
|
28
|
+
#
|
|
29
|
+
# @param item [ChangeEvent, TransactionEnvelope, Array<ChangeEvent>]
|
|
30
|
+
# @return [Object]
|
|
31
|
+
# @raise [UnsupportedWorkItemError] when the item cannot be routed
|
|
32
|
+
def process(item)
|
|
33
|
+
observer.dispatch_started(item)
|
|
34
|
+
case item
|
|
35
|
+
when ChangeEvent
|
|
36
|
+
result = processor.process(item)
|
|
37
|
+
observe_result(result)
|
|
38
|
+
result
|
|
39
|
+
when TransactionEnvelope
|
|
40
|
+
route_transaction(item)
|
|
41
|
+
when Array
|
|
42
|
+
route_many(item)
|
|
43
|
+
else
|
|
44
|
+
raise UnsupportedWorkItemError, "unsupported CDC work item: #{item.class}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# Route a transaction envelope to the configured transaction processor.
|
|
51
|
+
#
|
|
52
|
+
# @param transaction [TransactionEnvelope]
|
|
53
|
+
# @return [Object]
|
|
54
|
+
def route_transaction(transaction)
|
|
55
|
+
return transaction_processor.process(transaction) if transaction_processor
|
|
56
|
+
|
|
57
|
+
raise UnsupportedWorkItemError,
|
|
58
|
+
"unsupported CDC work item: #{transaction.class} (transaction processor not configured)"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Route many change events through the configured processor.
|
|
62
|
+
#
|
|
63
|
+
# @param items [Array]
|
|
64
|
+
# @return [Object]
|
|
65
|
+
def route_many(items)
|
|
66
|
+
unless items.all?(ChangeEvent)
|
|
67
|
+
raise UnsupportedWorkItemError, "unsupported CDC work item: Array(#{items.first.class})"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
return processor.process_many(items) if processor.respond_to?(:process_many)
|
|
71
|
+
|
|
72
|
+
items.map { |event| processor.process(event) }.freeze
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def observe_result(result)
|
|
76
|
+
return result unless result.is_a?(ProcessorResult)
|
|
77
|
+
|
|
78
|
+
case result.status
|
|
79
|
+
when :success
|
|
80
|
+
observer.dispatch_succeeded(result)
|
|
81
|
+
when :failure
|
|
82
|
+
observer.dispatch_failed(result)
|
|
83
|
+
when :skipped
|
|
84
|
+
observer.dispatch_skipped(result)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
result
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CDC
|
|
4
|
+
module Core
|
|
5
|
+
# Abstract base class for source adapters that normalize upstream payloads
|
|
6
|
+
# into CDC core domain objects.
|
|
7
|
+
#
|
|
8
|
+
# SourceAdapter is intentionally narrow. It does not own transport,
|
|
9
|
+
# polling, connection management, worker scheduling, or protocol parsing.
|
|
10
|
+
# It only defines the contract for turning source-specific inputs into
|
|
11
|
+
# {ChangeEvent}, {TransactionEnvelope}, or arrays of those objects.
|
|
12
|
+
#
|
|
13
|
+
# The concrete PostgreSQL implementation currently lives in the pgoutput*
|
|
14
|
+
# family. This class only defines the shared boundary other adapters can
|
|
15
|
+
# implement later.
|
|
16
|
+
class SourceAdapter
|
|
17
|
+
# Normalize one source payload into CDC core objects.
|
|
18
|
+
#
|
|
19
|
+
# Subclasses must override this method.
|
|
20
|
+
#
|
|
21
|
+
# @param _input [Object] source-specific payload
|
|
22
|
+
# @return [ChangeEvent, TransactionEnvelope, Array<ChangeEvent>, Array<TransactionEnvelope>]
|
|
23
|
+
# @raise [NotImplementedError] when not implemented by a subclass
|
|
24
|
+
def normalize(_input)
|
|
25
|
+
raise NotImplementedError, "#{self.class} must implement #normalize"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Normalize many source payloads into CDC core objects.
|
|
29
|
+
#
|
|
30
|
+
# The default implementation maps each input through {#normalize} and
|
|
31
|
+
# flattens one level so adapters can return a single object or a batch of
|
|
32
|
+
# objects for each payload.
|
|
33
|
+
#
|
|
34
|
+
# @param inputs [Enumerable] source-specific payloads
|
|
35
|
+
# @return [Array]
|
|
36
|
+
def normalize_many(inputs)
|
|
37
|
+
Array(inputs).flat_map { |input| normalize(input) }.freeze
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/cdc/core/version.rb
CHANGED
data/lib/cdc/core.rb
CHANGED
|
@@ -5,12 +5,20 @@ require_relative 'core/errors'
|
|
|
5
5
|
require_relative 'core/operation'
|
|
6
6
|
require_relative 'core/column_change'
|
|
7
7
|
require_relative 'core/event_metadata'
|
|
8
|
+
require_relative 'core/ordering_scope'
|
|
9
|
+
require_relative 'core/event_position'
|
|
10
|
+
require_relative 'core/ordering_key'
|
|
11
|
+
require_relative 'core/ordering_policy'
|
|
8
12
|
require_relative 'core/change_event'
|
|
9
13
|
require_relative 'core/transaction_envelope'
|
|
14
|
+
require_relative 'core/source_adapter'
|
|
10
15
|
require_relative 'core/processor_result'
|
|
11
16
|
require_relative 'core/processor'
|
|
12
17
|
require_relative 'core/composite_processor'
|
|
18
|
+
require_relative 'core/observer'
|
|
19
|
+
require_relative 'core/null_observer'
|
|
13
20
|
require_relative 'core/filter'
|
|
21
|
+
require_relative 'core/router'
|
|
14
22
|
require_relative 'core/pipeline'
|
|
15
23
|
|
|
16
24
|
# Top-level namespace for Change Data Capture libraries.
|
|
@@ -18,9 +26,10 @@ module CDC
|
|
|
18
26
|
# Database-agnostic Change Data Capture domain primitives.
|
|
19
27
|
#
|
|
20
28
|
# CDC::Core intentionally contains only lightweight runtime abstractions:
|
|
21
|
-
# events, metadata, processors, filters, pipelines, and
|
|
22
|
-
# Transport, PostgreSQL protocol parsing, and value
|
|
23
|
-
# gems so this layer can remain independently
|
|
29
|
+
# events, metadata, source adapters, processors, filters, pipelines, and
|
|
30
|
+
# processor results. Transport, PostgreSQL protocol parsing, and value
|
|
31
|
+
# decoding live in sibling gems so this layer can remain independently
|
|
32
|
+
# useful.
|
|
24
33
|
module Core
|
|
25
34
|
end
|
|
26
35
|
end
|
|
@@ -10,6 +10,8 @@ module CDC
|
|
|
10
10
|
|
|
11
11
|
@fail_fast: untyped
|
|
12
12
|
|
|
13
|
+
@observer: untyped
|
|
14
|
+
|
|
13
15
|
# @return [Array<Processor>] processors executed for each event
|
|
14
16
|
# @return [Boolean] whether processing stops on the first failure
|
|
15
17
|
attr_reader processors: untyped
|
|
@@ -18,11 +20,15 @@ module CDC
|
|
|
18
20
|
# @return [Boolean] whether processing stops on the first failure
|
|
19
21
|
attr_reader fail_fast: untyped
|
|
20
22
|
|
|
23
|
+
# @return [Observer] observer notified of dispatch events
|
|
24
|
+
attr_reader observer: untyped
|
|
25
|
+
|
|
21
26
|
# Build a composite processor.
|
|
22
27
|
#
|
|
23
28
|
# @param processors [Array<#process>] processors to execute
|
|
24
29
|
# @param fail_fast [Boolean] whether to stop after the first failure
|
|
25
|
-
|
|
30
|
+
# @param observer [Observer, nil] instrumentation observer
|
|
31
|
+
def initialize: (untyped processors, ?fail_fast: bool, ?observer: untyped?) -> void
|
|
26
32
|
|
|
27
33
|
# Process an event through each configured processor.
|
|
28
34
|
#
|
|
@@ -48,6 +54,11 @@ module CDC
|
|
|
48
54
|
# @param event [ChangeEvent] processed event
|
|
49
55
|
# @return [ProcessorResult]
|
|
50
56
|
def normalize_result: (untyped result, untyped event) -> untyped
|
|
57
|
+
|
|
58
|
+
def collect_results: (untyped event) -> untyped
|
|
59
|
+
def process_with: (untyped processor, untyped event) -> untyped
|
|
60
|
+
def observe_results: (untyped results) -> untyped
|
|
61
|
+
def freeze_results: (untyped results) -> untyped
|
|
51
62
|
end
|
|
52
63
|
end
|
|
53
64
|
end
|
data/sig/cdc/core/errors.rbs
CHANGED
|
@@ -8,6 +8,18 @@ module CDC
|
|
|
8
8
|
class InvalidOperationError < Error
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
# Raised when an ordering scope cannot be normalized to a supported value.
|
|
12
|
+
class InvalidOrderingScopeError < Error
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Raised when an ordering position cannot be normalized to a supported value.
|
|
16
|
+
class InvalidOrderingPositionError < Error
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Raised when a router receives an unsupported CDC work item.
|
|
20
|
+
class UnsupportedWorkItemError < Error
|
|
21
|
+
end
|
|
22
|
+
|
|
11
23
|
# Raised by processors when a processor-specific failure needs wrapping.
|
|
12
24
|
class ProcessorError < Error
|
|
13
25
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module CDC
|
|
2
|
+
module Core
|
|
3
|
+
# Immutable representation of an event's position metadata.
|
|
4
|
+
class EventPosition
|
|
5
|
+
@strategy: untyped
|
|
6
|
+
@value: untyped
|
|
7
|
+
@transaction_id: untyped
|
|
8
|
+
@sequence_number: untyped
|
|
9
|
+
@occurred_at: untyped
|
|
10
|
+
|
|
11
|
+
attr_reader strategy: untyped
|
|
12
|
+
attr_reader value: untyped
|
|
13
|
+
attr_reader transaction_id: untyped
|
|
14
|
+
attr_reader sequence_number: untyped
|
|
15
|
+
attr_reader occurred_at: untyped
|
|
16
|
+
|
|
17
|
+
def initialize: (strategy: untyped, value: untyped, ?transaction_id: untyped?, ?sequence_number: untyped?, ?occurred_at: untyped?) -> void
|
|
18
|
+
def to_h: () -> untyped
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module CDC
|
|
2
|
+
module Core
|
|
3
|
+
# Observer interface for CDC runtime instrumentation.
|
|
4
|
+
class Observer
|
|
5
|
+
METRIC_NAMES: untyped
|
|
6
|
+
|
|
7
|
+
def self.metric_tags: (untyped payload) -> untyped
|
|
8
|
+
def self.started_metric_name: () -> String
|
|
9
|
+
def self.succeeded_metric_name: () -> String
|
|
10
|
+
def self.failed_metric_name: () -> String
|
|
11
|
+
def self.skipped_metric_name: () -> String
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def self.change_event_metric_tags: (untyped event) -> untyped
|
|
15
|
+
def self.transaction_envelope_metric_tags: (untyped transaction) -> untyped
|
|
16
|
+
def self.processor_result_metric_tags: (untyped result) -> untyped
|
|
17
|
+
def self.batch_metric_tags: (untyped batch) -> untyped
|
|
18
|
+
|
|
19
|
+
def dispatch_started: (untyped _event) -> void
|
|
20
|
+
def dispatch_succeeded: (untyped _result) -> void
|
|
21
|
+
def dispatch_failed: (untyped _result) -> void
|
|
22
|
+
def dispatch_skipped: (untyped _result) -> void
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module CDC
|
|
2
|
+
module Core
|
|
3
|
+
# Immutable grouping key for ordering-related dispatch.
|
|
4
|
+
class OrderingKey
|
|
5
|
+
@scope: untyped
|
|
6
|
+
@components: untyped
|
|
7
|
+
|
|
8
|
+
attr_reader scope: untyped
|
|
9
|
+
attr_reader components: untyped
|
|
10
|
+
|
|
11
|
+
def initialize: (scope: untyped, ?components: ::Hash[untyped, untyped]) -> void
|
|
12
|
+
def empty?: () -> bool
|
|
13
|
+
def to_h: () -> untyped
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module CDC
|
|
2
|
+
module Core
|
|
3
|
+
# Immutable description of how ordered CDC work should be grouped.
|
|
4
|
+
class OrderingPolicy
|
|
5
|
+
@scope: untyped
|
|
6
|
+
@position: untyped
|
|
7
|
+
@transaction_aware: untyped
|
|
8
|
+
|
|
9
|
+
SUPPORTED_POSITIONS: untyped
|
|
10
|
+
|
|
11
|
+
attr_reader scope: untyped
|
|
12
|
+
attr_reader position: untyped
|
|
13
|
+
attr_reader transaction_aware: untyped
|
|
14
|
+
|
|
15
|
+
def initialize: (scope: untyped, ?position: untyped?, ?transaction_aware: bool) -> void
|
|
16
|
+
def transaction_aware?: () -> bool
|
|
17
|
+
def key_for: (untyped event) -> untyped
|
|
18
|
+
def position_for: (untyped event) -> untyped
|
|
19
|
+
def to_h: () -> untyped
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def normalize_position: (untyped position) -> untyped
|
|
24
|
+
def key_components: (untyped event) -> untyped
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module CDC
|
|
2
|
+
module Core
|
|
3
|
+
# Canonical ordering scopes used by the CDC ecosystem.
|
|
4
|
+
module OrderingScope
|
|
5
|
+
GLOBAL: :global
|
|
6
|
+
TRANSACTION: :transaction
|
|
7
|
+
RELATION: :relation
|
|
8
|
+
PRIMARY_KEY: :primary_key
|
|
9
|
+
NONE: :none
|
|
10
|
+
SUPPORTED: untyped
|
|
11
|
+
|
|
12
|
+
def normalize: (untyped scope) -> untyped
|
|
13
|
+
def supported?: (untyped scope) -> bool
|
|
14
|
+
|
|
15
|
+
def self.normalize: (untyped scope) -> untyped
|
|
16
|
+
def self.supported?: (untyped scope) -> bool
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/sig/cdc/core/pipeline.rbs
CHANGED
|
@@ -10,6 +10,8 @@ module CDC
|
|
|
10
10
|
|
|
11
11
|
@filters: untyped
|
|
12
12
|
|
|
13
|
+
@observer: untyped
|
|
14
|
+
|
|
13
15
|
# @return [#process] processor invoked for matching events
|
|
14
16
|
# @return [Array<Filter>] filters that must all match before processing
|
|
15
17
|
attr_reader processor: untyped
|
|
@@ -18,11 +20,15 @@ module CDC
|
|
|
18
20
|
# @return [Array<Filter>] filters that must all match before processing
|
|
19
21
|
attr_reader filters: untyped
|
|
20
22
|
|
|
23
|
+
# @return [Observer] observer notified of dispatch events
|
|
24
|
+
attr_reader observer: untyped
|
|
25
|
+
|
|
21
26
|
# Build a pipeline.
|
|
22
27
|
#
|
|
23
28
|
# @param processor [#process] processor for matching events
|
|
24
29
|
# @param filters [Array<Filter>] filters applied before processing
|
|
25
|
-
|
|
30
|
+
# @param observer [Observer, nil] instrumentation observer
|
|
31
|
+
def initialize: (processor: untyped, ?filters: untyped, ?observer: untyped?) -> void
|
|
26
32
|
|
|
27
33
|
# Process one event through the pipeline.
|
|
28
34
|
#
|
|
@@ -50,6 +56,9 @@ module CDC
|
|
|
50
56
|
# @param event [ChangeEvent] processed event
|
|
51
57
|
# @return [ProcessorResult]
|
|
52
58
|
def normalize_result: (untyped result, untyped event) -> untyped
|
|
59
|
+
|
|
60
|
+
def dispatch: (untyped event) -> untyped
|
|
61
|
+
def observe_result: (untyped result) -> untyped
|
|
53
62
|
end
|
|
54
63
|
end
|
|
55
64
|
end
|