light-services 2.2.1 → 3.0.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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.github/config/rubocop_linter_action.yml +4 -4
  3. data/.github/workflows/ci.yml +12 -12
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +77 -7
  6. data/CHANGELOG.md +23 -0
  7. data/CLAUDE.md +139 -0
  8. data/Gemfile +16 -11
  9. data/Gemfile.lock +53 -27
  10. data/README.md +76 -13
  11. data/docs/arguments.md +267 -0
  12. data/docs/best-practices.md +153 -0
  13. data/docs/callbacks.md +476 -0
  14. data/docs/concepts.md +80 -0
  15. data/docs/configuration.md +168 -0
  16. data/docs/context.md +128 -0
  17. data/docs/crud.md +525 -0
  18. data/docs/errors.md +250 -0
  19. data/docs/generators.md +250 -0
  20. data/docs/outputs.md +135 -0
  21. data/docs/pundit-authorization.md +320 -0
  22. data/docs/quickstart.md +134 -0
  23. data/docs/readme.md +100 -0
  24. data/docs/recipes.md +14 -0
  25. data/docs/service-rendering.md +222 -0
  26. data/docs/steps.md +337 -0
  27. data/docs/summary.md +19 -0
  28. data/docs/testing.md +549 -0
  29. data/lib/generators/light_services/install/USAGE +15 -0
  30. data/lib/generators/light_services/install/install_generator.rb +41 -0
  31. data/lib/generators/light_services/install/templates/application_service.rb.tt +8 -0
  32. data/lib/generators/light_services/install/templates/application_service_spec.rb.tt +7 -0
  33. data/lib/generators/light_services/install/templates/initializer.rb.tt +30 -0
  34. data/lib/generators/light_services/service/USAGE +21 -0
  35. data/lib/generators/light_services/service/service_generator.rb +68 -0
  36. data/lib/generators/light_services/service/templates/service.rb.tt +48 -0
  37. data/lib/generators/light_services/service/templates/service_spec.rb.tt +40 -0
  38. data/lib/light/services/base.rb +23 -113
  39. data/lib/light/services/callbacks.rb +103 -0
  40. data/lib/light/services/collection.rb +97 -0
  41. data/lib/light/services/concerns/execution.rb +76 -0
  42. data/lib/light/services/concerns/parent_service.rb +34 -0
  43. data/lib/light/services/concerns/state_management.rb +30 -0
  44. data/lib/light/services/config.rb +4 -18
  45. data/lib/light/services/constants.rb +97 -0
  46. data/lib/light/services/dsl/arguments_dsl.rb +84 -0
  47. data/lib/light/services/dsl/outputs_dsl.rb +80 -0
  48. data/lib/light/services/dsl/steps_dsl.rb +205 -0
  49. data/lib/light/services/dsl/validation.rb +132 -0
  50. data/lib/light/services/exceptions.rb +7 -2
  51. data/lib/light/services/messages.rb +19 -31
  52. data/lib/light/services/rspec/matchers/define_argument.rb +174 -0
  53. data/lib/light/services/rspec/matchers/define_output.rb +147 -0
  54. data/lib/light/services/rspec/matchers/define_step.rb +225 -0
  55. data/lib/light/services/rspec/matchers/execute_step.rb +230 -0
  56. data/lib/light/services/rspec/matchers/have_error_on.rb +148 -0
  57. data/lib/light/services/rspec/matchers/have_warning_on.rb +148 -0
  58. data/lib/light/services/rspec/matchers/trigger_callback.rb +138 -0
  59. data/lib/light/services/rspec.rb +15 -0
  60. data/lib/light/services/settings/field.rb +86 -0
  61. data/lib/light/services/settings/step.rb +31 -16
  62. data/lib/light/services/utils.rb +38 -0
  63. data/lib/light/services/version.rb +1 -1
  64. data/lib/light/services.rb +2 -0
  65. data/light-services.gemspec +6 -8
  66. metadata +54 -26
  67. data/lib/light/services/class_based_collection/base.rb +0 -86
  68. data/lib/light/services/class_based_collection/mount.rb +0 -33
  69. data/lib/light/services/collection/arguments.rb +0 -34
  70. data/lib/light/services/collection/base.rb +0 -59
  71. data/lib/light/services/collection/outputs.rb +0 -16
  72. data/lib/light/services/settings/argument.rb +0 -68
  73. data/lib/light/services/settings/output.rb +0 -34
