better_service 1.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +1321 -0
- data/Rakefile +15 -0
- data/lib/better_service/cache_service.rb +310 -0
- data/lib/better_service/concerns/instrumentation.rb +242 -0
- data/lib/better_service/concerns/serviceable/authorizable.rb +106 -0
- data/lib/better_service/concerns/serviceable/cacheable.rb +97 -0
- data/lib/better_service/concerns/serviceable/messageable.rb +30 -0
- data/lib/better_service/concerns/serviceable/presentable.rb +66 -0
- data/lib/better_service/concerns/serviceable/transactional.rb +51 -0
- data/lib/better_service/concerns/serviceable/validatable.rb +58 -0
- data/lib/better_service/concerns/serviceable/viewable.rb +49 -0
- data/lib/better_service/concerns/serviceable.rb +12 -0
- data/lib/better_service/concerns/workflowable/callbacks.rb +116 -0
- data/lib/better_service/concerns/workflowable/context.rb +108 -0
- data/lib/better_service/concerns/workflowable/step.rb +141 -0
- data/lib/better_service/concerns/workflowable.rb +12 -0
- data/lib/better_service/configuration.rb +113 -0
- data/lib/better_service/errors/better_service_error.rb +271 -0
- data/lib/better_service/errors/configuration/configuration_error.rb +21 -0
- data/lib/better_service/errors/configuration/invalid_configuration_error.rb +28 -0
- data/lib/better_service/errors/configuration/invalid_schema_error.rb +28 -0
- data/lib/better_service/errors/configuration/nil_user_error.rb +37 -0
- data/lib/better_service/errors/configuration/schema_required_error.rb +29 -0
- data/lib/better_service/errors/runtime/authorization_error.rb +38 -0
- data/lib/better_service/errors/runtime/database_error.rb +38 -0
- data/lib/better_service/errors/runtime/execution_error.rb +27 -0
- data/lib/better_service/errors/runtime/resource_not_found_error.rb +38 -0
- data/lib/better_service/errors/runtime/runtime_error.rb +22 -0
- data/lib/better_service/errors/runtime/transaction_error.rb +34 -0
- data/lib/better_service/errors/runtime/validation_error.rb +42 -0
- data/lib/better_service/errors/workflowable/configuration/duplicate_step_error.rb +27 -0
- data/lib/better_service/errors/workflowable/configuration/invalid_step_error.rb +12 -0
- data/lib/better_service/errors/workflowable/configuration/step_not_found_error.rb +29 -0
- data/lib/better_service/errors/workflowable/configuration/workflow_configuration_error.rb +24 -0
- data/lib/better_service/errors/workflowable/runtime/rollback_error.rb +46 -0
- data/lib/better_service/errors/workflowable/runtime/step_execution_error.rb +47 -0
- data/lib/better_service/errors/workflowable/runtime/workflow_execution_error.rb +40 -0
- data/lib/better_service/errors/workflowable/runtime/workflow_runtime_error.rb +25 -0
- data/lib/better_service/railtie.rb +6 -0
- data/lib/better_service/services/action_service.rb +60 -0
- data/lib/better_service/services/base.rb +249 -0
- data/lib/better_service/services/create_service.rb +60 -0
- data/lib/better_service/services/destroy_service.rb +57 -0
- data/lib/better_service/services/index_service.rb +56 -0
- data/lib/better_service/services/show_service.rb +44 -0
- data/lib/better_service/services/update_service.rb +58 -0
- data/lib/better_service/subscribers/log_subscriber.rb +131 -0
- data/lib/better_service/subscribers/stats_subscriber.rb +208 -0
- data/lib/better_service/version.rb +3 -0
- data/lib/better_service/workflows/base.rb +106 -0
- data/lib/better_service/workflows/dsl.rb +59 -0
- data/lib/better_service/workflows/execution.rb +89 -0
- data/lib/better_service/workflows/result_builder.rb +67 -0
- data/lib/better_service/workflows/rollback_support.rb +44 -0
- data/lib/better_service/workflows/transaction_support.rb +32 -0
- data/lib/better_service.rb +28 -0
- data/lib/generators/serviceable/action_generator.rb +29 -0
- data/lib/generators/serviceable/create_generator.rb +27 -0
- data/lib/generators/serviceable/destroy_generator.rb +27 -0
- data/lib/generators/serviceable/index_generator.rb +27 -0
- data/lib/generators/serviceable/scaffold_generator.rb +70 -0
- data/lib/generators/serviceable/show_generator.rb +27 -0
- data/lib/generators/serviceable/templates/action_service.rb.tt +42 -0
- data/lib/generators/serviceable/templates/create_service.rb.tt +33 -0
- data/lib/generators/serviceable/templates/destroy_service.rb.tt +40 -0
- data/lib/generators/serviceable/templates/index_service.rb.tt +54 -0
- data/lib/generators/serviceable/templates/service_test.rb.tt +23 -0
- data/lib/generators/serviceable/templates/show_service.rb.tt +37 -0
- data/lib/generators/serviceable/templates/update_service.rb.tt +50 -0
- data/lib/generators/serviceable/update_generator.rb +27 -0
- data/lib/generators/workflowable/WORKFLOW_README +27 -0
- data/lib/generators/workflowable/templates/workflow.rb.tt +72 -0
- data/lib/generators/workflowable/templates/workflow_test.rb.tt +62 -0
- data/lib/generators/workflowable/workflow_generator.rb +60 -0
- data/lib/tasks/better_service_tasks.rake +4 -0
- metadata +180 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterService
|
|
4
|
+
module Errors
|
|
5
|
+
module Runtime
|
|
6
|
+
# Raised when user is not authorized to perform the action
|
|
7
|
+
#
|
|
8
|
+
# This error is raised when the authorize_with block returns false.
|
|
9
|
+
# Authorization checks happen before the service execution begins.
|
|
10
|
+
#
|
|
11
|
+
# @example Authorization failure
|
|
12
|
+
# class Post::DestroyService < BetterService::Services::DestroyService
|
|
13
|
+
# model_class Post
|
|
14
|
+
#
|
|
15
|
+
# schema do
|
|
16
|
+
# required(:id).filled(:integer)
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# authorize_with do
|
|
20
|
+
# resource.user_id == user.id # Only owner can delete
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# # User tries to delete someone else's post
|
|
25
|
+
# Post::DestroyService.new(current_user, params: { id: other_users_post_id }).call
|
|
26
|
+
# # => raises AuthorizationError
|
|
27
|
+
#
|
|
28
|
+
# @example Handling authorization errors
|
|
29
|
+
# begin
|
|
30
|
+
# MyService.new(user, params: params).call
|
|
31
|
+
# rescue BetterService::Errors::Runtime::AuthorizationError => e
|
|
32
|
+
# render json: { error: e.message }, status: :forbidden
|
|
33
|
+
# end
|
|
34
|
+
class AuthorizationError < RuntimeError
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterService
|
|
4
|
+
module Errors
|
|
5
|
+
module Runtime
|
|
6
|
+
# Raised when a database operation fails
|
|
7
|
+
#
|
|
8
|
+
# This error wraps ActiveRecord database errors such as RecordInvalid,
|
|
9
|
+
# RecordNotSaved, constraint violations, and other database-level failures.
|
|
10
|
+
#
|
|
11
|
+
# @example Record validation fails
|
|
12
|
+
# class UserCreateService < BetterService::Services::CreateService
|
|
13
|
+
# model_class User
|
|
14
|
+
#
|
|
15
|
+
# schema do
|
|
16
|
+
# required(:email).filled(:string)
|
|
17
|
+
# end
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# UserCreateService.new(user, params: { email: "invalid" }).call
|
|
21
|
+
# # => raises DatabaseError wrapping ActiveRecord::RecordInvalid
|
|
22
|
+
#
|
|
23
|
+
# @example Constraint violation
|
|
24
|
+
# class MyService < BetterService::Services::Base
|
|
25
|
+
# schema { required(:user_id).filled(:integer) }
|
|
26
|
+
#
|
|
27
|
+
# process_with do |data|
|
|
28
|
+
# User.create!(email: "duplicate@example.com") # Unique constraint fails
|
|
29
|
+
# end
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# MyService.new(user, params: { user_id: 1 }).call
|
|
33
|
+
# # => raises DatabaseError
|
|
34
|
+
class DatabaseError < RuntimeError
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterService
|
|
4
|
+
module Errors
|
|
5
|
+
module Runtime
|
|
6
|
+
# Raised when unexpected error occurs during service execution
|
|
7
|
+
#
|
|
8
|
+
# This error wraps unexpected StandardError exceptions that occur during
|
|
9
|
+
# the service's search, process, transform, or respond phases.
|
|
10
|
+
#
|
|
11
|
+
# @example Unexpected error in service
|
|
12
|
+
# class MyService < BetterService::Services::Base
|
|
13
|
+
# schema { }
|
|
14
|
+
#
|
|
15
|
+
# process_with do |data|
|
|
16
|
+
# # Some operation that fails unexpectedly
|
|
17
|
+
# third_party_api.call # raises SocketError
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# MyService.new(user, params: {}).call
|
|
22
|
+
# # => raises ExecutionError wrapping SocketError
|
|
23
|
+
class ExecutionError < RuntimeError
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterService
|
|
4
|
+
module Errors
|
|
5
|
+
module Runtime
|
|
6
|
+
# Raised when a required resource is not found
|
|
7
|
+
#
|
|
8
|
+
# This error wraps ActiveRecord::RecordNotFound exceptions raised during
|
|
9
|
+
# service execution (usually in the search phase).
|
|
10
|
+
#
|
|
11
|
+
# @example Resource not found
|
|
12
|
+
# class UserShowService < BetterService::Services::ShowService
|
|
13
|
+
# model_class User
|
|
14
|
+
#
|
|
15
|
+
# schema do
|
|
16
|
+
# required(:id).filled(:integer)
|
|
17
|
+
# end
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# UserShowService.new(user, params: { id: 99999 }).call
|
|
21
|
+
# # => raises ResourceNotFoundError wrapping ActiveRecord::RecordNotFound
|
|
22
|
+
#
|
|
23
|
+
# @example In custom service
|
|
24
|
+
# class MyService < BetterService::Services::Base
|
|
25
|
+
# schema { required(:user_id).filled(:integer) }
|
|
26
|
+
#
|
|
27
|
+
# search_with do
|
|
28
|
+
# User.find(params[:user_id]) # Raises RecordNotFound if not exists
|
|
29
|
+
# end
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# MyService.new(user, params: { user_id: 99999 }).call
|
|
33
|
+
# # => raises ResourceNotFoundError
|
|
34
|
+
class ResourceNotFoundError < RuntimeError
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterService
|
|
4
|
+
module Errors
|
|
5
|
+
module Runtime
|
|
6
|
+
# Base class for all runtime errors in BetterService
|
|
7
|
+
#
|
|
8
|
+
# Runtime errors are raised when something goes wrong during service execution
|
|
9
|
+
# due to external factors (database, network, invalid data, etc.).
|
|
10
|
+
# These are not programming errors.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# raise BetterService::Errors::Runtime::RuntimeError.new(
|
|
14
|
+
# "Runtime error occurred",
|
|
15
|
+
# code: :runtime_error,
|
|
16
|
+
# context: { service: "MyService", operation: "fetch_data" }
|
|
17
|
+
# )
|
|
18
|
+
class RuntimeError < BetterServiceError
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterService
|
|
4
|
+
module Errors
|
|
5
|
+
module Runtime
|
|
6
|
+
# Raised when a database transaction fails
|
|
7
|
+
#
|
|
8
|
+
# This error is raised when ActiveRecord transaction operations fail,
|
|
9
|
+
# such as deadlocks, serialization errors, or constraint violations.
|
|
10
|
+
#
|
|
11
|
+
# @example Transaction failure
|
|
12
|
+
# class MyService < BetterService::Services::Base
|
|
13
|
+
# config do
|
|
14
|
+
# use_transaction true
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# schema { }
|
|
18
|
+
#
|
|
19
|
+
# process_with do |data|
|
|
20
|
+
# # Database operation that causes deadlock
|
|
21
|
+
# User.transaction do
|
|
22
|
+
# user.lock!
|
|
23
|
+
# other_user.lock! # Deadlock!
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# MyService.new(user, params: {}).call
|
|
29
|
+
# # => raises TransactionError
|
|
30
|
+
class TransactionError < RuntimeError
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterService
|
|
4
|
+
module Errors
|
|
5
|
+
module Runtime
|
|
6
|
+
# Raised when parameter validation fails
|
|
7
|
+
#
|
|
8
|
+
# This error is raised during service initialization when Dry::Schema
|
|
9
|
+
# validation fails. The validation errors are available in the context.
|
|
10
|
+
#
|
|
11
|
+
# @example Validation failure
|
|
12
|
+
# class MyService < BetterService::Services::Base
|
|
13
|
+
# schema do
|
|
14
|
+
# required(:email).filled(:string)
|
|
15
|
+
# required(:age).filled(:integer, gt?: 18)
|
|
16
|
+
# end
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# MyService.new(user, params: { email: "", age: 15 }).call
|
|
20
|
+
# # => raises ValidationError with context:
|
|
21
|
+
# # {
|
|
22
|
+
# # service: "MyService",
|
|
23
|
+
# # validation_errors: {
|
|
24
|
+
# # email: ["must be filled"],
|
|
25
|
+
# # age: ["must be greater than 18"]
|
|
26
|
+
# # }
|
|
27
|
+
# # }
|
|
28
|
+
#
|
|
29
|
+
# @example Handling validation errors
|
|
30
|
+
# begin
|
|
31
|
+
# MyService.new(user, params: invalid_params).call
|
|
32
|
+
# rescue BetterService::Errors::Runtime::ValidationError => e
|
|
33
|
+
# render json: {
|
|
34
|
+
# error: e.message,
|
|
35
|
+
# validation_errors: e.context[:validation_errors]
|
|
36
|
+
# }, status: :unprocessable_entity
|
|
37
|
+
# end
|
|
38
|
+
class ValidationError < RuntimeError
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterService
|
|
4
|
+
module Errors
|
|
5
|
+
module Workflowable
|
|
6
|
+
module Configuration
|
|
7
|
+
# Raised when a workflow has duplicate step names
|
|
8
|
+
#
|
|
9
|
+
# Each step in a workflow must have a unique name. This error is raised
|
|
10
|
+
# if you try to define multiple steps with the same name.
|
|
11
|
+
#
|
|
12
|
+
# @example Duplicate step names
|
|
13
|
+
# class MyWorkflow < BetterService::Workflow
|
|
14
|
+
# step :create_user,
|
|
15
|
+
# with: User::CreateService
|
|
16
|
+
#
|
|
17
|
+
# step :create_user, # Duplicate name!
|
|
18
|
+
# with: Profile::CreateService
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# # => raises DuplicateStepError during class definition
|
|
22
|
+
class DuplicateStepError < WorkflowConfigurationError
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterService
|
|
4
|
+
module Errors
|
|
5
|
+
module Workflowable
|
|
6
|
+
module Configuration
|
|
7
|
+
# Raised when a referenced workflow step is not found
|
|
8
|
+
#
|
|
9
|
+
# This error is raised when trying to access a step that doesn't exist
|
|
10
|
+
# in the workflow definition, such as in dependencies or conditionals.
|
|
11
|
+
#
|
|
12
|
+
# @example Step not found
|
|
13
|
+
# class MyWorkflow < BetterService::Workflow
|
|
14
|
+
# step :first_step,
|
|
15
|
+
# with: FirstService
|
|
16
|
+
#
|
|
17
|
+
# step :second_step,
|
|
18
|
+
# with: SecondService,
|
|
19
|
+
# if: ->(ctx) { ctx.non_existent_step.success? } # Step doesn't exist
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# MyWorkflow.new(user, params: {}).call
|
|
23
|
+
# # => raises StepNotFoundError
|
|
24
|
+
class StepNotFoundError < WorkflowConfigurationError
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterService
|
|
4
|
+
module Errors
|
|
5
|
+
module Workflowable
|
|
6
|
+
module Configuration
|
|
7
|
+
# Base class for workflow configuration errors
|
|
8
|
+
#
|
|
9
|
+
# Raised when a workflow is incorrectly configured, such as invalid steps,
|
|
10
|
+
# missing service classes, or conflicting configurations.
|
|
11
|
+
#
|
|
12
|
+
# @example Invalid workflow configuration
|
|
13
|
+
# class MyWorkflow < BetterService::Workflow
|
|
14
|
+
# step :invalid,
|
|
15
|
+
# with: nil # Missing service class
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# # => raises WorkflowConfigurationError during class definition
|
|
19
|
+
class WorkflowConfigurationError < BetterService::Errors::Configuration::ConfigurationError
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterService
|
|
4
|
+
module Errors
|
|
5
|
+
module Workflowable
|
|
6
|
+
module Runtime
|
|
7
|
+
# Raised when workflow rollback fails
|
|
8
|
+
#
|
|
9
|
+
# This error is raised when a step's rollback block fails during workflow rollback.
|
|
10
|
+
# Rollback failures are serious as they may leave the system in an inconsistent state.
|
|
11
|
+
#
|
|
12
|
+
# @example Rollback failure
|
|
13
|
+
# class MyWorkflow < BetterService::Workflow
|
|
14
|
+
# step :create_user,
|
|
15
|
+
# with: User::CreateService,
|
|
16
|
+
# rollback: ->(ctx) { ctx.user.destroy! }
|
|
17
|
+
#
|
|
18
|
+
# step :charge_payment,
|
|
19
|
+
# with: Payment::ChargeService,
|
|
20
|
+
# rollback: ->(ctx) {
|
|
21
|
+
# # Rollback fails - payment gateway is down
|
|
22
|
+
# PaymentGateway.refund(ctx.charge.id) # raises error
|
|
23
|
+
# }
|
|
24
|
+
#
|
|
25
|
+
# step :send_email,
|
|
26
|
+
# with: Email::WelcomeService # This step fails
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# MyWorkflow.new(user, params: {}).call
|
|
30
|
+
# # => send_email fails, triggers rollback
|
|
31
|
+
# # => charge_payment rollback fails
|
|
32
|
+
# # => raises RollbackError with context:
|
|
33
|
+
# # {
|
|
34
|
+
# # workflow: "MyWorkflow",
|
|
35
|
+
# # step: :charge_payment,
|
|
36
|
+
# # executed_steps: [:create_user, :charge_payment]
|
|
37
|
+
# # }
|
|
38
|
+
#
|
|
39
|
+
# @note Rollback errors indicate potential data inconsistency and should be
|
|
40
|
+
# monitored and handled carefully in production systems.
|
|
41
|
+
class RollbackError < WorkflowRuntimeError
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterService
|
|
4
|
+
module Errors
|
|
5
|
+
module Workflowable
|
|
6
|
+
module Runtime
|
|
7
|
+
# Raised when a workflow step execution fails
|
|
8
|
+
#
|
|
9
|
+
# This error is raised when a step in the workflow fails and the step is not optional.
|
|
10
|
+
# The error includes context about which step failed and what steps were executed.
|
|
11
|
+
#
|
|
12
|
+
# @example Step execution failure
|
|
13
|
+
# class MyWorkflow < BetterService::Workflow
|
|
14
|
+
# step :create_user,
|
|
15
|
+
# with: User::CreateService
|
|
16
|
+
#
|
|
17
|
+
# step :charge_payment,
|
|
18
|
+
# with: Payment::ChargeService # This step fails
|
|
19
|
+
#
|
|
20
|
+
# step :send_email,
|
|
21
|
+
# with: Email::WelcomeService # This step never executes
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# MyWorkflow.new(user, params: {}).call
|
|
25
|
+
# # => raises StepExecutionError with context:
|
|
26
|
+
# # {
|
|
27
|
+
# # workflow: "MyWorkflow",
|
|
28
|
+
# # step: :charge_payment,
|
|
29
|
+
# # steps_executed: [:create_user],
|
|
30
|
+
# # errors: { ... }
|
|
31
|
+
# # }
|
|
32
|
+
#
|
|
33
|
+
# @example Optional step failures don't raise
|
|
34
|
+
# class MyWorkflow < BetterService::Workflow
|
|
35
|
+
# step :create_user,
|
|
36
|
+
# with: User::CreateService
|
|
37
|
+
#
|
|
38
|
+
# step :send_email,
|
|
39
|
+
# with: Email::WelcomeService,
|
|
40
|
+
# optional: true # Failure won't raise StepExecutionError
|
|
41
|
+
# end
|
|
42
|
+
class StepExecutionError < WorkflowRuntimeError
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterService
|
|
4
|
+
module Errors
|
|
5
|
+
module Workflowable
|
|
6
|
+
module Runtime
|
|
7
|
+
# Raised when workflow execution fails
|
|
8
|
+
#
|
|
9
|
+
# This error is raised when unexpected errors occur during workflow execution,
|
|
10
|
+
# wrapping the original exception with workflow context.
|
|
11
|
+
#
|
|
12
|
+
# @example Workflow execution failure
|
|
13
|
+
# class MyWorkflow < BetterService::Workflow
|
|
14
|
+
# step :create_user,
|
|
15
|
+
# with: User::CreateService
|
|
16
|
+
#
|
|
17
|
+
# step :send_email,
|
|
18
|
+
# with: Email::WelcomeService
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# MyWorkflow.new(user, params: {}).call
|
|
22
|
+
# # If unexpected error occurs => raises WorkflowExecutionError
|
|
23
|
+
#
|
|
24
|
+
# @example Error context
|
|
25
|
+
# begin
|
|
26
|
+
# MyWorkflow.new(user, params: params).call
|
|
27
|
+
# rescue BetterService::Errors::Workflowable::Runtime::WorkflowExecutionError => e
|
|
28
|
+
# e.context
|
|
29
|
+
# # => {
|
|
30
|
+
# # workflow: "MyWorkflow",
|
|
31
|
+
# # steps_executed: [:create_user],
|
|
32
|
+
# # steps_skipped: []
|
|
33
|
+
# # }
|
|
34
|
+
# end
|
|
35
|
+
class WorkflowExecutionError < WorkflowRuntimeError
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterService
|
|
4
|
+
module Errors
|
|
5
|
+
module Workflowable
|
|
6
|
+
module Runtime
|
|
7
|
+
# Base class for workflow runtime errors
|
|
8
|
+
#
|
|
9
|
+
# Raised when errors occur during workflow execution, such as step failures,
|
|
10
|
+
# rollback failures, or workflow execution errors.
|
|
11
|
+
#
|
|
12
|
+
# @example Workflow runtime error
|
|
13
|
+
# class MyWorkflow < BetterService::Workflow
|
|
14
|
+
# step :first_step,
|
|
15
|
+
# with: FirstService
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# MyWorkflow.new(user, params: {}).call
|
|
19
|
+
# # If FirstService fails => raises WorkflowRuntimeError (or subclass)
|
|
20
|
+
class WorkflowRuntimeError < BetterService::Errors::Runtime::RuntimeError
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module BetterService
|
|
6
|
+
module Services
|
|
7
|
+
# ActionService - Specialized service for custom actions/transitions
|
|
8
|
+
#
|
|
9
|
+
# Returns: { resource: {}, metadata: { action: :custom_action_name } }
|
|
10
|
+
#
|
|
11
|
+
# Example:
|
|
12
|
+
# class Bookings::AcceptService < BetterService::Services::ActionService
|
|
13
|
+
# action_name :accepted # Sets metadata action
|
|
14
|
+
#
|
|
15
|
+
# schema do
|
|
16
|
+
# required(:id).filled(:integer)
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# search_with do
|
|
20
|
+
# { resource: user.bookings.find(params[:id]) }
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# process_with do |data|
|
|
24
|
+
# booking = data[:resource]
|
|
25
|
+
# booking.update!(status: 'accepted', accepted_at: Time.current)
|
|
26
|
+
# { resource: booking }
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
class ActionService < Services::Base
|
|
30
|
+
# Default action_name to nil - subclasses MUST set it
|
|
31
|
+
self._action_name = nil
|
|
32
|
+
|
|
33
|
+
def self.action_name(name)
|
|
34
|
+
self._action_name = name.to_sym
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Default schema - requires id parameter for actions
|
|
38
|
+
schema do
|
|
39
|
+
required(:id).filled
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
# Override respond to ensure resource key is present
|
|
45
|
+
def respond(data)
|
|
46
|
+
# Get base result (from custom respond_with block or default)
|
|
47
|
+
if self.class._respond_block
|
|
48
|
+
result = instance_exec(data, &self.class._respond_block)
|
|
49
|
+
else
|
|
50
|
+
result = success_result("Action completed successfully", data)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Ensure resource key exists (default to nil if not provided)
|
|
54
|
+
result[:resource] ||= nil
|
|
55
|
+
|
|
56
|
+
result
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|