以前、KARTEのTrackAPIをRailsを使って繋げました。その備忘録を書いていきます。必要部分に絞っているため、セットアップや、クライアントサイドのコードは省いています。
備忘録ということもあり、当時と違い、KARTEは v1 → v2 へと移行していますが、エンドポイントが変わっていること以外は特筆することがなかったと記憶しています。
話す内容
1:APIの仕様確認
2:controllerの処理
3:責務を分けて実装する
4:テスト
1:APIの確認
今回はKARTEのAPIを使います。
https://developers.karte.io/docs
KARTEでは、イベントアクションを用いて、アプリケーション側からKARTEに向けてデータを送る事ができます。
イベント とは、ユーザーの行動を表すためのデータで、次のような特徴があります。
・イベントの種類を表すイベント名と、スキーマレスな値を持つ
・あらかじめ定義されているイベント以外にも独自のイベントを扱える
・ユニークなユーザーIDと紐づく
送信されたイベントはフィールドごとに最大値や合計値などが解析され、ユーザーに紐づいた統計値として扱うことができます。
イベントとは https://developers.karte.io/docs/guide-event
KARTEでは既に定義済みのイベントが用意されており、今回はその中でもユーザー情報(identify)を取り扱うこととします。
定義済みイベント https://developers.karte.io/docs/predefined-event
Event Name | Display Name |
---|---|
identify | ユーザー情報 |
サンプルコードではNode.jsによる実装が挙げられているため、それを参考にRailsでの処理を考えていきます。
サンプルコード:https://developers.karte.io/docs/auth-api-request#track-api-%E3%81%AE%E5%A0%B4%E5%90%88
API仕様:https://developers.karte.io/reference#v1-api-overview
2:controllerで書くこと
今回、userではなくownerの情報を送る想定として作っていきます。
controllerでは、ownerの情報を持たせています。中身はeventの値に入るものが入っている想定です。
今回であれば
events | data |
---|---|
owner_id | owner.uuid |
owner_gender | owner.gender |
first_name | owner.first_name |
last_name | owner.last_name |
owner.email |
みたいな感じになっていればOKです。
app/controllers/owners_controller.rb
def create
#色々な処理...
KARTE::Server.send_owner_info(owner)
...
end
KARTEモジュールのServerクラスである、send_owner_infoメソッドに対して、クライアントから渡ってきたownerデータを渡しています。
今回、新規のオーナーがXXXを登録した際に、データをKARTE側に送るためcreateアクション内に記載しております。
3:責務を分けて実装する
apiをいじるにあたって、 api_client.rb, api_server.rb と責務を分離して作成していきます。
・app/services/karte/api_client.rb
・app/services/karte/api_server.rb
中身を見ていきましょう。
app/services/karte/api_client.rb
module KARTE
class Client
# envに環境変数を設定しとく
attr_reader :karte_host, :karte_client_id, :karte_api_key, :karte_access_token
def initialize(karte_host, karte_client_id, karte_api_key, karte_access_token)
@karte_host = karte_host
@karte_client_id = karte_client_id
@karte_api_key = karte_api_key
@karte_access_token = karte_access_token
#= 環境変数が無い時点で、引数エラーを投げる。
raise ArgumentError if [karte_host, karte_client_id, karte_api_key, karte_access_token].any?(&:blank?)
end
#= api_server.rbからowner_idとeventsデータを貰います。
def send_track_event(owner_id, events)
args = {
client_id: @karte_client_id,
api_key: @karte_api_key,
keys: {
owner_id: owner_id
},
events: events
}
# v2だとエンドポイントが変わっているので /v2/track/event/write にする必要あり
api_call("/v1/event/track", args)
end
private
def api_call(path, args)
uri = URI.parse("https://#{karte_host}#{path}")
req = Net::HTTP::Post.new(uri.request_uri)
req["Content-Type"] = 'application/json; charset=utf-8'
req["Authorization"] = 'Bearer ' + @karte_access_token
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
res = http.request(req, args.to_json)
res.value
body = res.body
{ status: "succeeded", body: body }
#= API連携失敗した際、サービスを止めないよう、rescueする
rescue StandardError => e
Bugsnag.notify(e) if Rails.env.production?
Rails.logger.error(e)
{ status: "failed", body: nil }
end
end
end
send_track_eventは、client_id, api_key, keys, events の4つを引数として取っています。
api_callの中身は、API連携が失敗したとき用のrescueを書いて、postが失敗した時でもアプリケーションが止まらないようにしています。
app/services/karte/api_server.rb
module KARTE
class Server
def self.send_owner_info(owner)
api_client = Client.new(ENV["KARTE_HOST"], ENV["KARTE_CLIENT_ID"], ENV["KARTE_API_KEY"], ENV["KARTE_ACCESS_TOKEN"])
owner_uuid = owner.owner_uuid
events = [
{
event_name: "identify",
values: {
owner_id: owner_uuid,
owner_gender: owner.gender,
first_name: owner.first_name,
last_name: owner.last_name,
email: owner.email
}
}
]
api_client.send_track_event(owner_uuid, events)
#= データの受け渡し時にエラーが起こった際、rescueする
rescue StandardError => e
Bugsnag.notify(e) if Rails.env.production?
Rails.logger.error(e)
end
end
end
event_name ですが
https://developers.karte.io/docs/predefined-event
を参考に、ownerのユーザー情報を登録するため、Event Name が identify のモノを利用しました。
問題なければ、send_track_event に owner_uuid, events の中身を投げます。
4:テスト
Stripeのように仮でAPIを送ったときに、XXXを返すようなサーバーが立ってないため、スタブを利用して特定のURLにpostした際にstatus: trueになるかどうかの検証をしています。
spec/services/karte/api_client.rb
require 'rails_helper'
require 'webmock/rspec'
describe KARTE::ApiClient do
let(:karte_api_client) { KARTE::ApiClient.new("karte_host", "karte_client_id", "karte_api_key", "karte_access_token") }
let(:karte_host) { "api.karte.io" }
# v2だと /v2/track/event/write になっているので注意。
let(:path) { "/v1/event/track" }
describe '#send_track_event' do
let(:owner) { create(:owner) }
let(:owner_uuid) { owner.owner_uuid }
let(:events) { {} }
subject { karte_api_client.send_track_event(owner_uuid, events)[:status] }
context 'when response status is 2xx' do
before { stub_request(:post, "https://#{karte_host}#{path}").to_return(status: 200) }
it { is_expected.to eq("succeeded") }
end
context 'when response status is 5xx' do
before { stub_request(:post, "https://#{karte_host}#{path}").to_return(status: [500, "Internal Server Error"]) }
it { is_expected.to eq("failed") }
end
end
end