Skip to main content

Introducing Core Libraries in Ruby

· 7 min read

APIMatic has introduced core libraries to provide a stable runtime that powers all functionality of our SDKs. In this release, we have revamped our Ruby SDKs to improve the code quality and provide better test coverage.

Refer to Introducing core libraries in APIMatic generated SDKs changelog to understand the importance of core libraries in our SDKs.

Details

The SDKs generated by APIMatic contain utility code that helps the SDK make API calls. This utility code may be involved in but is not limited to:

  • Creating HTTP requests from user data
  • Deserializing API responses to class instances
  • Handling various API request states and errors
  • API authentication
  • Validating user and server data
  • Functionality such as File streaming and JSON pass-through

This utility code was growing with the introduction of new features and functionality, which not only increased the SDK size but also made the code untestable.

What has Changed?

The Ruby SDK has been refactored to make it as lightweight as possible so that the user only has access to the relevant components of the SDK. To achieve this, we have created the following three packages as dependencies.

Core Library

APIMatic will now support the entire Ruby SDK through the core library package, which will be the heart of the SDK's functionality. So, this core library package is responsible for every function that the SDK was responsible for. This library contains completely refactored utilities with easy-to-maintain code following best practices for Ruby. Moreover, each component of the core library is thoroughly tested to ensure maximum performance and stability.

Basic structure of the core library is as follows:

Basic structure of the core library in Ruby SDK

The core library has been designed to contain each component coherently, as can be seen in this structure. The two main components are the core library itself and its unit tests.

Responsibilities of the Core library

Core library is mainly responsible for:

  • Preprocessing of the request. This includes:
    • Setting and cleaning all types of parameters
    • Validating parameters (optional and required)
    • Setting Url for the API call
    • Setting auth managers
    • XML preprocessing if required
  • Creating the request
    • Setting up the request with the right parameters
  • Execution of the request
  • Response Handling, which includes:
    • Deserializing response into required forms
    • Validating the response
    • Status code assertions
    • Converting the response into API response type
    • Exception handling in case of failed request
  • Unit testing. Following come under unit testing:
    • Testing all core library components
    • Achieving maximum possible code coverage and branch coverage
    • Testing utilities with meaningful inputs
    • Covering corner cases

Core Interfaces

Along with core library, we have also introduced core interfaces as one of the dependencies of the Ruby SDK. The core interface acts as an abstract layer for the core library. The important components of this dependency are as follows:

Components of core interfaces in Ruby SDK

The primary goal for introducing this dependency is to ensure that any changes required in the future can be smoothly carried out without any breaking changes. Therefore, it will ensure the scalability of the functioning of the core libraries as well as the SDK in the future.

Faraday Client Adapter

The Ruby SDK relies on the faraday library for making an HTTP call. Previously, the SDK contained the client implementation for sending an HTTP request through this module. With the introduction of the core library, the faraday client implementation has been separated into another package which will be added as a dependency in the SDK. This client is a simple and clean implementation of the same functionalities that the SDK previously implemented. Moreover, we now have support for unit tests in the client as well to ensure the reliability of the component. The structure of this package is as follows:

Structure of Faraday client adaptor for Ruby SDK

And the supported methods are:

Faraday client adaptor methods in Ruby SDK

It is worth mentioning here that this will help APIMatic to support different requesting libraries present in Ruby without any breaking changes to ensure reliable scalability.

The SDK

The SDK is now compact and easy to understand. All utilities have been moved to the core library and all responsibilities for the request creation and response handling also lie within the core library. This has improved the readability of the SDK, hence improving the overall user experience.

The endpoint

Let's compare an endpoint that does a POST request before and after introducing core libraries in Ruby SDK.

The code before core libraries:

    def send_unix_date_time(datetime)
# Validate required parameters.
validate_parameters(
'datetime' => datetime
)

# Prepare query url.
_query_builder = config.get_base_uri
_query_builder << '/body/unixdatetime'
_query_url = APIHelper.clean_url _query_builder

# Prepare headers.
_headers = {
'accept' => 'application/json',
'content-type' => 'text/plain; charset=utf-8'
}

# Prepare and execute HttpRequest.
_request = config.http_client.post(
_query_url,
headers: _headers,
parameters: datetime.to_time.utc.to_i.to_s
)
_response = execute_request(_request)

# Validate response against endpoint and global error codes.
return nil if _response.status_code == 404

validate_response(_response)

# Return appropriate response type.
decoded = APIHelper.json_deserialize(_response.raw_body)
ServerResponse.from_hash(decoded)
end

And the code after core libraries:

    def send_unix_date_time(datetime)
new_api_call_builder
.request(new_request_builder(HttpMethodEnum::POST, '/body/unixdatetime', Server::DEFAULT)
.body_param(new_parameter(datetime)
.is_required(true))
.header_param(new_parameter('text/plain; charset=utf-8', key: 'content-type'))
.header_param(new_parameter('application/json', key: 'accept'))
.body_serializer(proc do |param|
param.to_time.utc.to_i.to_s unless param.nil?
end))
.response(new_response_handler
.is_nullify404(true)
.deserializer(APIHelper.method(:custom_type_deserializer))
.deserialize_into(ServerResponse.method(:from_hash)))
.execute
end

This code asks the Request builder to set the server, URL path, header, and body params (or any other type of parameters) along with whether they are required or not. Then it asks the response handler to set the parameters of the response, the deserializer, and the custom type to deserialize the response into. Finally, it calls the execute method from the library to make the request. This completes your API call.

From this example, it is quite clear that:

  • The code for an endpoint has drastically decreased and all the repeated pre-processing has been eliminated.
  • Method changing has been introduced to make the endpoint code concise and easy to read.

The Unchanged Parts

Regardless of this change, the Ruby SDKs will still contain all parts that are set by the user in the API specification. These parts include:

  • User set configurations
    • The Server
    • Environments
  • Custom Types
    • Models
    • Enums
    • Exceptions
note

There are no functional or breaking changes in the Ruby SDK. The imports and code samples will also remain the same and will function without any breakages.

The benefits

Following are the benefits observed by the introduction of core libraries in Ruby SDK.

  • Significant increase in the stability of the SDKs as the implementation of the SDK code has been improved & unit tests are made part of the core library packages.
  • Add support for new functionality in the SDKs without requiring the API provider to republish the SDKs.
  • Easy to solicit feedback or get bug reports from users by making the source code available on GitHub.
  • Reduce the cost of introducing new features and improving the SDKs iteratively.
  • Create the ability to introduce alternative implementations for core functionality like HTTP clients, data validation, configuration loading,
  • Allow us to explore SDK customizations.