ServiceCore
ServiceCore provides a standardized way to define and use service objects in Ruby and Rails applications. It includes support for specifying fields, validations, responses, and error logging. This approach is inspired by the DRY (Don't Repeat Yourself) principle and Rails' convention over configuration philosophy.
Installation
Install the gem and add to the application's Gemfile by executing:
bundle add service_core
Or in your Gemfile:
gem "service_core"
If the bundler is not being used to manage dependencies, install the gem by executing:
gem install service_core
Service Response Structure
The idea is to define a convention that the response from a service can have only four keys:
The data type of any of the above keys is not enforced, giving the developer flexibility to return based on the use case, but should follow this response structure.
Usage
Defining a Service
To define a new service, include the ServiceCore
module in your service class and define your fields and the perform
method.
class MyService
include ServiceCore
field :first_name, :string
field :last_name, :string
field :active, :boolean, default: true
def perform
success_response(message: "Hello, World", data: name)
end
def name
"#{first_name} #{last_name}"
end
end
Using a Service
Instantiate and call the service to execute it. The call method will validate the input, perform the operation, and return the output.
service = MyService.new(first_name: "John", last_name: "Doe")
result = service.call
puts result
puts service.output
The call
method can be invoked on the service class and it too will return the object of the service.
obj = MyService.call(first_name: "John", last_name: "Doe")
puts obj.output
field
method
The field
method can define primitive types and objects, like hash/array or any object. For objects, there is no need to declare the datatype.
class MyService
include ServiceCore
field :first_name, :string
field :last_name, :string
field :payload
def perform
success_response(message: "Hello, World", data: name)
end
def name
"#{first_name} #{last_name}"
end
end
set_output
method
The set_output
method provides a way to set output of a specific key. It is the method used by the response_setters to set specific output value
class MyService
include ServiceCore
field :first_name, :string
field :last_name, :string
field :payload
def perform
set_output :message, "Hello, World"
set_output :data, name
end
def name
"#{first_name} #{last_name}"
end
end
obj = MyService.call(first_name: "John", last_name: "Doe")
puts obj.output
NOTE: If :status
is not explicitly set in the perform method, the success
status is returned if errors
are blank else the error
status is returned.
Response Setters
success_response
Use the success_response
method to return the success
status, data
and message
class MyService
include ServiceCore
field :first_name, :string
field :last_name, :string
field :active, :boolean, default: true
def perform
success_response(message: "Hello, World", data: name)
end
def name
"#{first_name} #{last_name}"
end
end
service = MyService.new(first_name: "John", last_name: "Doe")
result = service.call
puts result
success_response
accepts following arguments:
error_response
Use the error_response
method to return the error
status, errors
and message
class MyService
include ServiceCore
field :first_name, :string
field :last_name, :string
field :active, :boolean, default: true
def perform
error_response(message: "validation failure", errors: "last_name can't be blank")
end
def name
"#{first_name} #{last_name}"
end
end
service = MyService.new(first_name: "John")
result = service.call
puts result
error_response
accepts following arguments:
formatted_response
Use the formatted_response
method to return any status other than success
or error
.
class MyService
include ServiceCore
field :first_name, :string
field :last_name, :string
field :active, :boolean, default: true
def perform
formatted_response(status: 'processed', message: "Hello, World", data: name)
end
def name
"#{first_name} #{last_name}"
end
end
service = MyService.new(first_name: "John", last_name: "Doe")
result = service.call
puts result
formatted_response
accepts following arguments:
Validations
Define validation on the service and those will be invoked before service logic is invoked.
class MyService
include ServiceCore
field :name, :string
validates :name, presence: true
def perform
success_response(message: "Hello, World", data: name)
end
end
service = MyService.new(name: "")
result = service.call
puts result
Step Validation
Perform validation at each step of service logic. This is helpful when the result of the previous step decides the next logic.
class MyService
include ServiceCore
field :first_name, :string
field :last_name, :string
field :user
validates :first_name, presence: true
validates :user, presence: true
def perform
if last_name.blank?
add_error_and_validate(:last_name, "can't be nil")
return error_response(message: "validation failure", errors: errors)
end
success_response(data: { user: { id: 1 } })
end
end
obj = MyService.call(first_name: 'abc')
obj.output
Logging Errors
Log errors using the log_error
method.
class MyService
include ServiceCore
field :name, :string
def perform
begin
raise StandardError, "Something went wrong"
rescue StandardError => e
log_error(e)
error_response(message: "Failed", errors: { base: [e.message] })
end
end
end
service = MyService.new(name: "World")
result = service.call
puts result
Configuring the Logger
Configure the logger for the ServiceCore module.
ServiceCore.configure do |config|
config.logger = Logger.new(STDOUT)
end
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/sehgalmayank001/service-core. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the ServiceCore project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.