data/docs/callbacks.md ADDED
@@ -0,0 +1,476 @@
1
+ # Callbacks
2
+
3
+ Callbacks are hooks that allow you to run custom code at specific points during service and step execution. They're perfect for logging, benchmarking, auditing, and other cross-cutting concerns.
4
+
5
+ ## TL;DR
6
+
7
+ - Define callbacks using DSL methods like `before_service_run`, `after_step_run`, etc.
8
+ - Use symbols (method names) or procs/lambdas
9
+ - Callbacks are inherited from parent classes
10
+ - Around callbacks wrap execution and must yield
11
+
12
+ ```ruby
13
+ class User::Charge < ApplicationService
14
+ before_service_run :log_start
15
+ after_service_run :log_end
16
+ on_service_failure :notify_admin
17
+
18
+ around_step_run :benchmark_step
19
+
20
+ step :authorize
21
+ step :charge
22
+
23
+ private
24
+
25
+ def log_start(service)
26
+ Rails.logger.info "Starting #{service.class.name}"
27
+ end
28
+
29
+ def log_end(service)
30
+ Rails.logger.info "Finished #{service.class.name}"
31
+ end
32
+
33
+ def notify_admin(service)
34
+ AdminMailer.service_failed(service).deliver_later
35
+ end
36
+
37
+ def benchmark_step(service, step_name)
38
+ start = Time.current
39
+ yield
40
+ duration = Time.current - start
41
+ Rails.logger.info "Step #{step_name} took #{duration}s"
42
+ end
43
+ end
44
+ ```
45
+
46
+ ## Available Callbacks
47
+
48
+ ### Service Callbacks
49
+
50
+ | Callback | When it runs | Arguments |
51
+ |----------|--------------|-----------|
52
+ | `before_service_run` | Before the service starts executing steps | `(service)` |
53
+ | `after_service_run` | After the service completes (success or failure) | `(service)` |
54
+ | `around_service_run` | Wraps the entire service execution | `(service, &block)` |
55
+ | `on_service_success` | After service completes without errors | `(service)` |
56
+ | `on_service_failure` | After service completes with errors | `(service)` |
57
+
58
+ ### Step Callbacks
59
+
60
+ | Callback | When it runs | Arguments |
61
+ |----------|--------------|-----------|
62
+ | `before_step_run` | Before each step executes | `(service, step_name)` |
63
+ | `after_step_run` | After each step completes (success or failure) | `(service, step_name)` |
64
+ | `around_step_run` | Wraps each step execution | `(service, step_name, &block)` |
65
+ | `on_step_success` | After step completes without errors | `(service, step_name)` |
66
+ | `on_step_failure` | When step produces errors | `(service, step_name)` |
67
+ | `on_step_crash` | When step raises an exception | `(service, step_name, exception)` |
68
+
69
+ {% hint style="info" %}
70
+ Note the difference between `on_step_failure` and `on_step_crash`:
71
+ - `on_step_failure` is called when a step adds errors (similar to `on_service_failure`)
72
+ - `on_step_crash` is called when a step raises an exception
73
+
74
+ When a step crashes (raises an exception), `after_step_run` is NOT called.
75
+ {% endhint %}
76
+
77
+ ## Defining Callbacks
78
+
79
+ ### Using Symbols (Method Names)
80
+
81
+ The most common way to define callbacks is using symbols that reference instance methods:
82
+
83
+ ```ruby
84
+ class Order::Process < ApplicationService
85
+ before_service_run :log_start
86
+ after_service_run :log_end
87
+
88
+ step :validate
89
+ step :process
90
+ step :notify
91
+
92
+ private
93
+
94
+ def log_start(service)
95
+ Rails.logger.info "Processing order started"
96
+ end
97
+
98
+ def log_end(service)
99
+ Rails.logger.info "Processing order finished, success: #{service.success?}"
100
+ end
101
+ end
102
+ ```
103
+
104
+ ### Using Procs/Lambdas
105
+
106
+ For simple callbacks, you can use inline procs:
107
+
108
+ ```ruby
109
+ class Order::Process < ApplicationService
110
+ before_service_run do |service|
111
+ Rails.logger.info "Starting #{service.class.name}"
112
+ end
113
+
114
+ after_service_run do |service|
115
+ Rails.logger.info "Completed with #{service.errors.count} errors"
116
+ end
117
+
118
+ on_step_failure do |service, step_name|
119
+ Rails.logger.warn "Step #{step_name} produced errors"
120
+ end
121
+
122
+ on_step_crash do |service, step_name, exception|
123
+ Bugsnag.notify(exception, step: step_name)
124
+ end
125
+
126
+ step :validate
127
+ step :process
128
+ end
129
+ ```
130
+
131
+ ## Around Callbacks
132
+
133
+ Around callbacks wrap execution and must call `yield` to continue:
134
+
135
+ ```ruby
136
+ class Order::Process < ApplicationService
137
+ around_service_run :with_logging
138
+ around_step_run :with_timing
139
+
140
+ step :validate
141
+ step :process
142
+
143
+ private
144
+
145
+ def with_logging(service)
146
+ Rails.logger.info "=== Starting #{service.class.name} ==="
147
+ yield
148
+ Rails.logger.info "=== Finished #{service.class.name} ==="
149
+ end
150
+
151
+ def with_timing(service, step_name)
152
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
153
+ yield
154
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
155
+ Rails.logger.info "Step :#{step_name} completed in #{duration.round(3)}s"
156
+ end
157
+ end
158
+ ```
159
+
160
+ ### Around Callbacks with Procs
161
+
162
+ When using procs for around callbacks, the block is passed as the last argument:
163
+
164
+ ```ruby
165
+ class Order::Process < ApplicationService
166
+ around_service_run do |service, block|
167
+ Rails.logger.info "Starting..."
168
+ block.call
169
+ Rails.logger.info "Finished!"
170
+ end
171
+
172
+ around_step_run do |service, step_name, block|
173
+ Rails.logger.info "Running step :#{step_name}"
174
+ block.call
175
+ Rails.logger.info "Completed step :#{step_name}"
176
+ end
177
+
178
+ step :process
179
+ end
180
+ ```
181
+
182
+ {% hint style="warning" %}
183
+ Forgetting to call `yield` (or `block.call` for procs) in around callbacks will prevent the service/step from executing!
184
+ {% endhint %}
185
+
186
+ ## Multiple Callbacks
187
+
188
+ You can define multiple callbacks of the same type. They execute in the order they're defined:
189
+
190
+ ```ruby
191
+ class Order::Process < ApplicationService
192
+ before_service_run :log_start
193
+ before_service_run :validate_environment
194
+ before_service_run :check_permissions
195
+
196
+ step :process
197
+
198
+ private
199
+
200
+ def log_start(service)
201
+ # Runs first
202
+ end
203
+
204
+ def validate_environment(service)
205
+ # Runs second
206
+ end
207
+
208
+ def check_permissions(service)
209
+ # Runs third
210
+ end
211
+ end
212
+ ```
213
+
214
+ ### Multiple Around Callbacks
215
+
216
+ Multiple around callbacks are nested, with the first one wrapping the second:
217
+
218
+ ```ruby
219
+ class Order::Process < ApplicationService
220
+ around_service_run :outer_wrapper
221
+ around_service_run :inner_wrapper
222
+
223
+ step :process
224
+
225
+ private
226
+
227
+ def outer_wrapper(service)
228
+ puts "outer before"
229
+ yield
230
+ puts "outer after"
231
+ end
232
+
233
+ def inner_wrapper(service)
234
+ puts "inner before"
235
+ yield
236
+ puts "inner after"
237
+ end
238
+
239
+ # Output:
240
+ # outer before
241
+ # inner before
242
+ # (service executes)
243
+ # inner after
244
+ # outer after
245
+ end
246
+ ```
247
+
248
+ ## Callback Inheritance
249
+
250
+ Callbacks are inherited from parent classes. Child class callbacks run after parent callbacks:
251
+
252
+ ```ruby
253
+ class ApplicationService < Light::Services::Base
254
+ before_service_run :log_service_start
255
+
256
+ private
257
+
258
+ def log_service_start(service)
259
+ Rails.logger.info "[#{service.class.name}] Starting"
260
+ end
261
+ end
262
+
263
+ class Order::Process < ApplicationService
264
+ before_service_run :validate_order
265
+
266
+ step :process
267
+
268
+ private
269
+
270
+ def validate_order(service)
271
+ # Runs after log_service_start
272
+ end
273
+ end
274
+ ```
275
+
276
+ ### Deep Inheritance
277
+
278
+ Callbacks accumulate through the inheritance chain:
279
+
280
+ ```ruby
281
+ class BaseService < Light::Services::Base
282
+ before_service_run :base_callback
283
+ end
284
+
285
+ class MiddleService < BaseService
286
+ before_service_run :middle_callback
287
+ end
288
+
289
+ class ConcreteService < MiddleService
290
+ before_service_run :concrete_callback
291
+
292
+ # Execution order:
293
+ # 1. base_callback
294
+ # 2. middle_callback
295
+ # 3. concrete_callback
296
+ end
297
+ ```
298
+
299
+ ## Execution Order
300
+
301
+ ### Service Callbacks Order
302
+
303
+ ```
304
+ before_service_run
305
+ └── around_service_run (before yield)
306
+ └── [steps execute]
307
+ around_service_run (after yield)
308
+ after_service_run
309
+ on_service_success OR on_service_failure
310
+ ```
311
+
312
+ ### Step Callbacks Order (for each step)
313
+
314
+ **Normal execution (no exception):**
315
+ ```
316
+ before_step_run
317
+ └── around_step_run (before yield)
318
+ └── [step executes]
319
+ around_step_run (after yield)
320
+ after_step_run
321
+ on_step_success OR on_step_failure (if errors were added)
322
+ ```
323
+
324
+ **Exception during step:**
325
+ ```
326
+ before_step_run
327
+ └── around_step_run (before yield)
328
+ └── [step raises exception]
329
+ on_step_crash
330
+ [exception propagates]
331
+ ```
332
+
333
+ ## Use Cases
334
+
335
+ ### Logging
336
+
337
+ ```ruby
338
+ class ApplicationService < Light::Services::Base
339
+ before_service_run :log_start
340
+ after_service_run :log_finish
341
+
342
+ private
343
+
344
+ def log_start(service)
345
+ Rails.logger.tagged(service.class.name) do
346
+ Rails.logger.info "Started with arguments: #{service.arguments.to_h}"
347
+ end
348
+ end
349
+
350
+ def log_finish(service)
351
+ Rails.logger.tagged(service.class.name) do
352
+ if service.success?
353
+ Rails.logger.info "Completed successfully"
354
+ else
355
+ Rails.logger.warn "Failed with errors: #{service.errors.full_messages}"
356
+ end
357
+ end
358
+ end
359
+ end
360
+ ```
361
+
362
+ ### Benchmarking
363
+
364
+ ```ruby
365
+ class ApplicationService < Light::Services::Base
366
+ around_service_run :benchmark
367
+
368
+ private
369
+
370
+ def benchmark(service)
371
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
372
+ yield
373
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
374
+
375
+ if duration > 1.0
376
+ Rails.logger.warn "#{service.class.name} took #{duration.round(2)}s"
377
+ end
378
+ end
379
+ end
380
+ ```
381
+
382
+ ### Error Tracking
383
+
384
+ ```ruby
385
+ class ApplicationService < Light::Services::Base
386
+ on_service_failure :track_failure
387
+ on_step_failure :track_step_error
388
+ on_step_crash :track_step_crash
389
+
390
+ private
391
+
392
+ def track_failure(service)
393
+ Bugsnag.notify("Service failed") do |report|
394
+ report.add_metadata(:service, {
395
+ class: service.class.name,
396
+ errors: service.errors.full_messages,
397
+ arguments: service.arguments.to_h
398
+ })
399
+ end
400
+ end
401
+
402
+ def track_step_error(service, step_name)
403
+ Rails.logger.warn "Step :#{step_name} produced errors in #{service.class.name}"
404
+ end
405
+
406
+ def track_step_crash(service, step_name, exception)
407
+ Bugsnag.notify(exception) do |report|
408
+ report.add_metadata(:service, {
409
+ class: service.class.name,
410
+ step: step_name
411
+ })
412
+ end
413
+ end
414
+ end
415
+ ```
416
+
417
+ ### Audit Trail
418
+
419
+ ```ruby
420
+ class Order::Process < ApplicationService
421
+ after_service_run :create_audit_log
422
+
423
+ arg :order, type: Order
424
+ arg :current_user, type: User
425
+
426
+ step :process
427
+
428
+ private
429
+
430
+ def create_audit_log(service)
431
+ AuditLog.create!(
432
+ user: current_user,
433
+ action: "order.process",
434
+ resource: order,
435
+ success: service.success?,
436
+ metadata: {
437
+ errors: service.errors.full_messages
438
+ }
439
+ )
440
+ end
441
+ end
442
+ ```
443
+
444
+ ### Database Instrumentation
445
+
446
+ ```ruby
447
+ class ApplicationService < Light::Services::Base
448
+ around_step_run :track_queries
449
+
450
+ private
451
+
452
+ def track_queries(service, step_name)
453
+ query_count = 0
454
+
455
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do
456
+ query_count += 1
457
+ end
458
+
459
+ yield
460
+
461
+ ActiveSupport::Notifications.unsubscribe(subscriber)
462
+
463
+ if query_count > 10
464
+ Rails.logger.warn "Step :#{step_name} executed #{query_count} queries"
465
+ end
466
+ end
467
+ end
468
+ ```
469
+
470
+ ## What's Next?
471
+
472
+ Learn about testing your services, including how to test callbacks.
473
+
474
+ [Next: Testing](testing.md)
475
+
476
+
data/docs/concepts.md ADDED
@@ -0,0 +1,80 @@
1
+ # Concepts
2
+
3
+ This section covers the core concepts of Light Services: **Arguments**, **Steps**, **Outputs**, **Context**, **Errors**, and **Callbacks**.
4
+
5
+ ## Service Execution Flow
6
+
7
+ When you call `MyService.run(args)`, the following happens:
8
+
9
+ ```
10
+ ┌─────────────────────────────────────────────────────────────┐
11
+ │ Service.run(args) │
12
+ ├─────────────────────────────────────────────────────────────┤
13
+ │ 1. Load default values for arguments and outputs │
14
+ │ 2. Validate argument types │
15
+ │ 3. Run before_service_run callbacks │
16
+ ├─────────────────────────────────────────────────────────────┤
17
+ │ 4. Begin around_service_run callback │
18
+ │ 5. Begin database transaction (if use_transactions: true) │
19
+ │ ┌─────────────────────────────────────────────────────┐ │
20
+ │ │ 6. Execute steps in order │ │
21
+ │ │ - Run before_step_run / around_step_run │ │
22
+ │ │ - Execute step method │ │
23
+ │ │ - Run after_step_run / on_step_success │ │
24
+ │ │ - Skip if condition (if:/unless:) not met │ │
25
+ │ │ - Stop if errors.break? is true │ │
26
+ │ │ - Stop if done! was called │ │
27
+ │ ├─────────────────────────────────────────────────────┤ │
28
+ │ │ 7. On error → Rollback transaction │ │
29
+ │ │ On success → Commit transaction │ │
30
+ │ └─────────────────────────────────────────────────────┘ │
31
+ │ 8. End around_service_run callback │
32
+ ├─────────────────────────────────────────────────────────────┤
33
+ │ 9. Run steps marked with always: true (unless done! called) │
34
+ │ 10. Validate output types (if success) │
35
+ │ 11. Copy errors/warnings to parent service (if in context) │
36
+ │ 12. Run after_service_run callback │
37
+ │ 13. Run on_service_success or on_service_failure callback │
38
+ ├─────────────────────────────────────────────────────────────┤
39
+ │ 14. Return service instance │
40
+ │ - service.success? / service.failed? │
41
+ │ - service.outputs / service.errors │
42
+ └─────────────────────────────────────────────────────────────┘
43
+ ```
44
+
45
+ ## Arguments
46
+
47
+ Arguments are the inputs provided to a service when it is invoked. They can be validated by type, assigned default values, and be designated as optional or required.
48
+
49
+ [Read more about arguments](arguments.md)
50
+
51
+ ## Steps
52
+
53
+ Steps are the fundamental units of work within a service, representing each individual task a service performs. They can be executed conditionally or skipped.
54
+
55
+ [Read more about steps](steps.md)
56
+
57
+ ## Outputs
58
+
59
+ Outputs are the results produced by a service upon its completion. They can have default values and be validated by type.
60
+
61
+ [Read more about outputs](outputs.md)
62
+
63
+ ## Context
64
+
65
+ Context refers to the shared state that passes between services in a service chain, enabling the transfer of arguments and error states from one service to another.
66
+
67
+ [Read more about context](context.md)
68
+
69
+ ## Errors
70
+
71
+ Errors occur during service execution and cause execution to halt. When an error occurs, all services in the same context chain stop, and database transactions are rolled back (configurable).
72
+
73
+ [Read more about errors](errors.md)
74
+
75
+ ## Callbacks
76
+
77
+ Callbacks allow you to run custom code at specific points during service and step execution. They're perfect for logging, benchmarking, auditing, and other cross-cutting concerns.
78
+
79
+ [Read more about callbacks](callbacks.md)
80
+
@@ -0,0 +1,168 @@
1
+ # Configuration
2
+
3
+ Light Services provides a flexible configuration system that allows you to customize behavior at three levels: global, per-service, and per-call.
4
+
5
+ ## Global Configuration
6
+
7
+ Configure Light Services globally using an initializer. For Rails applications, create `config/initializers/light_services.rb`:
8
+
9
+ ```ruby
10
+ Light::Services.configure do |config|
11
+ # Transaction settings
12
+ config.use_transactions = true # Wrap each service in a database transaction
13
+
14
+ # Error behavior
15
+ config.load_errors = true # Copy errors to parent service in context chain
16
+ config.break_on_error = true # Stop step execution when an error is added
17
+ config.raise_on_error = false # Raise an exception when an error is added
18
+ config.rollback_on_error = true # Rollback transaction when an error is added
19
+
20
+ # Warning behavior
21
+ config.load_warnings = true # Copy warnings to parent service in context chain
22
+ config.break_on_warning = false # Stop step execution when a warning is added
23
+ config.raise_on_warning = false # Raise an exception when a warning is added
24
+ config.rollback_on_warning = false # Rollback transaction when a warning is added
25
+ end
26
+ ```
27
+
28
+ ## Default Values
29
+
30
+ | Option | Default | Description |
31
+ |--------|---------|-------------|
32
+ | `use_transactions` | `true` | Wraps service execution in `ActiveRecord::Base.transaction` |
33
+ | `load_errors` | `true` | Propagates errors to parent service when using `.with(self)` |
34
+ | `break_on_error` | `true` | Stops executing remaining steps when an error is added |
35
+ | `raise_on_error` | `false` | Raises `Light::Services::Error` when an error is added |
36
+ | `rollback_on_error` | `true` | Rolls back the transaction when an error is added |
37
+ | `load_warnings` | `true` | Propagates warnings to parent service when using `.with(self)` |
38
+ | `break_on_warning` | `false` | Stops executing remaining steps when a warning is added |
39
+ | `raise_on_warning` | `false` | Raises `Light::Services::Error` when a warning is added |
40
+ | `rollback_on_warning` | `false` | Rolls back the transaction when a warning is added |
41
+
42
+ ## Per-Service Configuration
43
+
44
+ Override global configuration for a specific service class using the `config` class method:
45
+
46
+ ```ruby
47
+ class CriticalPaymentService < ApplicationService
48
+ # This service will raise exceptions instead of collecting errors
49
+ config raise_on_error: true
50
+
51
+ step :process_payment
52
+ step :send_receipt
53
+
54
+ # ...
55
+ end
56
+ ```
57
+
58
+ ```ruby
59
+ class NonCriticalNotificationService < ApplicationService
60
+ # This service doesn't need transactions and shouldn't stop on errors
61
+ config use_transactions: false, break_on_error: false
62
+
63
+ step :send_push_notification
64
+ step :send_email_notification
65
+
66
+ # ...
67
+ end
68
+ ```
69
+
70
+ ## Per-Call Configuration
71
+
72
+ Override configuration for a single service call:
73
+
74
+ ```ruby
75
+ # Pass config as second argument to run
76
+ MyService.run({ name: "John" }, { raise_on_error: true })
77
+
78
+ # Or use with() for context-based calls
79
+ MyService.with({ raise_on_error: true }).run(name: "John")
80
+
81
+ # Combine with parent service context
82
+ ChildService
83
+ .with(self, { use_transactions: false })
84
+ .run(data: some_data)
85
+ ```
86
+
87
+ ## Configuration Precedence
88
+
89
+ Configuration is merged in this order (later overrides earlier):
90
+
91
+ 1. Global configuration (from initializer)
92
+ 2. Per-service configuration (from `config` class method)
93
+ 3. Per-call configuration (from `run` or `with` arguments)
94
+
95
+ ```ruby
96
+ # Global: raise_on_error = false
97
+ Light::Services.configure do |config|
98
+ config.raise_on_error = false
99
+ end
100
+
101
+ # Per-service: raise_on_error = true (overrides global)
102
+ class MyService < ApplicationService
103
+ config raise_on_error: true
104
+ end
105
+
106
+ # Per-call: raise_on_error = false (overrides per-service)
107
+ MyService.run(args, { raise_on_error: false })
108
+ ```
109
+
110
+ ## Common Configuration Patterns
111
+
112
+ ### Strict Mode for Critical Services
113
+
114
+ ```ruby
115
+ class Payment::Process < ApplicationService
116
+ config raise_on_error: true, rollback_on_error: true
117
+
118
+ # Any error will raise an exception and rollback the transaction
119
+ end
120
+ ```
121
+
122
+ ### Fire-and-Forget Services
123
+
124
+ ```ruby
125
+ class Analytics::Track < ApplicationService
126
+ config use_transactions: false, break_on_error: false, load_errors: false
127
+
128
+ # Errors won't stop execution or propagate to parent services
129
+ end
130
+ ```
131
+
132
+ ### Background Job Services
133
+
134
+ ```ruby
135
+ class BackgroundTaskService < ApplicationService
136
+ # Background jobs typically handle their own transactions
137
+ config use_transactions: false
138
+ end
139
+ ```
140
+
141
+ ## Disabling Transactions
142
+
143
+ If you're not using ActiveRecord or want to manage transactions yourself:
144
+
145
+ ```ruby
146
+ Light::Services.configure do |config|
147
+ config.use_transactions = false
148
+ end
149
+ ```
150
+
151
+ Or disable for specific services:
152
+
153
+ ```ruby
154
+ class MyService < ApplicationService
155
+ config use_transactions: false
156
+ end
157
+ ```
158
+
159
+ {% hint style="info" %}
160
+ When `use_transactions` is `true`, Light Services uses `ActiveRecord::Base.transaction(requires_new: true)` to create savepoints, allowing nested services to rollback independently.
161
+ {% endhint %}
162
+
163
+ ## What's Next?
164
+
165
+ Now that you understand configuration, learn about the core concepts:
166
+
167
+ [Next: Concepts](concepts.md)
168
+