senro_usecaser 0.2.0 → 0.4.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/.rubocop.yml +4 -0
- data/CHANGELOG.md +23 -0
- data/README.md +850 -0
- data/examples/namespace_demo.rb +50 -15
- data/examples/order_system.rb +222 -34
- data/examples/sig/namespace_demo.rbs +35 -10
- data/examples/sig/order_system.rbs +196 -20
- data/lib/senro_usecaser/base.rb +387 -86
- data/lib/senro_usecaser/depends_on.rb +257 -0
- data/lib/senro_usecaser/hook.rb +28 -82
- data/lib/senro_usecaser/provider.rb +1 -1
- data/lib/senro_usecaser/retry_configuration.rb +131 -0
- data/lib/senro_usecaser/retry_context.rb +133 -0
- data/lib/senro_usecaser/version.rb +1 -1
- data/lib/senro_usecaser.rb +3 -0
- data/sig/generated/senro_usecaser/base.rbs +179 -37
- data/sig/generated/senro_usecaser/depends_on.rbs +197 -0
- data/sig/generated/senro_usecaser/hook.rbs +23 -35
- data/sig/generated/senro_usecaser/provider.rbs +1 -1
- data/sig/generated/senro_usecaser/retry_configuration.rbs +90 -0
- data/sig/generated/senro_usecaser/retry_context.rbs +101 -0
- data/sig/overrides.rbs +1 -2
- metadata +7 -1
|
@@ -89,30 +89,14 @@ module SenroUsecaser
|
|
|
89
89
|
# organize StepA, StepB
|
|
90
90
|
# end
|
|
91
91
|
class Base
|
|
92
|
-
|
|
93
|
-
#
|
|
94
|
-
# : (Symbol, ?Class) -> void
|
|
95
|
-
def self.depends_on: (Symbol, ?Class) -> void
|
|
96
|
-
|
|
97
|
-
# Returns the list of declared dependencies
|
|
98
|
-
#
|
|
99
|
-
# : () -> Array[Symbol]
|
|
100
|
-
def self.dependencies: () -> Array[Symbol]
|
|
92
|
+
extend DependsOn
|
|
101
93
|
|
|
102
|
-
|
|
103
|
-
#
|
|
104
|
-
# : () -> Hash[Symbol, Class]
|
|
105
|
-
def self.dependency_types: () -> Hash[Symbol, Class]
|
|
106
|
-
|
|
107
|
-
# Sets the namespace for dependency resolution
|
|
108
|
-
#
|
|
109
|
-
# : ((Symbol | String)) -> void
|
|
110
|
-
def self.namespace: (Symbol | String) -> void
|
|
94
|
+
include DependsOn::InstanceMethods
|
|
111
95
|
|
|
112
|
-
#
|
|
96
|
+
# Alias for backward compatibility
|
|
113
97
|
#
|
|
114
98
|
# : () -> (Symbol | String)?
|
|
115
|
-
|
|
99
|
+
alias self.use_case_namespace self.declared_namespace
|
|
116
100
|
|
|
117
101
|
# Declares a sequence of UseCases to execute as a pipeline
|
|
118
102
|
#
|
|
@@ -188,15 +172,98 @@ module SenroUsecaser
|
|
|
188
172
|
# : () -> Array[Proc]
|
|
189
173
|
def self.around_hooks: () -> Array[Proc]
|
|
190
174
|
|
|
191
|
-
#
|
|
175
|
+
# Adds an on_failure hook
|
|
176
|
+
#
|
|
177
|
+
# : () { (untyped, Result[untyped], ?RetryContext?) -> void } -> void
|
|
178
|
+
def self.on_failure: () { (untyped, Result[untyped], ?RetryContext?) -> void } -> void
|
|
179
|
+
|
|
180
|
+
# Returns the list of on_failure hooks
|
|
181
|
+
#
|
|
182
|
+
# : () -> Array[Proc]
|
|
183
|
+
def self.on_failure_hooks: () -> Array[Proc]
|
|
184
|
+
|
|
185
|
+
# Configures automatic retry for specific error types
|
|
186
|
+
#
|
|
187
|
+
# @example Retry on network errors
|
|
188
|
+
# retry_on :network_error, attempts: 3, wait: 1
|
|
189
|
+
#
|
|
190
|
+
# @example Retry on exception class
|
|
191
|
+
# retry_on Net::OpenTimeout, attempts: 5, wait: 2, backoff: :exponential
|
|
192
|
+
#
|
|
193
|
+
# @example Multiple error types with jitter
|
|
194
|
+
# retry_on :rate_limited, :timeout, attempts: 3, wait: 1, jitter: 0.1
|
|
195
|
+
#
|
|
196
|
+
# rubocop:disable Metrics/ParameterLists
|
|
197
|
+
# : (*(Symbol | Class), ?attempts: Integer, ?wait: (Float | Integer),
|
|
198
|
+
# : ?backoff: Symbol, ?max_wait: (Float | Integer)?, ?jitter: (Float | Integer)) -> void
|
|
199
|
+
def self.retry_on: (*untyped error_matchers, ?attempts: untyped, ?wait: untyped, ?backoff: untyped, ?max_wait: untyped, ?jitter: untyped) -> untyped
|
|
200
|
+
|
|
201
|
+
# Returns the list of retry configurations
|
|
202
|
+
#
|
|
203
|
+
# : () -> Array[RetryConfiguration]
|
|
204
|
+
def self.retry_configurations: () -> Array[RetryConfiguration]
|
|
205
|
+
|
|
206
|
+
# Configures errors that should immediately discard (no retry)
|
|
207
|
+
#
|
|
208
|
+
# @example Discard on validation errors
|
|
209
|
+
# discard_on :validation_error, :not_found
|
|
210
|
+
#
|
|
211
|
+
# @example Discard on exception class
|
|
212
|
+
# discard_on ArgumentError
|
|
192
213
|
#
|
|
193
|
-
# : (Class) -> void
|
|
194
|
-
def self.
|
|
214
|
+
# : (*(Symbol | Class)) -> void
|
|
215
|
+
def self.discard_on: (*Symbol | Class) -> void
|
|
195
216
|
|
|
196
|
-
# Returns the
|
|
217
|
+
# Returns the list of discard matchers
|
|
197
218
|
#
|
|
198
|
-
# : () -> Class
|
|
199
|
-
|
|
219
|
+
# : () -> Array[(Symbol | Class)]
|
|
220
|
+
def self.discard_matchers: () -> Array[Symbol | Class]
|
|
221
|
+
|
|
222
|
+
# Adds a before_retry hook
|
|
223
|
+
#
|
|
224
|
+
# : () { (untyped, Result[untyped], RetryContext) -> void } -> void
|
|
225
|
+
def self.before_retry: () { (untyped, Result[untyped], RetryContext) -> void } -> void
|
|
226
|
+
|
|
227
|
+
# Returns the list of before_retry hooks
|
|
228
|
+
#
|
|
229
|
+
# : () -> Array[Proc]
|
|
230
|
+
def self.before_retry_hooks: () -> Array[Proc]
|
|
231
|
+
|
|
232
|
+
# Adds an after_retries_exhausted hook
|
|
233
|
+
#
|
|
234
|
+
# : () { (untyped, Result[untyped], RetryContext) -> void } -> void
|
|
235
|
+
def self.after_retries_exhausted: () { (untyped, Result[untyped], RetryContext) -> void } -> void
|
|
236
|
+
|
|
237
|
+
# Returns the list of after_retries_exhausted hooks
|
|
238
|
+
#
|
|
239
|
+
# : () -> Array[Proc]
|
|
240
|
+
def self.after_retries_exhausted_hooks: () -> Array[Proc]
|
|
241
|
+
|
|
242
|
+
# Declares the expected input type(s) for this UseCase
|
|
243
|
+
# Accepts a Class or one or more Modules that input must include
|
|
244
|
+
#
|
|
245
|
+
# @example Single class
|
|
246
|
+
# input UserInput
|
|
247
|
+
#
|
|
248
|
+
# @example Single module (interface)
|
|
249
|
+
# input HasUserId
|
|
250
|
+
#
|
|
251
|
+
# @example Multiple modules (interfaces)
|
|
252
|
+
# input HasUserId, HasEmail
|
|
253
|
+
#
|
|
254
|
+
# : (*Module) -> void
|
|
255
|
+
def self.input: (*Module) -> void
|
|
256
|
+
|
|
257
|
+
# Returns the input types as an array
|
|
258
|
+
#
|
|
259
|
+
# : () -> Array[Module]
|
|
260
|
+
def self.input_types: () -> Array[Module]
|
|
261
|
+
|
|
262
|
+
# Returns the input class (for backwards compatibility)
|
|
263
|
+
# If a Class is specified, returns it. Otherwise returns the first type.
|
|
264
|
+
#
|
|
265
|
+
# : () -> Module?
|
|
266
|
+
def self.input_class: () -> Module?
|
|
200
267
|
|
|
201
268
|
# Declares the expected output type for this UseCase
|
|
202
269
|
#
|
|
@@ -245,6 +312,18 @@ module SenroUsecaser
|
|
|
245
312
|
# : (?untyped input) -> Result[untyped]
|
|
246
313
|
def call: (?untyped input) -> Result[untyped]
|
|
247
314
|
|
|
315
|
+
# Represents a record of a step execution in a pipeline
|
|
316
|
+
class StepExecutionRecord < Struct[untyped]
|
|
317
|
+
attr_accessor step(): untyped
|
|
318
|
+
|
|
319
|
+
attr_accessor input(): untyped
|
|
320
|
+
|
|
321
|
+
attr_accessor result(): untyped
|
|
322
|
+
|
|
323
|
+
def self.new: (?step: untyped, ?input: untyped, ?result: untyped) -> instance
|
|
324
|
+
| ({ ?step: untyped, ?input: untyped, ?result: untyped }) -> instance
|
|
325
|
+
end
|
|
326
|
+
|
|
248
327
|
private
|
|
249
328
|
|
|
250
329
|
# Creates a success Result with the given value
|
|
@@ -267,11 +346,54 @@ module SenroUsecaser
|
|
|
267
346
|
# : [T] (*Class, ?code: Symbol) { () -> T } -> Result[T]
|
|
268
347
|
def capture: [T] (*Class, ?code: Symbol) { () -> T } -> Result[T]
|
|
269
348
|
|
|
349
|
+
# Validates that input satisfies all declared input types
|
|
350
|
+
# For Modules: checks if input's class includes the module
|
|
351
|
+
# For Classes: checks if input is an instance of the class
|
|
352
|
+
#
|
|
353
|
+
# : (untyped) -> void
|
|
354
|
+
def validate_input!: (untyped) -> void
|
|
355
|
+
|
|
356
|
+
# Validates that the result's value satisfies the declared output type
|
|
357
|
+
# Only validates if result is success and output_schema is a Class
|
|
358
|
+
#
|
|
359
|
+
# : (Result[untyped]) -> void
|
|
360
|
+
def validate_output!: (Result[untyped]) -> void
|
|
361
|
+
|
|
270
362
|
# Executes the core logic with before/after/around hooks
|
|
271
363
|
#
|
|
272
364
|
# : (untyped) { () -> Result[untyped] } -> Result[untyped]
|
|
273
365
|
def execute_with_hooks: (untyped) { () -> Result[untyped] } -> Result[untyped]
|
|
274
366
|
|
|
367
|
+
# Executes the UseCase with retry support
|
|
368
|
+
#
|
|
369
|
+
# : (untyped) -> Result[untyped]
|
|
370
|
+
def execute_with_retry: (untyped) -> Result[untyped]
|
|
371
|
+
|
|
372
|
+
# Builds a retry context with max attempts from configurations
|
|
373
|
+
#
|
|
374
|
+
# : () -> RetryContext
|
|
375
|
+
def build_retry_context: () -> RetryContext
|
|
376
|
+
|
|
377
|
+
# Finds a retry configuration that matches the result
|
|
378
|
+
#
|
|
379
|
+
# : (Result[untyped]) -> RetryConfiguration?
|
|
380
|
+
def find_matching_retry_config: (Result[untyped]) -> RetryConfiguration?
|
|
381
|
+
|
|
382
|
+
# Checks if the result should be discarded (no retry)
|
|
383
|
+
#
|
|
384
|
+
# : (Result[untyped]) -> bool
|
|
385
|
+
def should_discard?: (Result[untyped]) -> bool
|
|
386
|
+
|
|
387
|
+
# Runs before_retry hooks
|
|
388
|
+
#
|
|
389
|
+
# : (untyped, Result[untyped], RetryContext) -> void
|
|
390
|
+
def run_before_retry_hooks: (untyped, Result[untyped], RetryContext) -> void
|
|
391
|
+
|
|
392
|
+
# Runs after_retries_exhausted hooks
|
|
393
|
+
#
|
|
394
|
+
# : (untyped, Result[untyped], RetryContext) -> void
|
|
395
|
+
def run_after_retries_exhausted_hooks: (untyped, Result[untyped], RetryContext) -> void
|
|
396
|
+
|
|
275
397
|
# Wraps a non-Result value in Result.success
|
|
276
398
|
#
|
|
277
399
|
# : (untyped) -> Result[untyped]
|
|
@@ -307,6 +429,16 @@ module SenroUsecaser
|
|
|
307
429
|
# : (untyped, Result[untyped]) -> void
|
|
308
430
|
def run_after_hooks: (untyped, Result[untyped]) -> void
|
|
309
431
|
|
|
432
|
+
# Runs all on_failure hooks when result is a failure
|
|
433
|
+
#
|
|
434
|
+
# : (untyped, Result[untyped], ?RetryContext?) -> void
|
|
435
|
+
def run_on_failure_hooks: (untyped, Result[untyped], ?RetryContext?) -> void
|
|
436
|
+
|
|
437
|
+
# Calls an on_failure hook with appropriate arguments
|
|
438
|
+
#
|
|
439
|
+
# : (untyped, Symbol, untyped, Result[untyped], RetryContext?) -> void
|
|
440
|
+
def call_on_failure_hook: (untyped, Symbol, untyped, Result[untyped], RetryContext?) -> void
|
|
441
|
+
|
|
310
442
|
# Returns instantiated hook objects
|
|
311
443
|
#
|
|
312
444
|
# : () -> Array[Hook]
|
|
@@ -323,20 +455,11 @@ module SenroUsecaser
|
|
|
323
455
|
def resolve_dependencies: (Container, Hash[Symbol, untyped]) -> void
|
|
324
456
|
|
|
325
457
|
# Resolves a single dependency from the container
|
|
458
|
+
# Overrides DependsOn::InstanceMethods to accept container as parameter
|
|
326
459
|
#
|
|
327
460
|
# : (Container, Symbol) -> untyped
|
|
328
461
|
def resolve_from_container: (Container, Symbol) -> untyped
|
|
329
462
|
|
|
330
|
-
# Returns the effective namespace for dependency resolution
|
|
331
|
-
#
|
|
332
|
-
# : () -> (Symbol | String)?
|
|
333
|
-
def effective_namespace: () -> (Symbol | String)?
|
|
334
|
-
|
|
335
|
-
# Infers namespace from the class's module structure
|
|
336
|
-
#
|
|
337
|
-
# : () -> String?
|
|
338
|
-
def infer_namespace_from_class: () -> String?
|
|
339
|
-
|
|
340
463
|
# Executes the organized UseCase pipeline
|
|
341
464
|
#
|
|
342
465
|
# : (untyped) -> Result[untyped]
|
|
@@ -378,9 +501,28 @@ module SenroUsecaser
|
|
|
378
501
|
def step_should_stop?: (Step) -> bool
|
|
379
502
|
|
|
380
503
|
# Calls a single UseCase in the pipeline
|
|
381
|
-
# Requires
|
|
504
|
+
# Requires input type(s) to be defined for pipeline steps
|
|
505
|
+
# Note: on_failure hooks are not called here - they're called in pipeline rollback
|
|
382
506
|
#
|
|
383
507
|
# : (singleton(Base), untyped) -> Result[untyped]
|
|
384
508
|
def call_use_case: (singleton(Base), untyped) -> Result[untyped]
|
|
509
|
+
|
|
510
|
+
# Performs the UseCase as a pipeline step (without on_failure hooks)
|
|
511
|
+
# on_failure hooks are handled by the pipeline's rollback mechanism instead
|
|
512
|
+
#
|
|
513
|
+
# : (untyped, ?capture_exceptions: bool) -> Result[untyped]
|
|
514
|
+
def perform_as_pipeline_step: (untyped, ?capture_exceptions: bool) -> Result[untyped]
|
|
515
|
+
|
|
516
|
+
# Executes rollback by calling on_failure hooks on executed steps in reverse order
|
|
517
|
+
# Unlike run_on_failure_hooks, this method calls hooks regardless of result status
|
|
518
|
+
# because we want to rollback even successfully completed steps when pipeline fails
|
|
519
|
+
#
|
|
520
|
+
# : (Array[StepExecutionRecord]) -> void
|
|
521
|
+
def execute_pipeline_rollback: (Array[StepExecutionRecord]) -> void
|
|
522
|
+
|
|
523
|
+
# Runs on_failure hooks for rollback purposes (regardless of result status)
|
|
524
|
+
#
|
|
525
|
+
# : (untyped, Result[untyped]) -> void
|
|
526
|
+
def run_rollback_hooks: (untyped, Result[untyped]) -> void
|
|
385
527
|
end
|
|
386
528
|
end
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Generated from lib/senro_usecaser/depends_on.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module SenroUsecaser
|
|
4
|
+
# Module that provides dependency injection support
|
|
5
|
+
#
|
|
6
|
+
# This module can be extended into any class to enable the full DI functionality
|
|
7
|
+
# similar to UseCase and Hook classes, including:
|
|
8
|
+
# - `depends_on` for declaring dependencies
|
|
9
|
+
# - `namespace` for scoped dependency resolution
|
|
10
|
+
# - Automatic `infer_namespace_from_module` support
|
|
11
|
+
# - Default `initialize` that sets up dependency injection (uses SenroUsecaser.container if not provided)
|
|
12
|
+
#
|
|
13
|
+
# @example Basic usage (no initialize needed)
|
|
14
|
+
# class MyService
|
|
15
|
+
# extend SenroUsecaser::DependsOn
|
|
16
|
+
#
|
|
17
|
+
# depends_on :logger, Logger
|
|
18
|
+
# depends_on :repository
|
|
19
|
+
#
|
|
20
|
+
# # No initialize needed! Default is provided automatically.
|
|
21
|
+
#
|
|
22
|
+
# def perform
|
|
23
|
+
# logger.info("Performing...")
|
|
24
|
+
# repository.find(1)
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# service = MyService.new # Uses SenroUsecaser.container
|
|
29
|
+
# service.logger # => Logger instance
|
|
30
|
+
#
|
|
31
|
+
# @example Custom initialize with super
|
|
32
|
+
# class MyService
|
|
33
|
+
# extend SenroUsecaser::DependsOn
|
|
34
|
+
#
|
|
35
|
+
# depends_on :logger
|
|
36
|
+
# attr_reader :extra
|
|
37
|
+
#
|
|
38
|
+
# def initialize(extra:, container: nil)
|
|
39
|
+
# super(container: container) # Handles dependency resolution
|
|
40
|
+
# @extra = extra
|
|
41
|
+
# end
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# @example With explicit namespace
|
|
45
|
+
# class Admin::UserService
|
|
46
|
+
# extend SenroUsecaser::DependsOn
|
|
47
|
+
#
|
|
48
|
+
# namespace :admin
|
|
49
|
+
# depends_on :user_repository, UserRepository
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
# @example With infer_namespace_from_module (when configured)
|
|
53
|
+
# # When SenroUsecaser.configuration.infer_namespace_from_module = true
|
|
54
|
+
# class Admin::Orders::ProcessService
|
|
55
|
+
# extend SenroUsecaser::DependsOn
|
|
56
|
+
#
|
|
57
|
+
# depends_on :order_repository # resolved from "admin::orders" namespace
|
|
58
|
+
# end
|
|
59
|
+
module DependsOn
|
|
60
|
+
# Hook called when module is extended into a class
|
|
61
|
+
# Automatically includes InstanceMethods
|
|
62
|
+
def self.extended: (untyped base) -> untyped
|
|
63
|
+
|
|
64
|
+
# Declares a dependency to be injected from the container
|
|
65
|
+
#
|
|
66
|
+
# @param name [Symbol] The name of the dependency
|
|
67
|
+
# @param type [Class, nil] Optional expected type for the dependency
|
|
68
|
+
#
|
|
69
|
+
# @example Basic dependency
|
|
70
|
+
# depends_on :logger
|
|
71
|
+
#
|
|
72
|
+
# @example Typed dependency
|
|
73
|
+
# depends_on :repository, UserRepository
|
|
74
|
+
#
|
|
75
|
+
# : (Symbol, ?Class) -> void
|
|
76
|
+
def depends_on: (Symbol, ?Class) -> void
|
|
77
|
+
|
|
78
|
+
# Returns the list of declared dependencies
|
|
79
|
+
#
|
|
80
|
+
# : () -> Array[Symbol]
|
|
81
|
+
def dependencies: () -> Array[Symbol]
|
|
82
|
+
|
|
83
|
+
# Returns the dependency type mapping
|
|
84
|
+
#
|
|
85
|
+
# : () -> Hash[Symbol, Class]
|
|
86
|
+
def dependency_types: () -> Hash[Symbol, Class]
|
|
87
|
+
|
|
88
|
+
# Sets or returns the namespace for dependency resolution
|
|
89
|
+
#
|
|
90
|
+
# @param name [Symbol, String, nil] The namespace to set
|
|
91
|
+
# @return [Symbol, String, nil] The current namespace when no argument given
|
|
92
|
+
#
|
|
93
|
+
# @example Setting namespace
|
|
94
|
+
# namespace :admin
|
|
95
|
+
#
|
|
96
|
+
# @example Getting namespace
|
|
97
|
+
# current_ns = namespace
|
|
98
|
+
#
|
|
99
|
+
# : (?(Symbol | String)) -> (Symbol | String)?
|
|
100
|
+
def namespace: (?Symbol | String) -> (Symbol | String)?
|
|
101
|
+
|
|
102
|
+
# Returns the declared namespace
|
|
103
|
+
#
|
|
104
|
+
# : () -> (Symbol | String)?
|
|
105
|
+
def declared_namespace: () -> (Symbol | String)?
|
|
106
|
+
|
|
107
|
+
# Copies dependency configuration to a subclass
|
|
108
|
+
#
|
|
109
|
+
# @param subclass [Class] The subclass to copy dependencies to
|
|
110
|
+
#
|
|
111
|
+
# @example In inherited hook
|
|
112
|
+
# def self.inherited(subclass)
|
|
113
|
+
# super
|
|
114
|
+
# copy_depends_on_to(subclass)
|
|
115
|
+
# end
|
|
116
|
+
#
|
|
117
|
+
# : (Class) -> void
|
|
118
|
+
def copy_depends_on_to: (Class) -> void
|
|
119
|
+
|
|
120
|
+
# Instance methods for dependency resolution
|
|
121
|
+
#
|
|
122
|
+
# These methods are automatically included when DependsOn is extended.
|
|
123
|
+
# They require @_container and @_dependencies instance variables to be set.
|
|
124
|
+
module InstanceMethods
|
|
125
|
+
# Default initialize for classes using DependsOn
|
|
126
|
+
#
|
|
127
|
+
# This provides a default initialize that sets up dependency injection.
|
|
128
|
+
# Classes can override this and call super to extend the behavior.
|
|
129
|
+
#
|
|
130
|
+
# @param container [Container, nil] The DI container to resolve dependencies from.
|
|
131
|
+
# If nil, uses SenroUsecaser.container.
|
|
132
|
+
#
|
|
133
|
+
# @example Default usage (no arguments needed)
|
|
134
|
+
# class MyService
|
|
135
|
+
# extend SenroUsecaser::DependsOn
|
|
136
|
+
# depends_on :logger
|
|
137
|
+
# end
|
|
138
|
+
# service = MyService.new # Uses SenroUsecaser.container
|
|
139
|
+
#
|
|
140
|
+
# @example With explicit container
|
|
141
|
+
# service = MyService.new(container: custom_container)
|
|
142
|
+
#
|
|
143
|
+
# @example Custom initialize with super
|
|
144
|
+
# class MyService
|
|
145
|
+
# extend SenroUsecaser::DependsOn
|
|
146
|
+
# depends_on :logger
|
|
147
|
+
# attr_reader :extra
|
|
148
|
+
#
|
|
149
|
+
# def initialize(extra:, container: nil)
|
|
150
|
+
# super(container: container)
|
|
151
|
+
# @extra = extra
|
|
152
|
+
# end
|
|
153
|
+
# end
|
|
154
|
+
#
|
|
155
|
+
# : (?container: Container?) -> void
|
|
156
|
+
def initialize: (?container: Container?) -> void
|
|
157
|
+
|
|
158
|
+
# Resolves all declared dependencies from the container
|
|
159
|
+
#
|
|
160
|
+
# Call this in your initialize method after setting @_container and @_dependencies.
|
|
161
|
+
#
|
|
162
|
+
# @example
|
|
163
|
+
# def initialize(container:)
|
|
164
|
+
# @_container = container
|
|
165
|
+
# @_dependencies = {}
|
|
166
|
+
# resolve_dependencies
|
|
167
|
+
# end
|
|
168
|
+
#
|
|
169
|
+
# : () -> void
|
|
170
|
+
def resolve_dependencies: () -> void
|
|
171
|
+
|
|
172
|
+
private
|
|
173
|
+
|
|
174
|
+
# Returns the effective namespace for dependency resolution
|
|
175
|
+
#
|
|
176
|
+
# Priority:
|
|
177
|
+
# 1. Explicitly declared namespace via `namespace :name`
|
|
178
|
+
# 2. Inferred namespace from module structure (if configured)
|
|
179
|
+
#
|
|
180
|
+
# : () -> (Symbol | String)?
|
|
181
|
+
def effective_namespace: () -> (Symbol | String)?
|
|
182
|
+
|
|
183
|
+
# Infers namespace from the class's module structure
|
|
184
|
+
#
|
|
185
|
+
# Converts CamelCase module names to snake_case and joins with "::"
|
|
186
|
+
# e.g., Admin::Orders::ProcessService -> "admin::orders"
|
|
187
|
+
#
|
|
188
|
+
# : () -> String?
|
|
189
|
+
def infer_namespace_from_class: () -> String?
|
|
190
|
+
|
|
191
|
+
# Resolves a single dependency from the container
|
|
192
|
+
#
|
|
193
|
+
# : (Symbol) -> untyped
|
|
194
|
+
def resolve_from_container: (Symbol) -> untyped
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
@@ -36,30 +36,14 @@ module SenroUsecaser
|
|
|
36
36
|
# end
|
|
37
37
|
# end
|
|
38
38
|
class Hook
|
|
39
|
-
|
|
40
|
-
#
|
|
41
|
-
# : (Symbol, ?Class) -> void
|
|
42
|
-
def self.depends_on: (Symbol, ?Class) -> void
|
|
43
|
-
|
|
44
|
-
# Returns the list of declared dependencies
|
|
45
|
-
#
|
|
46
|
-
# : () -> Array[Symbol]
|
|
47
|
-
def self.dependencies: () -> Array[Symbol]
|
|
48
|
-
|
|
49
|
-
# Returns the dependency type mapping
|
|
50
|
-
#
|
|
51
|
-
# : () -> Hash[Symbol, Class]
|
|
52
|
-
def self.dependency_types: () -> Hash[Symbol, Class]
|
|
39
|
+
extend DependsOn
|
|
53
40
|
|
|
54
|
-
|
|
55
|
-
#
|
|
56
|
-
# : (?(Symbol | String)) -> (Symbol | String)?
|
|
57
|
-
def self.namespace: (?Symbol | String) -> (Symbol | String)?
|
|
41
|
+
include DependsOn::InstanceMethods
|
|
58
42
|
|
|
59
|
-
# Alias for
|
|
43
|
+
# Alias for backward compatibility
|
|
60
44
|
#
|
|
61
45
|
# : () -> (Symbol | String)?
|
|
62
|
-
|
|
46
|
+
alias self.hook_namespace self.declared_namespace
|
|
63
47
|
|
|
64
48
|
# @api private
|
|
65
49
|
def self.inherited: (untyped subclass) -> untyped
|
|
@@ -87,26 +71,30 @@ module SenroUsecaser
|
|
|
87
71
|
# : (untyped) { () -> Result[untyped] } -> Result[untyped]
|
|
88
72
|
def around: (untyped) { () -> Result[untyped] } -> Result[untyped]
|
|
89
73
|
|
|
74
|
+
# Called when the UseCase fails
|
|
75
|
+
# Override in subclass to add failure handling or rollback logic
|
|
76
|
+
#
|
|
77
|
+
# @example Basic logging
|
|
78
|
+
# def on_failure(input, result)
|
|
79
|
+
# logger.error("Failed: #{result.errors.first&.message}")
|
|
80
|
+
# end
|
|
81
|
+
#
|
|
82
|
+
# @example Request retry
|
|
83
|
+
# def on_failure(input, result, context)
|
|
84
|
+
# if result.errors.first&.code == :network_error && context.attempt < 3
|
|
85
|
+
# context.retry!(wait: 2.0)
|
|
86
|
+
# end
|
|
87
|
+
# end
|
|
88
|
+
#
|
|
89
|
+
# : (untyped, Result[untyped], ?RetryContext?) -> void
|
|
90
|
+
def on_failure: (untyped, Result[untyped], ?RetryContext?) -> void
|
|
91
|
+
|
|
90
92
|
private
|
|
91
93
|
|
|
92
94
|
# Returns the effective namespace for dependency resolution
|
|
95
|
+
# Overrides DependsOn::InstanceMethods to add use_case_namespace fallback
|
|
93
96
|
#
|
|
94
97
|
# : () -> (Symbol | String)?
|
|
95
98
|
def effective_namespace: () -> (Symbol | String)?
|
|
96
|
-
|
|
97
|
-
# Infers namespace from the class's module structure
|
|
98
|
-
#
|
|
99
|
-
# : () -> String?
|
|
100
|
-
def infer_namespace_from_class: () -> String?
|
|
101
|
-
|
|
102
|
-
# Resolves dependencies from the container
|
|
103
|
-
#
|
|
104
|
-
# : () -> void
|
|
105
|
-
def resolve_dependencies: () -> void
|
|
106
|
-
|
|
107
|
-
# Resolves a single dependency from the container
|
|
108
|
-
#
|
|
109
|
-
# : (Symbol) -> untyped
|
|
110
|
-
def resolve_from_container: (Symbol) -> untyped
|
|
111
99
|
end
|
|
112
100
|
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Generated from lib/senro_usecaser/retry_configuration.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module SenroUsecaser
|
|
4
|
+
# Configuration for automatic retry behavior
|
|
5
|
+
#
|
|
6
|
+
# This class defines when and how retries should occur based on
|
|
7
|
+
# error codes or exception classes, with configurable backoff strategies.
|
|
8
|
+
#
|
|
9
|
+
# @example Basic retry configuration
|
|
10
|
+
# RetryConfiguration.new(
|
|
11
|
+
# matchers: [:network_error, Net::OpenTimeout],
|
|
12
|
+
# attempts: 3,
|
|
13
|
+
# wait: 1.0
|
|
14
|
+
# )
|
|
15
|
+
#
|
|
16
|
+
# @example With exponential backoff
|
|
17
|
+
# RetryConfiguration.new(
|
|
18
|
+
# matchers: [:rate_limited],
|
|
19
|
+
# attempts: 5,
|
|
20
|
+
# wait: 2.0,
|
|
21
|
+
# backoff: :exponential,
|
|
22
|
+
# max_wait: 60
|
|
23
|
+
# )
|
|
24
|
+
class RetryConfiguration
|
|
25
|
+
# Returns the list of error matchers (Symbols for error codes, Classes for exceptions)
|
|
26
|
+
# : () -> Array[(Symbol | Class)]
|
|
27
|
+
attr_reader matchers: untyped
|
|
28
|
+
|
|
29
|
+
# Returns the maximum number of attempts
|
|
30
|
+
# : () -> Integer
|
|
31
|
+
attr_reader attempts: untyped
|
|
32
|
+
|
|
33
|
+
# Returns the base wait time in seconds
|
|
34
|
+
# : () -> (Float | Integer)
|
|
35
|
+
attr_reader wait: untyped
|
|
36
|
+
|
|
37
|
+
# Returns the backoff strategy (:fixed, :linear, :exponential)
|
|
38
|
+
# : () -> Symbol
|
|
39
|
+
attr_reader backoff: untyped
|
|
40
|
+
|
|
41
|
+
# Returns the maximum wait time in seconds
|
|
42
|
+
# : () -> (Float | Integer)
|
|
43
|
+
attr_reader max_wait: untyped
|
|
44
|
+
|
|
45
|
+
# Returns the jitter factor (0.0 to 1.0)
|
|
46
|
+
# : () -> (Float | Integer)
|
|
47
|
+
attr_reader jitter: untyped
|
|
48
|
+
|
|
49
|
+
# Initializes a new retry configuration
|
|
50
|
+
#
|
|
51
|
+
# @param matchers [Array<Symbol, Class>] Error codes or exception classes to match
|
|
52
|
+
# @param attempts [Integer] Maximum number of attempts (default: 3)
|
|
53
|
+
# @param wait [Numeric] Base wait time in seconds (default: 0)
|
|
54
|
+
# @param backoff [Symbol] Backoff strategy: :fixed, :linear, or :exponential (default: :fixed)
|
|
55
|
+
# @param max_wait [Numeric] Maximum wait time in seconds (default: 3600)
|
|
56
|
+
# @param jitter [Numeric] Jitter factor 0.0-1.0 to randomize wait times (default: 0)
|
|
57
|
+
#
|
|
58
|
+
# rubocop:disable Metrics/ParameterLists
|
|
59
|
+
# : (matchers: Array[(Symbol | Class)], ?attempts: Integer, ?wait: (Float | Integer),
|
|
60
|
+
# : ?backoff: Symbol, ?max_wait: (Float | Integer)?, ?jitter: (Float | Integer)) -> void
|
|
61
|
+
def initialize: (matchers: untyped, ?attempts: untyped, ?wait: untyped, ?backoff: untyped, ?max_wait: untyped, ?jitter: untyped) -> untyped
|
|
62
|
+
|
|
63
|
+
# Checks if this configuration matches the given result
|
|
64
|
+
#
|
|
65
|
+
# : (Result[untyped]) -> bool
|
|
66
|
+
def matches?: (Result[untyped]) -> bool
|
|
67
|
+
|
|
68
|
+
# Calculates the wait time for the given attempt number
|
|
69
|
+
#
|
|
70
|
+
# : (Integer) -> Float
|
|
71
|
+
def calculate_wait: (Integer) -> Float
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
# Checks if an error matches any of the configured matchers
|
|
76
|
+
#
|
|
77
|
+
# : (Error) -> bool
|
|
78
|
+
def matches_error?: (Error) -> bool
|
|
79
|
+
|
|
80
|
+
# Calculates the base wait time based on backoff strategy
|
|
81
|
+
#
|
|
82
|
+
# : (Integer) -> (Float | Integer)
|
|
83
|
+
def calculate_base_wait: (Integer) -> (Float | Integer)
|
|
84
|
+
|
|
85
|
+
# Applies jitter to the wait time
|
|
86
|
+
#
|
|
87
|
+
# : ((Float | Integer)) -> Float
|
|
88
|
+
def apply_jitter: (Float | Integer) -> Float
|
|
89
|
+
end
|
|
90
|
+
end
|