よくわかる Apache Thrift J to R

はじめに

この記事は、サーバサイドエンジニアの平凡な日常で得た Apache Thrift の基本的な使い方を淡々と説明するものです。
過度な期待はしないでください。
それと、Swift1)アップルのiOSおよびOS Xのためのプログラミング言語と勘違いしてこのページを開いてしまった方にはすみません。


みさなまどうも。サーバサイドのエンジニアぶらいじぇんです。

エンジニアの皆様におかれましては、普段いかがおプログラミングでしょうか?
さて、諸姉兄におきましてはサービスリリース浅く監視・運用厳しき折、既存のサービスとの連携とは名ばかりで無茶な実装せざるを得ないなんてこともままあるのではないでしょうか?
私に至っては、『このサービスの連携がこんなに難しいはずがない』とか『僕には解決策が少ない』とか感じてしまう今日此頃です。2)超訳すると、RoRのサービスでJavaのAPIを叩きたかったときにThriftを調べながら使ってみた。

この記事はサンプルコード(クライアントサイドRuby、サーバーサイドJava)を実装し実行することで、Thriftの基本的な使い方を説明する内容となっています。

Thrift の概要

社内の勉強会で使用した資料3)加筆修正しています。がありますので、こちらを御覧ください。


実装サンプル(コード生成)

Thriftをインストールする

まずはThriftをインストールします。
テンプレートファイルから各言語のソースコードをジェネレートするためのもので、開発環境にさえインストールしておけばオッケーです。

brew install thrift

サンプルを作成する

あっさりと開発環境の構築をおえたところで、早速サンプルコードを書いていきます。
このサンプルでは、Thriftサーバの情報(時刻、JVMメモリ)を返すAPIを作成します。4)本番運用の際にもこういうAPIを作っておくと、接続確認などに使えるのでお勧めです。(╹◡╹)b

テンプレートファイルを作成する

environment.thrift ファイルを作成します。

namespace java com.example.gen
namespace rb Com.Example.Gen
enum ServerInfoKey {
  TIME = 1;
  MEM = 2;
}
struct ReqServerInfo {
  1:ServerInfoKey key;
}
struct ResServerInfo {
  1:string value;
}
exception ServerInfoException {
 1:list<string> traces;
}
service Environment {
  ResServerInfo getServerInfo(1:ReqServerInfo req) throws (1:ServerInfoException e);
}

テンプレートファイルからジェネレートする

thrift --gen java --gen rb environment.thrift

生成されるファイル
gen-java/com/example ディレクトリ以下に生成されます。

Environment.java
ReqServerInfo.java
ResServerInfo.java
ServerInfoException.java
ServerInfoKey.java

gem-rb/environment ディレクトリ以下に生成されます。

environment.rb
environment_constants.rb
environment_types.rb

備考

ここでいったん Thriftテンプレートファイル での定義を補足しておきます。

  • namespace - 出力されるファイルのパッケージ名を指定する (言語ごとに指定可能)
  • exception - service で投げる例外
  • struct - service での引数、戻り値になる構造体
    • 引数、戻り値を構造体(ReqServerInfo、ResServerInfo)にしていることで、null(valueプロパティ)の値を返すことができる
    • 引数、戻り値そのもの(ReqServerInfo、ResServerInfo)は null にできない
  • enum - enum型を定義できる
  • 定義済み型 - bool, byte, i16, i32, i64, double, string, binary, map, set, list

実装サンプル(サーバーサイド)

Java Servletを実装する

package com.example.servlet;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.servlet.annotation.WebServlet;
import com.example.gen.Environment.Processor;
import com.example.gen.Environment.Iface;
import com.example.gen.ReqServerInfo;
import com.example.gen.ResServerInfo;
import com.example.gen.ServerInfoException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServlet;
@WebServlet(name = "EnvironmentServlet", urlPatterns = { "/environment" })
public class EnvironmentServlet extends TServlet {
    public EnvironmentServlet() {
        super(new Processor<>(new Iface() {
            @Override
            public ResServerInfo getServerInfo(ReqServerInfo req) throws ServerInfoException {
                String value;
                try {
                    switch (req.getKey()) {
                        case MEM:
                            value = String.valueOf(Runtime.getRuntime().totalMemory());
                            break;
                        case TIME:
                            value = String.valueOf(new Date());
                            break;
                        default:
                            throw new IllegalArgumentException("Not Found: key = " + req.getKey());
                    }
                } catch (Exception e) {
                    StackTraceElement[] sts = e.getStackTrace();
                    List<String> traces = new ArrayList<>(sts.length);
                    for (StackTraceElement st : sts) {
                        traces.add(st.toString());
                    }
                    throw new ServerInfoException(traces);
                }
                return new ResServerInfo(value);
            }
        }), new TBinaryProtocol.Factory());
    }
}

pom.xml(Maven)に追記します。

<dependency>
  <groupId>org.apache.thrift</groupId>
  <artifactId>libthrift</artifactId>
  <version>0.9.2</version>
</dependency>

実装サンプル(クライアントサイド)

Gemfile ファイルを以下の内容で作成(or 追記)します。

source 'https://rubygems.org'
gem 'thrift', '0.9.1'
gem 'rack', '~> 1.5.2'
gem 'thin', '~> 1.6.2'

bundle install します。

bundle config build.thrift --with-cppflags='-D_FORTIFY_SOURCE=0'
bundle --path vendor/bundle

Rubyクライアントを実装する

environment_client.rb ファイルを以下の内容で作成します。

#!/usr/bin/env ruby
$:.push('./gen-rb')
require 'thrift'
require 'environment'
GEN = Com::Example::Gen
url = "http://localhost:8080/environment"
transport = Thrift::HTTPClientTransport.new(url)
protocol = Thrift::BinaryProtocol.new(transport)
client = GEN::Environment::Client.new(protocol)
begin
  p "=== BEGIN"
  transport.open
  req = GEN::ReqServerInfo.new
  req.key = GEN::ServerInfoKey::TIME
  res = client.getServerInfo(req)
  p "res = #{res}, res.value = #{res.value}"
rescue => e
  p "Occurred Exception. #{e}"
else
  p "Success."
ensure
  transport.close
  p "=== END"
end

実行する

先に実装しておいた サーバーサイドの実装(Servlet)TomcatJetty などで実行しておき、以下のコマンドを実行します。

bundle exec ruby environment_client.rb

出力結果

=== BEGIN
res = #<Com::Example::Gen::ResServerInfo:0x007fa6ac2d35c0>, res.value = Thu Jan 04 23:59:55 JST 2015
Success.
=== END

簡単でしたね(╹◡╹)

さいごに

上記サンプルではサーバーサイドを Java の Servlet として実装しているので、AWS の Opsworks(Java App)ElasticBeanstalk 上でわりと簡単に稼働させることができます。

インターナルなAPIをプログラム言語を越えてプロキシしたいとき、解決策の一つとしていかがでしょうか?

以上になります。m(_ _)m
それでは
来週もサービス、開発

参考サイト

脚注

脚注
1 アップルのiOSおよびOS Xのためのプログラミング言語
2 超訳すると、RoRのサービスでJavaのAPIを叩きたかったときにThriftを調べながら使ってみた。
3 加筆修正しています。
4 本番運用の際にもこういうAPIを作っておくと、接続確認などに使えるのでお勧めです。(╹◡╹)b
5 この記事では触れませんでしたが、Thriftのサーバーとクライアント間でデータを送受信する際のプロトコルのことです。