Service classes for external integrations
Last Updated: March 19, 2025
This page provides an overview of the process for making requests to external service through an API client class called a service.
Service classes
Service classes are subclasses of Common::Client::Base
, which performs the actual request to the external service through a Faraday connection, error handling, outage monitoring, and logging.
Service classes configure and initiate the requests to specific endpoints. They also translate the Faraday::Response
into an internal Response
object. Additional error handling and logging can be included in service classes.
Sample implementation
Let's say you are integrating an external service called "HealthCheck."
First, create a new service class:
# lib/my_integration/health_check/profile/service.rb
require 'common/client/base'
module MyIntegration
module HealthCheck
module Profile
class Service < Common::Client::Base
include Common::Client::Monitoring
configuration HealthCheck::Profile::Configuration
def get_person_details
with_monitoring do
raw_response = perform(:get, 'person_details')
HealthCheck::Profile::PersonResponse.new(raw_response.status, raw_response)
end
rescue StandardError => e
handle_error(e)
end
end
end
end
end
If HealthCheck offers multiple services, use namespaces (e.g., Profile
) to differentiate the various web services of the external service.
Configuration classes
Configuration classes create a Faraday::Connection
used by the service and contain additional information necessary for monitoring and logging. For more information on Faraday, visit their documentation site.
The configuration may include request headers, request options (e.g., read timeouts), base URL, authentication, and reference additional middleware used by the connection. Mocking logic is defined in the configuration. Typically the some of the configuration values come from Settings (stored in config/setttings.yml
).
Configuration classes you create when implementing a new external service inherit from Common::Client::Configuration::REST
or Common::Client::Configuration::SOAP
depending on the protocol of the external service.
Sample implementation
The HealthCheck configuration should contain at least base_path
, connection
, and service_name
.
# lib/my_integration/health_check/configuration.rb
require 'common/client/configuration/rest'
module MyIntegration
module HealthCheck
class Configuration < Common::Client::Configuration::REST
self.read_timeout = Settings.my_integration_health_check.profile.timeout || 30
def base_path
"#{Settings.my_integration_health_check.url}/profile/rest/v1"
end
def connection
@conn ||= Faraday.new(base_path, headers: base_request_headers, request: request_options, ssl: ssl_options) do |faraday|
faraday.use :breakers
...
faraday.adapter Faraday.default_adapter
end
end
def service_name
'HealthCheck'
end
private
def mock_enabled?
Settings.my_integration_health_check.mock_profile || false
end
end
end
end
When services within the external service require different middleware or a different configuration option, such as an open timeout, create a subclass.
# lib/my_integration/health_check/profile/configuration.rb
module MyIntegration
module HealthCheck
module Profile
class Configuration < HealthCheck::Configuration
def connection
@conn ||= Faraday.new(base_path, headers: base_request_headers, request: profile_request_options, ssl: ssl_options) do |faraday|
faraday.use :breakers
faraday.use :profile_specific_middleware
...
faraday.adapter Faraday.default_adapter
end
end
private
def profile_request_options
...
end
end
end
end
end
Response classes
The response class converts the raw response from the external service into a native Ruby object.
The Common::Base
class encapsulates our base logic for defining attributes and serialization. We use Virtus to define attributes on classes and modules.
Virtus is still in use. It is marked as discontinued because its gem is no longer maintained.
Base response classes you create when implementing a new external service inherit from Common::Base
. This new base response class houses logic associated with customizing initialization and response statuses and checks.
If there are sub-services for your new external service, create a new child response class that inherits from your new base response class. This child class is where you convert the response to Ruby and leverage any new model classes that you create.
Sample implementation
Using the HealthCheck service, first create a new base response class:
# lib/my_integration/health_check/response.rb
require 'common/client/concerns/service_status'
require 'common/models/base'
module MyIntegration
module HealthCheck
class Response < Common::Base
include Common::Client::ServiceStatus
attribute :status, Integer
def initialize(status, attributes = nil)
super(attributes) if attributes
self.status = status
end
def ok?
status == 200
end
def response_status
...
end
end
end
end
Next, create a new child response class unique to the profile sub-service to convert the raw response to Ruby, set the status, etc.:
# lib/my_integration/health_check/profile/person_response.rb
require 'health_check/response'
module MyIntegration
module HealthCheck
module Profile
class PersonResponse < HealthCheck::Response
attribute :person, HealthCheck::Models::Person
attr_reader :bio
def initialize(status, response = nil)
@bio = response&.body&.dig('bio')
super(status, person: build_person)
end
def build_person
Vet360::Models::Person.new(
...
)
end
end
end
end
end
Models classes
The Common::Base
class that encapsulates our base logic for defining attributes and serialization. We use Virtus to define attributes on classes and modules.
Virtus is still in use. It is marked as discontinued because its gem is no longer maintained.
Base models classes created when implementing your external service require Common::Base
. This new base model class includes all of the validation, serialization, and attribute libraries used for all of your models.
These models are vehicles to convert responses received from an external service into native Ruby objects so that we can set attributes, conduct validations, use dot notation, build out custom methods for the attributes, etc.
After you've created the new base model class, create any new children model classes that inherit from it.
Sample implementation
Using the HealthCheck service, create a new base model class:
# lib/my_integration/health_check/models/base.rb
require 'common/models/base'
module MyIntegration
module HealthCheck
module Models
class Base
include ActiveModel::Validations
include ActiveModel::Serialization
include Virtus.model(nullify_blank: true)
end
end
end
end
Next, create any new child model classes:
module MyIntegration
module HealthCheck
module Models
class Person < Base
attribute :name, String
attribute :address, String
...
validates :name, presence: true
...
end
end
end
end
Updating the config settings
Because this is a brand new external service, update our config/settings.yml
file with the service's root URL. Include keys and values for any usage of Settings
in your configuration classes.
Sample implementation
# Settings for HealthCheck
my_integration_health_check:
url: https://health_check/api/v1
profile:
timeout: 30
mock_profile: false
For more details, go to Settings and Parameter Store.
Help and feedback
Get help from the Platform Support Team in Slack.
Submit a feature idea to the Platform.