Class: Servus::Base Abstract

Inherits:
Object
  • Object
show all
Extended by:
Schema::Declaration
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

Constants included from Events::Emitter

Events::Emitter::EMISSION_TRIGGERS

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Schema::Declaration

declare_schemas, schema, schema_types

Methods included from Guards

load_defaults

Methods included from Events::Emitter

#build_event_payload, #emission_condition_met?, #emit_events_for, emit_result_events!, #evaluate_emission_condition, #require_event_schema!, #validate_event_payload!

Methods included from Support::Lockdown

included

Methods included from Support::Rescuer

included

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:



305
306
307
308
# File 'lib/servus/base.rb', line 305

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

.arguments_schemaHash?

Returns the compiled arguments schema.

Returns:

  • (Hash, nil)

    the compiled arguments schema



110
# File 'lib/servus/base.rb', line 110

declare_schemas :arguments, :result, :failure

.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:



288
289
290
291
# File 'lib/servus/base.rb', line 288

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:



320
321
322
323
324
325
326
327
328
# File 'lib/servus/base.rb', line 320

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


250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/servus/base.rb', line 250

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

.failure_schemaHash?

Returns the compiled failure schema.

Returns:

  • (Hash, nil)

    the compiled failure schema



110
# File 'lib/servus/base.rb', line 110

declare_schemas :arguments, :result, :failure

.result_schemaHash?

Returns the compiled result schema.

Returns:

  • (Hash, nil)

    the compiled result schema



110
# File 'lib/servus/base.rb', line 110

declare_schemas :arguments, :result, :failure

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

This method returns an undefined value.

Declares the JSON schemas used to validate this service.

Arguments are validated before call runs, so the body can trust the shape of its inputs. Result data is validated after it returns, so a service that stops honouring its own contract fails loudly rather than shipping the wrong shape to its callers.

Schemas may reference shared fragments registered with Schema.register; refs are resolved on first read.

Omitting a keyword leaves any schema declared earlier — or by a superclass — in place. Passing one explicitly as nil raises.

Examples:

Declaring 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

Referencing a shared fragment

schema arguments: {
  type: 'object',
  properties: { amount: { '$ref' => '#/core/$defs/amount' } }
}

Parameters:

  • arguments (Hash) (defaults to: nil)

    JSON schema for the service's arguments

  • result (Hash) (defaults to: nil)

    JSON schema for successful result data

  • failure (Hash) (defaults to: nil)

    JSON schema for failure response data

Raises:

  • (ArgumentError)

    on an unknown keyword or an explicit nil

See Also:



110
# File 'lib/servus/base.rb', line 110

declare_schemas :arguments, :result, :failure

Instance Method Details

#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:



208
209
210
211
212
213
214
215
216
# File 'lib/servus/base.rb', line 208

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:



180
181
182
183
# File 'lib/servus/base.rb', line 180

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:



139
140
141
# File 'lib/servus/base.rb', line 139

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