light-services 2.2.1 → 3.1.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.
- checksums.yaml +4 -4
- data/.github/config/rubocop_linter_action.yml +4 -4
- data/.github/workflows/ci.yml +12 -12
- data/.gitignore +1 -0
- data/.rubocop.yml +83 -7
- data/CHANGELOG.md +38 -0
- data/CLAUDE.md +139 -0
- data/Gemfile +16 -11
- data/Gemfile.lock +53 -27
- data/README.md +84 -21
- data/docs/arguments.md +290 -0
- data/docs/best-practices.md +153 -0
- data/docs/callbacks.md +476 -0
- data/docs/concepts.md +80 -0
- data/docs/configuration.md +204 -0
- data/docs/context.md +128 -0
- data/docs/crud.md +525 -0
- data/docs/errors.md +280 -0
- data/docs/generators.md +250 -0
- data/docs/outputs.md +158 -0
- data/docs/pundit-authorization.md +320 -0
- data/docs/quickstart.md +134 -0
- data/docs/readme.md +101 -0
- data/docs/recipes.md +14 -0
- data/docs/rubocop.md +285 -0
- data/docs/ruby-lsp.md +133 -0
- data/docs/service-rendering.md +222 -0
- data/docs/steps.md +391 -0
- data/docs/summary.md +21 -0
- data/docs/testing.md +549 -0
- data/lib/generators/light_services/install/USAGE +15 -0
- data/lib/generators/light_services/install/install_generator.rb +41 -0
- data/lib/generators/light_services/install/templates/application_service.rb.tt +8 -0
- data/lib/generators/light_services/install/templates/application_service_spec.rb.tt +7 -0
- data/lib/generators/light_services/install/templates/initializer.rb.tt +30 -0
- data/lib/generators/light_services/service/USAGE +21 -0
- data/lib/generators/light_services/service/service_generator.rb +68 -0
- data/lib/generators/light_services/service/templates/service.rb.tt +48 -0
- data/lib/generators/light_services/service/templates/service_spec.rb.tt +40 -0
- data/lib/light/services/base.rb +134 -122
- data/lib/light/services/base_with_context.rb +23 -1
- data/lib/light/services/callbacks.rb +157 -0
- data/lib/light/services/collection.rb +145 -0
- data/lib/light/services/concerns/execution.rb +79 -0
- data/lib/light/services/concerns/parent_service.rb +34 -0
- data/lib/light/services/concerns/state_management.rb +30 -0
- data/lib/light/services/config.rb +82 -16
- data/lib/light/services/constants.rb +100 -0
- data/lib/light/services/dsl/arguments_dsl.rb +85 -0
- data/lib/light/services/dsl/outputs_dsl.rb +81 -0
- data/lib/light/services/dsl/steps_dsl.rb +205 -0
- data/lib/light/services/dsl/validation.rb +162 -0
- data/lib/light/services/exceptions.rb +25 -2
- data/lib/light/services/message.rb +28 -3
- data/lib/light/services/messages.rb +92 -32
- data/lib/light/services/rspec/matchers/define_argument.rb +174 -0
- data/lib/light/services/rspec/matchers/define_output.rb +147 -0
- data/lib/light/services/rspec/matchers/define_step.rb +225 -0
- data/lib/light/services/rspec/matchers/execute_step.rb +230 -0
- data/lib/light/services/rspec/matchers/have_error_on.rb +148 -0
- data/lib/light/services/rspec/matchers/have_warning_on.rb +148 -0
- data/lib/light/services/rspec/matchers/trigger_callback.rb +138 -0
- data/lib/light/services/rspec.rb +15 -0
- data/lib/light/services/rubocop/cop/light_services/argument_type_required.rb +52 -0
- data/lib/light/services/rubocop/cop/light_services/condition_method_exists.rb +173 -0
- data/lib/light/services/rubocop/cop/light_services/deprecated_methods.rb +113 -0
- data/lib/light/services/rubocop/cop/light_services/dsl_order.rb +176 -0
- data/lib/light/services/rubocop/cop/light_services/missing_private_keyword.rb +102 -0
- data/lib/light/services/rubocop/cop/light_services/no_direct_instantiation.rb +66 -0
- data/lib/light/services/rubocop/cop/light_services/output_type_required.rb +52 -0
- data/lib/light/services/rubocop/cop/light_services/step_method_exists.rb +109 -0
- data/lib/light/services/rubocop.rb +12 -0
- data/lib/light/services/settings/field.rb +114 -0
- data/lib/light/services/settings/step.rb +53 -20
- data/lib/light/services/utils.rb +38 -0
- data/lib/light/services/version.rb +1 -1
- data/lib/light/services.rb +2 -0
- data/lib/ruby_lsp/light_services/addon.rb +36 -0
- data/lib/ruby_lsp/light_services/definition.rb +132 -0
- data/lib/ruby_lsp/light_services/indexing_enhancement.rb +263 -0
- data/light-services.gemspec +6 -8
- metadata +68 -26
- data/lib/light/services/class_based_collection/base.rb +0 -86
- data/lib/light/services/class_based_collection/mount.rb +0 -33
- data/lib/light/services/collection/arguments.rb +0 -34
- data/lib/light/services/collection/base.rb +0 -59
- data/lib/light/services/collection/outputs.rb +0 -16
- data/lib/light/services/settings/argument.rb +0 -68
- 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 stop! 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 stop! 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
|
+
|