Object-oriented Third-party API layer

At HireArt, we try to make our customer experience better by building out integration with our customer's Applicant Tracking Systems (ATS). Building out multiple API integrations can be time-consuming and complex as more APIs are added. We decided to take a structured approach to building out our many third-party ATS integrations. Here's how we did it:

Structured Approach

We have two use cases for ATSs but we will be focusing on only one of them in this post. We source and evaluate candiates for our clients who are employers. The candidates we present to the employers should be easily accessible from the employer's ATS system. Given the wide range of ATS systems out there and their different implementations, we knew we needed a way to abstract away some of the implementation details.

We were heavily influenced by the incredible work Shopify did on ActiveMerchant. We wanted to do something similar and leverage an abstration layer that unifies third-party api calls with a transactional model.

The system we designed has 4 distinct classes:

  1. Transaction (Model) The parent class of all ats transactions. Common methods for creating a transaction live here.
  2. [ATS]Transction (Model) A transaction class that inherits from the Transaction model that's tailored to specific ATS.
  3. ATS This class makes all the API calls for a specific ATS implementation.
  4. [ATS]Authorization (Model) The implementation of an authorization for a particular ATS.
  5. JobReqMapping This model stores mapping configurations between the job requisitions on our system and the employer's ATS.

Transaction Model

We begin by creating a transaction model that collects the candidates we wish to send off to the ATS. Once we know which ats we're using, we simply type-cast it into a specific ATS transaction and get access to all of its methods.

Here's a high level overview of the Transaction Class

module Ats
  class Transaction < ActiveRecord::Base
    def has_auth?
    end

    def make_auth
    end

    def get_auth
    end

    def has_job_map?
    end

    def make_job_map
    end

    def get_job_map
    end

    def push_candidates_to_ats
    end
  end
end

[ATS]

Each ATS has a class with methods that access specific endpoints on that particular ATS. The ATS class is as dumb as possible. It only does 2 things, create a session with the ATS and expose methods to push candidates into the ats. Here we're using HTTParty for our web requests.

We found that for each method that makes a web request we're duplicating code for setting up the request, checking to see if the request was successful and storing any error response on the ATS isntance. So we built a general endpoint method for our Taleo Ats class. Using this method we can make any number of API calls just by invoking end_point with the proper resource route.

def end_point(url, data=nil)
  full_url = "#{base_url}#{url}"
  if auth_token
    if data
      r = HTTParty.post full_url, cookies: { 'authToken' => auth_token }, body: data, headers: { 'Content-Type' => 'application/json' }
    else
      r = HTTParty.get full_url, cookies: { 'authToken' => auth_token }
    end
  else
    r = HTTParty.post full_url
  end
  success?(r)
  r
end

def success?(response)
  self.success = ( JSON.parse(response.body)["status"]["success"] == true ) rescue false
  self.error_code = JSON.parse(response.body)["status"]["detail"]['errormessage']
end

A typical API call looks like this:

def candidate_insert(candidate)
  end_point "/object/candidate", candidate
end

After calling candidate_insert, we can then ask ats.success? and ats.error to know if the request went through properly.

[ATS]Authorization

Authorizations for each ATS varies. The ATS Transaction model tracks which authorization to look up and then authorizes the user for an ATS instance. Authorizations do persist so future sessions can look up an previous authorizations for that user.

The most interesting part of the authorization model is the get_ats method, it creates an instance of that ats, performs the login (if the session has timed out) and returns an ats instance.

module Ats
  class Authorization < ActiveRecord::Base
    def get_ats
      # creates an ats instance for this authorization and logs into a session on that ats
    end
  end
end

Sequence of Calls

To put it all together, we first create an ats_trasaction. Once we know which ats we're working with, we type-cast the transaction to a subclass of ats_transaction (ex. TaleoTransaction). This initializes a Taleo instance and if it has the credentials, creates an active session on that Taleo. Then any number of methods can then be invoked on the TaleoTransaction to interact with the ATS.