Class: Servus::Base Abstract

Inherits:
Object
  • Object
show all
Includes:
Events::Emitter, Guards, Support::Errors, Support::Lockdown, Support::Rescuer
Defined in:
lib/servus/base.rb

Overview

This class is abstract.

Subclass and implement initialize and call methods to create a service

Base class for all service objects in the Servus framework.

This class provides the foundational functionality for implementing the Service Object pattern, including automatic validation, logging, benchmarking, and error handling.

Examples:

Creating a basic service

class Services::ProcessPayment::Service < Servus::Base
  def initialize(user:, amount:, payment_method:)
    @user = user
    @amount = amount
    @payment_method = payment_method
  end

  def call
    return failure("Invalid amount") if @amount <= 0

    transaction = charge_payment
    success({ transaction_id: transaction.id })
  end

  private

  def charge_payment
    # Payment processing logic
  end
end

Using a service

result = Services::ProcessPayment::Service.call(
  user: current_user,
  amount: 100,
  payment_method: "credit_card"
)

if result.success?
  puts "Transaction ID: #{result.data[:transaction_id]}"
else
  puts "Error: #{result.error.message}"
end

See Also:

Constant Summary collapse

Logger =

Support class aliases

Servus::Support::Logger
Emitter =
Servus::Events::Emitter
Response =
Servus::Support::Response
Validator =
Servus::Support::Validator

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Guards

load_defaults

Methods included from Events::Emitter

#emit_events_for, emit_result_events!

Methods included from Support::Lockdown

included

Methods included from Support::Rescuer

included

Class Attribute Details

