In this short note, I want to share with you a way to create services of any complexity in Ruby projects. This will standardize all services and provide a unified approach to their development.
Ruby Gem
Everything to know about Servactory gem.
Repository in GitHub: github.com/servactory/servactory
Documentation: servactory.com
The library was developed on the basis of several projects and includes everything you need to implement services of any complexity.
Usage
There is an example of a service implementation for sending events. This implementation includes a service for working with the API client and a service for preparing data.
Service for working with the API client, which makes a request to an external service:
class AmplitudeService::Base < ApplicationService::Base
perform :api_request!
private
def perform_api_request!
outputs.response = api_request
rescue AmplitudeApi::Errors::Failed => e
fail!(message: e.message)
end
def api_request
raise "Need to specify the API request"
end
def api_model
raise "Need to specify the API model"
end
def api_client
@api_client ||= AmplitudeApi::Client.new
end
end
class AmplitudeService::TrackEvent < AmplitudeService::Base
input :user_identifier, type: String
input :device_identifier, type: String, required: false, default: nil
input :type, type: Symbol
input :event_properties, type: Hash, required: false, default: {}
input :user_properties, type: Hash, required: false, default: {}
# some other inputs
output :response, type: AmplitudeApi::Responses::Event
private
def api_request
api_client.events.track(api_model)
end
def api_model
AmplitudeApi::Requests::Event.new(
user_identifier: inputs.user_identifier,
device_identifier: inputs.device_identifier,
type: inputs.type,
event_properties: inputs.event_properties,
user_properties: inputs.user_properties,
# some other data
time:
)
end
def time
DateTime.now.strftime("%Q").to_i
end
end
And directly the service itself, which prepares the data and passes it on to fulfill the request:
class UserService::Amplitude::Activated < ApplicationService::Base
input :user, type: User
make :api_request!
private
def api_request!
service_result = AmplitudeService::TrackEvent.call(
user_identifier: inputs.user.id,
type: :USER_ACTIVATED,
event_properties: {
# some metadata
},
user_properties: {
# some metadata
}
# some other data
)
fail!(error: service_result.error) if service_result.failure?
end
end
The service is called like this:
UserService::Amplitude::Activated.call!(user: User.first)
The Servactory has many utilities that will allow you to build services with much more complex logic, but keeping them simple and expressive. Make sure of it by reading the documentation.
Testing
As far as testing is concerned, this is implemented by regular Ruby class testing.
You can do regression testing on input
, checking for expected falls. You can check expected output
values in Result
.
Same approach to the development of services makes their testing also come to a single form!
Top comments (0)