Asyncapi::Client
Asyncapi::Client is a Rails engine that easily allows asynchronous communication with a Asyncapi::Server-based API server.
The common pattern that this gem saves you from implementing for the service callback pattern. soapatterns.org describes it well, and this image they provide summarizes it:
Implementing this typically requires a bunch of code that looks the same: HTTP posts, receive hooks - all while the data that's passed around different.
Usage
To communicate with the server (in the spirit of running things in the background, call the following from a worker):
Asyncapi::Client::Job.post(
"http://server.com/long/running/process",
# Options below are optional
body: {info: "i want to send"},
headers: { AUTHORIZATION: "Bearer XYZ" },
on_success: DoOnSuccess,
on_error: DoOnError,
on_queue: DoOnQueue,
on_queue_error: DoOnQueueError,
on_time_out: DoOnTimeOut,
time_out: 2.minutes # Defaults to nil (never time out)
)
Here is a breakdown of what these parameters mean:
-
http://server.com/long/running/process
: this is an endpoint that you will create using Asyncapi::Server -
body
: any data you want to send to the server that's needed for it to do its work -
headers
: if needed, headers for authorization, for example -
on_success
,on_error
,on_*
: service objects that will get executed whenever these events occur. More on this below. -
time_out
: when this job should be considered timed out. This is helpful when you want to know if the server ever got to working on the job. Jobs that should be timed out are marked astimed_out
approximately every minute.
on_*
Callbacks
class DoOnQueue < Asyncapi::Client::CallbackRunner
def call
Rails.logger.info "Job##{job.id} successfully queued with body: #{job.body}"
end
end
class DoOnSuccess < Asyncapi::Client::CallbackRunner
def call
Rails.logger.info "Job##{job.id} is done. The server's response: #{job.message}"
end
end
class DoOnError < Asyncapi::Client::CallbackRunner
def call
Rails.logger.info "Job##{job.id} failed. The server's response: #{job.message}"
end
end
In the callback service objects, you have access to the job
and its fields (directly):
callback_params
body
headers
message
response_code
Currently, this Engine only works with Sidekiq, typhoeus, and kaminari. Customizing these can be introduced to this gem as needed.
To run the application, you need to have the Sidekiq workers running as well.
There is a feed of all jobs that can be accessed via /asyncapi/client/v1/jobs.json
. You can pass per_page
and page
to paginate through the records. Pagination is done by api-pagination via kaminari.
Expiry
To make space in the database, old jobs must be deleted. By default, jobs older than 4 days will be deleted in both the Asyncapi Client and Asyncapi Server. Asyncapi Client is responsible for deleting the jobs it no longer needs a response from on the server.
Important: keep in mind that this setting may conflict with the successful_jobs_deletion_after
setting with jobs in success
state, which normally are deleted after they reach this state (see Successful jobs automatic deletion for more information).
By default, jobs 4 days old and older will be deleted. You can change this setting by putting this in an initializer:
Asyncapi::Client.expiry_threshold = 5.days
If you don't want the jobs to get deleted, set the threshold to nil
.
The cleaner job is run every day at "0 0 * * *". If you want to change the frequency that this runs, then:
Asyncapi::Client.clean_job_cron = "30 2 * * *"
Successful jobs automatic deletion
After a job completes successfully, we schedule another job to delete it after a brief period of time to avoid having a lot of records on the table. By default, a successful job is deleted from the table after 2 minutes, ignoring the expiry_threshold
configuration option. You can change this setting using the successful_jobs_deletion_after
configuration option in an initializer if you want to extend or shorten the wait time for deletion:
Asyncapi::Client.successful_jobs_deletion_after = 3.days
Important: if you want to ignore successful_jobs_deletion_after
setting in favor of expiry_threshold
, set successful_jobs_deletion_after
to a value greater than expiry_threshold
.
Installation
Note: if you're using the protected_attributes
, also see the "Optional" section below.
Required
Add the gem to the Gemfile:
gem "asyncapi-client"
From your Rails app:
rails g asyncapi:client:config
rake asyncapi_client:install:migrations
rake db:migrate
Make sure you have :host
in the default_url_options
set up for your server. Example config/initializers/default_url_options.rb
:
Rails.application.routes.default_url_options ||= {}
Rails.application.routes.default_url_options[:host] = ENV["HOST"]
Optional:
If you want to change the controller that the engine's controllers inherit from, from an initializer:
Asyncapi::Client.configure do |c|
c.parent_controller = Api::BaseController
end
If you use protected_attributes
, you need to whitelist attributes. In an initializer:
Asyncapi::Client::Job.attr_accessible(
:follow_up_at,
:time_out_at,
:on_queue,
:on_success,
:on_error,
:on_queue_error,
:headers,
:body,
:status,
:response_code,
)
If you use FactoryGirl, you can require the factories for easy testing and stubbing:
require "asyncapi/client/factories"
Security
asyncapi-client
generates a secret for the job using SecureRandom.uuid
, and sends this to the app using asyncapi-server
. The server app, using the latest compatible gem, will reply with the secret in the params. asyncapi-client
will make sure the job id matches with the given secret.
Development
rake db:migrate && rake db:migrate RAILS_ENV=test
rspec spec
License
Copyright (c) 2016 G5
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.