.arguments_schemaHash? (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the arguments schema defined via the schema DSL method.

Returns:

  • (Hash, nil)

    the arguments schema or nil if not defined



309
310
311
# File 'lib/servus/base.rb', line 309

def arguments_schema
  @arguments_schema
end

.failure_schemaHash? (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the failure schema defined via the schema DSL method.

Returns:

  • (Hash, nil)

    the failure schema or nil if not defined



321
322
323
# File 'lib/servus/base.rb', line 321

def failure_schema
  @failure_schema
end

.result_schemaHash? (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the result schema defined via the schema DSL method.

Returns:

  • (Hash, nil)

    the result schema or nil if not defined



315
316
317
# File 'lib/servus/base.rb', line 315

def result_schema
  @result_schema
end

Class Method Details

.after_call(result, instance) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Executes post-call hooks including result validation and event emission.

This method is automatically called after service execution completes and handles:

  • Validating the result data against RESULT_SCHEMA (if defined)

  • Emitting events declared with the emits DSL

Parameters:

Raises:



351
352
353
354
# File 'lib/servus/base.rb', line 351

def after_call(result, instance)
  Validator.validate_result!(self, result)
  Emitter.emit_result_events!(instance, result)
end

.before_call(args) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Executes pre-call hooks including logging and argument validation.

This method is automatically called before service execution and handles:

  • Logging the service call with arguments

  • Validating arguments against ARGUMENTS_SCHEMA (if defined)

Parameters:

  • args (Hash)

    keyword arguments being passed to the service

Raises:



334
335
336
337
# File 'lib/servus/base.rb', line 334

def before_call(args)
  Logger.log_call(self, args)
  Validator.validate_arguments!(self, args)
end

.benchmark(**_args) ⇒ Servus::Support::Response

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Measures service execution time and logs the result.

This method wraps the service execution to capture timing metrics. The duration is logged along with the success/failure status of the service.

Parameters:

  • _args (Hash)

    keyword arguments (unused, kept for method signature compatibility)

Yield Returns:

Returns:



366
367
368
369
370
371
372
373
374
# File 'lib/servus/base.rb', line 366

def benchmark(**_args)
  start_time = Time.now.utc
  result = yield
  duration = Time.now.utc - start_time

  Logger.log_result(self, result, duration)

  result
end

.call(**args) ⇒ Servus::Support::Response

Executes the service with automatic validation, logging, and benchmarking.

This is the primary entry point for executing services. It handles the complete service lifecycle including:

  • Input argument validation against schema

  • Service instantiation

  • Execution timing/benchmarking

  • Result validation against schema

  • Automatic logging of calls, results, and errors

rubocop:disable Metrics/MethodLength

Examples:

Successful execution

result = MyService.call(user_id: 123, amount: 50)
result.success? # => true
result.data # => { transaction_id: "abc123" }

Failed execution

result = MyService.call(user_id: 123, amount: -10)
result.success? # => false
result.error.message # => "Amount must be positive"

Parameters:

  • args (Hash)

    keyword arguments passed to the service’s initialize method

Returns:

Raises:

See Also:

  • #initialize
  • #call


233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/servus/base.rb', line 233

def call(**args)
  before_call(args)

  instance = new(**args)

  # Wrap execution in catch block to handle guard failures
  result = catch(:guard_failure) do
    benchmark(**args) { instance.send(:call) }
  end

  if result.is_a?(Servus::Support::Errors::GuardError)
    Logger.log_guard_failure(self, result)
    result = Response.new(false, nil, result)
  end

  after_call(result, instance)

  result
rescue Servus::Support::Errors::ValidationError => e
  Logger.log_validation_error(self, e)
  raise e
rescue StandardError => e
  Logger.log_exception(self, e)
  raise e
end

.schema(arguments: nil, result: nil, failure: nil) ⇒ void

This method returns an undefined value.

Defines schema validation rules for the service’s arguments, result, and/or failure data.

This method provides a clean DSL for specifying JSON schemas that will be used to validate service inputs and outputs. Schemas defined via this method take precedence over ARGUMENTS_SCHEMA, RESULT_SCHEMA, and FAILURE_SCHEMA constants. The next major version will deprecate those constants in favor of this DSL.

Examples:

Defining both arguments and result schemas

class ProcessPayment::Service < Servus::Base
  schema(
    arguments: {
      type: 'object',
      required: ['user_id', 'amount'],
      properties: {
        user_id: { type: 'integer' },
        amount: { type: 'number', minimum: 0.01 }
      }
    },
    result: {
      type: 'object',
      required: ['transaction_id'],
      properties: {
        transaction_id: { type: 'string' }
      }
    }
  )
end

Defining only arguments schema

class SendEmail::Service < Servus::Base
  schema arguments: { type: 'object', required: ['email', 'subject'] }
end

Parameters:

  • arguments (Hash, nil) (defaults to: nil)

    JSON schema for validating service arguments

  • result (Hash, nil) (defaults to: nil)

    JSON schema for validating service result data

  • failure (Hash, nil) (defaults to: nil)

    JSON schema for validating failure response data

See Also:



299
300
301
302
303
# File 'lib/servus/base.rb', line 299

def schema(arguments: nil, result: nil, failure: nil)
  @arguments_schema = arguments.with_indifferent_access if arguments
  @result_schema    = result.with_indifferent_access    if result
  @failure_schema   = failure.with_indifferent_access   if failure
end

Instance Method Details

#call!(service_class, **params) ⇒ Servus::Support::DataObject, Object

Invokes another service from within this service’s #call and returns its data on success. On failure, halts the outer service with the sub-service’s failure Response — the outer service’s caller receives that Response unchanged (same error object, message, code, http_status).

Sugar over:

result = SubService.call(**params)
return result unless result.success?
data = result.data

Only call from within a service’s ‘#call` (or helpers reachable from it); the throw is caught by call.

For invoking a service from outside a service context (controllers, rake tasks, jobs, consoles), see Helpers::ControllerHelpers#run_service!.

Examples:

Composing services

class SendDigitalCash::Service < Servus::Base
  def call
    data1 = call!(Accounts::Lookup::Service, id: )
    data2 = call!(Ledger::RecordTransfer::Service, account:, amount:)
    success(ref: data2.ref)
  end
end

Parameters:

  • service_class (Class<Servus::Base>)

    the sub-service to invoke

  • params (Hash)

    keyword arguments to pass to the sub-service

Returns:

See Also:



194
195
196
197
198
199
# File 'lib/servus/base.rb', line 194

def call!(service_class, **params)
  result = service_class.call(**params)
  return result.data if result.success?

  throw(:guard_failure, result)
end

#error!(message = nil, type: Servus::Support::Errors::ServiceError) ⇒ void

Note:

Prefer #failure for expected error conditions. Use this for exceptional cases.

This method returns an undefined value.

Logs an error and raises an exception, halting service execution.

Use this method when you need to immediately halt execution with an exception rather than returning a failure response. The error is automatically logged before the exception is raised.

Examples:

Raising an error with custom message

def call
  error!("Critical system failure") if system_down?
end

Raising with specific error type

def call
  error!("Unauthorized access", type: Servus::Support::Errors::UnauthorizedError)
end

Parameters:

  • message (String, nil) (defaults to: nil)

    error message for the exception (uses default if nil)

  • type (Class) (defaults to: Servus::Support::Errors::ServiceError)

    error class to raise (must inherit from ServiceError)

Raises:

See Also:



151
152
153
154
155
156
157
158
159
# File 'lib/servus/base.rb', line 151

def error!(message = nil, type: Servus::Support::Errors::ServiceError)
  error = type.new(message)
  Logger.log_exception(self.class, error)

  # Emit error! events before raising
  emit_events_for(:error!, Response.new(false, nil, error))

  raise type, message
end

#failure(message = nil, data: nil, type: Servus::Support::Errors::ServiceError) ⇒ Servus::Support::Response

Creates a failure response with an error.

Use this method to return failure results from your service’s call method. The failure is logged automatically and returns a response containing the error.

Examples:

Using default error type with custom message

def call
  return failure("User not found") unless user_exists?
  # ...
end

Using custom error type

def call
  return failure("Invalid payment", type: Servus::Support::Errors::BadRequestError)
  # ...
end

Using error type’s default message

def call
  return failure(type: Servus::Support::Errors::NotFoundError)
  # Uses "Not found" as the message
end

Attaching structured data to a failure

def call
  return failure("Approval required", data: { requires_human_approval: true })
end

Parameters:

  • message (String, nil) (defaults to: nil)

    custom error message (uses error type’s default if nil)

  • data (Object, nil) (defaults to: nil)

    optional structured data to attach to the failure response. When a failure schema is defined, this data will be validated against it.

  • type (Class) (defaults to: Servus::Support::Errors::ServiceError)

    error class to instantiate (must inherit from ServiceError)

Returns:

See Also:



123
124
125
126
# File 'lib/servus/base.rb', line 123

def failure(message = nil, data: nil, type: Servus::Support::Errors::ServiceError)
  error = type.new(message)
  Response.new(false, data, error)
end

#success(data) ⇒ Servus::Support::Response

Creates a successful response with the provided data.

Use this method to return successful results from your service’s call method. The data will be validated against the RESULT_SCHEMA if one is defined.

Examples:

Returning simple data

def call
  success({ user_id: 123, status: "active" })
end

Returning nil for operations without data

def call
  perform_action
  success(nil)
end

Parameters:

  • data (Object)

    the data to return in the response (typically a Hash)

Returns:

See Also:



82
83
84
# File 'lib/servus/base.rb', line 82

def success(data)
  Response.new(true, data, nil)
end