WEB

【Rails】KARTEのTrackAPIを使った時の備忘録

WEB
この記事は約10分で読めます。

以前、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 NameDisplay 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の値に入るものが入っている想定です。
今回であれば

eventsdata
owner_idowner.uuid
owner_genderowner.gender
first_nameowner.first_name
last_nameowner.last_name
emailowner.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