gRPCでサーバーとAndroid/iOSを通信する - Re.Ra.Ku アドベントカレンダー day 22

Re.Ra.Ku アドベントカレンダー 22日目です。

こんにちは。安部です。

ずっと気になっていたgRPCをせっかくの機会なので試してみました。

基本はgRPCの公式のサンプルとほぼ変わっていませんが、サーバー側とAndroid/iOSを連携するために必要なものをまとめてみました。

gRPCとは

grpc / grpc.io

A high performance, open-source universal RPC framework

Googleが作ったRPCフレームワークです。

gRPCでは様々な言語をサポートしていて、Protocol Buffersを使ってます。

Protocol Buffersのインターフェース定義からgRPCのコード生成できるようになっています。

gRPCインストール

Protocol Buffersの.protoファイルからコードを生成するためのビルドツールをインストールします。

Macを使っているので、今回はHomebrewを使ってインストールします。

$ brew tap grpc/grpc
$ brew install --with-plugins grpc

grpc/homebrew-grpc: gRPC formulae repo for Homebrew

protoファイル

Protocol Buffersのインターフェースの定義ファイルになります。インタフェース定義言語(IDL)を使って記述していきます。これを元にソースコードを生成します。

サーバーとクライアントの両方で同じファイルを使用するのでjavaやobjcの設定もあります。

詳しい仕様はProtocol Buffersのドキュメントを見てください。

メッセージとしてHelloRequestHelloReplyを定義して、GreeterというサービスでRPCのSayHelloメソッドを定義している感じです。

helloworld.protoで保存します。

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.star_zero.example.grpcandroid.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

サーバー側設定

今回はRubyでやります。ちょっと調べた感じGoで書いてるのが多い気がしましたが、私はGoがまだ分からないので…

セットアップ

$ bundle init

Gemfileを次のようにします。

source "https://rubygems.org"

gem "grpc"

bundle install

$ bundle install --path vendor/bundle

protoファイルからソースコード生成

protocというコマンドを使ってソースコードを生成します。

libディレクトリに出力するので事前にディレクトリを作成しています。helloworld.protoは一つ上の階層のprotosディレクトリに配置しています。パスは適宜変更指定ください。

$ mkdir lib
$ protoc -I ../protos --ruby_out=lib --grpc_out=lib --plugin=protoc-gen-grpc=`which grpc_ruby_plugin` ../protos/helloworld.proto

これを実行するとソースコードが2つ生成されていると思います。

  • lib/helloworld_pb.rb
  • lib/helloworld_services_pb.rb

サーバー側コード

かなり分かってないこと多いですが、先程のprotoファイルから生成されたコードを使ってRPCサーバーを起動しています。

この例ではもらったリクエストパラメータにHelloという文字列をつけて返却しています。

root = File.dirname(__FILE__)
$LOAD_PATH.unshift File.join(root, 'lib')

require 'rubygems'
require 'bundler/setup'
require 'grpc'
require 'helloworld_services_pb'

class GreeterServer < Helloworld::Greeter::Service
  def say_hello(hello_req, _unused_call)
    Helloworld::HelloReply.new(message: "Hello #{hello_req.name}")
  end
end

def main
  s = GRPC::RpcServer.new
  s.add_http2_port('0.0.0.0:50051', :this_port_is_insecure)
  s.handle(GreeterServer)
  s.run_till_terminated
end

main

実行

$ ruby server.rb

これでサーバー起動します。

Androidクライアント

適当にプロジェクトを作ります。

build.gradle

プロジェクト直下のbuild.gradleにprotocol bufferのプラグインを追加します。

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.2'
        classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.0"
    }
}

app直下のbuild.gradleにgRPC関連の設定と、protoからコードを生成するための設定を追記します。

apply plugin: 'com.google.protobuf'

// ...

android {

    // Warning:Conflict with dependency 'com.google.code.findbugs:jsr305'.
    // が出たときはこれを追加します
    configurations.all {
        resolutionStrategy.force 'com.google.code.findbugs:jsr305:2.0.1'
    }
}

dependencies {
    // ...

    compile 'com.squareup.okhttp:okhttp:2.7.5'

    compile 'io.grpc:grpc-okhttp:1.0.2'
    compile 'io.grpc:grpc-protobuf-lite:1.0.2'
    compile 'io.grpc:grpc-stub:1.0.2'
    compile 'javax.annotation:javax.annotation-api:1.2'
}

protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.0.0'
    }
    plugins {
        javalite {
            artifact = "com.google.protobuf:protoc-gen-javalite:3.0.0"
        }
        grpc {
            artifact = 'io.grpc:protoc-gen-grpc-java:1.0.2'
        }
    }
    generateProtoTasks {
        all().each { task ->
            task.plugins {
                javalite {}
                grpc {
                    option 'lite'
                }
            }
        }
    }
}

protoファイル

app/src/mainprotoディレクトリを作ってそこに、先程作成したhelloworld.protoを配置します。

少し確認した感じですと、protoっていうディレクトリ名が重要でこれを間違えているとうまくビルドできませんでした。

ビルド

ビルドをすると、app/build/generated/source/proto内にソースコードが生成されていると思います。

ソースコード

サーバーとの通信するには、protoファイルから生成されたものを使用して通信します。

newBlockingStubでstubを作ると、処理をブロックして通信が終わるのを待ちます。

// ホストとポートを指定(エミュレータからlocalhostアクセスのために10.0.2.2にしてます)
ManagedChannel channel = ManagedChannelBuilder.forAddress("10.0.2.2", 50051)
        .usePlaintext(true)
        .build();


GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);

// リクエスト生成してパラメータを設定
HelloRequest message = HelloRequest.newBuilder().setName("Android").build();
// 実行して結果を受け取る
HelloReply reply = stub.sayHello(message);

return reply.getMessage();

非同期でやる場合は次のようにnewStubでstubを作って、メソッドを呼び出すときにObserverを渡してあげる感じになります。RxJavaっぽい。

GreeterGrpc.GreeterStub stub = GreeterGrpc.newStub(channel);

HelloRequest message = HelloRequest.newBuilder().setName("Android").build();
stub.sayHello(message, new StreamObserver<HelloReply>() {
    @Override
    public void onNext(HelloReply reply) {
        Log.d(TAG, "Message = " + reply.getMessage());
    }

    @Override
    public void onError(Throwable t) {
    }

    @Override
    public void onCompleted() {
    }
});

iOS

こちらも適当にプロジェクトを作ります。今回はSwiftでやっています。

CocoaPods

CocoaPodsを使いますが、結構特殊な感じになってます。

まずはpodspecを作ります。通常はライブラリ作るときに使うんですけど、protoファイルから生成したコードをライブラリとして設定するためのものになります。

authorsやディレクトリ等の設定は環境に合わせてください。authorsとかはとりあえずサンプルの設定のままやってます。

s.prepare_commandのとこでprotoファイルからソースコードを生成しています。

HelloWorld.podspecとして保存します。

Pod::Spec.new do |s|
  s.name     = "HelloWorld"
  s.version  = "0.0.1"
  s.license  = "New BSD"
  s.authors  = { 'gRPC contributors' => 'grpc-io@googlegroups.com' }
  s.homepage = "http://www.grpc.io/"
  s.summary = "HelloWorld example"
  s.source = { :git => 'https://github.com/grpc/grpc.git' }

  s.ios.deployment_target = "10.1"

  # protoファイルのディレクトリ
  src = "../protos"

  # gRPCのプラグイン
  s.dependency "!ProtoCompiler-gRPCPlugin", "~> 1.0"

  pods_root = 'Pods'

  protoc_dir = "#{pods_root}/!ProtoCompiler"
  protoc = "#{protoc_dir}/protoc"
  plugin = "#{pods_root}/!ProtoCompiler-gRPCPlugin/grpc_objective_c_plugin"

  dir = "#{pods_root}/#{s.name}"

  # protoファイルからソースコード生成処理
  s.prepare_command = <<-CMD
    mkdir -p #{dir}
    #{protoc} \
        --plugin=protoc-gen-grpc=#{plugin} \
        --objc_out=#{dir} \
        --grpc_out=#{dir} \
        -I #{src} \
        -I #{protoc_dir} \
        #{src}/helloworld.proto
  CMD

  # 生成されたコードからsubspec設定
  s.subspec "Messages" do |ms|
    ms.source_files = "#{dir}/*.pbobjc.{h,m}", "#{dir}/**/*.pbobjc.{h,m}"
    ms.header_mappings_dir = dir
    ms.requires_arc = false
    ms.dependency "Protobuf"
  end

  # 生成されたコードからsubspec設定
  s.subspec "Services" do |ss|
    ss.source_files = "#{dir}/*.pbrpc.{h,m}", "#{dir}/**/*.pbrpc.{h,m}"
    ss.header_mappings_dir = dir
    ss.requires_arc = true
    ss.dependency "gRPC-ProtoRPC"
    ss.dependency "#{s.name}/Messages"
  end

  s.pod_target_xcconfig = {
    'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1',
    'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES',
  }
end

Podfileは以下のように自分のディレクトリをライブラリとして読み込むように設定します。

target 'GrpcIos' do
  use_frameworks!

  # HelloWorld.podspec
  pod 'HelloWorld', :path => '.'
end

ここまで出来たらインストールします。

$ pod install

ソースコード

gRPCのコードはObjective-Cになっていますので、Bridging-Headerに使用するものをimportします。

#import <GRPCClient/GRPCCall+ChannelArg.h>
#import <GRPCClient/GRPCCall+Tests.h>
#import <HelloWorld/Helloworld.pbrpc.h>

Swift側のコードimportします。

import HelloWorld

最後に通信する処理を記述します。

let hostAddress = "localhost:50051"

GRPCCall.useInsecureConnections(forHost: hostAddress)
GRPCCall.setUserAgentPrefix("HelloWorld/1.0", forHost: hostAddress)

let client = HLWGreeter(host: hostAddress)
let request = HLWHelloRequest()
request.name = "iOS"
    
client.sayHello(with: request, handler: { (reply: HLWHelloReply?, error: Error?) -> Void in
    if let reply = reply {
        print("message = " + reply.message)
    }
})

まとめ

少し触ってみた感じ使いこなせるとだいぶ強力だなと思いました。型もあって、メソッド呼び出しと変わらないので、JSONより断然扱いやすかったです。

protoファイルによってインターフェースもしっかり定義されるので、それ自体が仕様になるのも良かったです。

敷居は高い感じですが、導入事例も国内外で結構あるようなので今後選択肢に入ってくるのではないでしょうか。

プログラミングしながらできる、健康への近道講座:4回目 - Re.Ra.Ku アドベントカレンダー day 21

こんにちは!
Re.Ra.Kuの泉原です。

ついに4回目、アドベントカレンダーの中では最後の投稿です。
拙い内容でしたが、読んでくださった方々、ありがとうございます!
今回も宜しくお願い致します!

最後は、エンジニアの皆様が最も酷使しているであろう、目・首のストレッチ方法をお伝えします。

目・首の仕組み

今までご紹介した部位に比べて小さい部位である目と首。
小さな筋肉ですが、やっていることはとても大事な働きをしています。

1. 重い頭を支える首

頭の重さは、ご自身の体重の約10%と言われています。
50kgの体重の方の場合、頭の重さはおよそ5kgです。身近なところでいうと、だいたいスイカ1個分ほどの重さです。
日々生活している時は、スイカの重さを首で支えている状態になっています。
1回目で触れたとおり、パソコン業務をしている時は体が前のめりになりやすく、首も前に倒れがちです。
プログラミングしながらできる、健康への近道講座:1回目 - Re.Ra.Ku アドベントカレンダー day 3 - Re.Ra.Ku tech blog
細い首にスイカが乗った状態なので、少し前に倒れるだけでも首にかかる負担は思っている以上に大きくなります。

2. 涙で覆われている目

他の部位は皮膚や筋肉で守られていますが、目は外気に近い部位です。
皮膚や筋肉の代わりに涙で表面を覆って保護しています。
そのため、まばたきが減ったり強い力でこすると、目を守っている涙が乾燥し、目への刺激が強くなり疲れが溜まりやすくなります。
上記に加えて、首の筋肉が固まっていると、目の疲れを取るための流れも悪くなり、目の疲れも取れにくくなります。

目の疲れを取るためにも、首の位置を整えていらない力を抜いてあげる必要があります。

首・目のストレッチ

それではストレッチに入っていきます。
今回もいつもと同じく、1回目の講座で出てきた姿勢を作ってみて下さい。
また、首は背骨を通じて腰・骨盤につながっています。
骨盤周りのストレッチを入念に行ってから首のストレッチをするとよりほぐれやすくなるので、時間がある方は復習してみて下さい。
http://techblog.reraku.co.jp/entry/2016/12/15/150000

綺麗な姿勢を作ったら、左手で椅子の真ん中をつかみ、左肘の裏を表に向けます。
その状態から右手で左耳の後ろを支え、自然な呼吸で右側に体を倒します。 f:id:nozomiii:20161214124337j:plain
気持ち良く伸びたな、と実感できたら息を吸いながらゆっくり体を戻します。
戻した後は反対側も行います。

反対側が終わったら、椅子を持つ位置を真ん中から後ろに変えて、腕の対角線上に体を倒します。
f:id:nozomiii:20161214124308j:plain
これも、気持ちの良い負荷と呼吸で、両方共ストレッチしていきます。

このストレッチは、肩甲骨の回で最初にご紹介したストレッチの応用編からやっていきます。
プログラミングしながらできる、健康への近道講座:2回目 - Re.Ra.Ku アドベントカレンダー day 9 - Re.Ra.Ku tech blog
肩甲骨と首を一緒に伸ばすことができるお得なストレッチなので、ぜひプログラミングの合間にやってみてください。

次に、首と目の疲れを取るストレッチを行います。
首と耳のつけ根のくぼみに親指をあてて、力を抜いて頭の重さで首を後ろに倒します。
f:id:nozomiii:20161214124310j:plain
首の疲れを取ると共に、目の疲れからくるリンパの流れをよくするストレッチです。

最後に、目の疲れを取るストレッチです。
両肘をついて、親指を鼻の付け根の両脇におき、力を抜いて頭の重さで首を前に倒します。
この時、眼球を押さないように、また指の力を最大限に抜くように意識してみてください。
f:id:nozomiii:20161214124330j:plain
考え事をしているついでにできるストレッチなので、ぜひお試しください。

首と目の力が抜けると、自律神経が整い体だけでなく心の疲れも取れやすくなります。
リラックスするとよりコードの進みも良くなると思いますので、ちょっとした合間にやってみてください。

最後に

しつこいようですが、弊社リラクの宣伝もさせて下さい笑
弊社の店舗では首、目、頭もほぐす「アイヘッドケア」というオプションコースもあります。
(一部対象外店舗あり)
10分追加するだけでも、リフレッシュ感が全然違います。
お仕事の合間でも、お休みの日でも、ぜひ試しにいらしてください。
reraku.jp

最後になりますが、4回に渡った今回の企画、読んでくださった皆様本当にありがとうございます!
機会があればまた書きますので、その際はまた宜しくお願いいたします。
今後ともよろしくお願いいたします!

Scalaと向き合い続けてゆく - Re.Ra.Ku アドベントカレンダー day 20

Re.Ra.Ku アドベントカレンダー 20日目です。

こんにちは。近藤です。

今日も今日とてScalaのお話です。しかしコードはほとんど出て来ません。

さて、この記事をご覧の方々はScalaに対してどんなイメージを抱いているでしょうか?Scalaにまったく触れたことがない方、Scalaを少しだけ触ったことがある方、Scalaを普段からお使いの方、もちろんそれ以外にも触れたことのあるレベルに違いはあるでしょう。そして抱くイメージにも違いがあるかと思います。

そして私は一貫して「難しい、そして楽しい」というイメージを抱いています。

たしかにScalaに触れ始めたころは難しいところもありました。でもそれを使いこなしているときの楽しさも並ではありません。

私がScalaに触れ始めたのは4年ほど前だった気がしますが、そのときは趣味程度で触れていただけで、実務として触れているのはここ1年だけです。そんな中どう向き合ってきたかを振り返ってみます。

いきなり難しいことをしない

Scalaを使い始めるのはとても簡単です。JREやScalaをインストールすればいつでも始められます。

それでもいざ書こうとすると、やれ関数型だとか、やれ型だとか、そういう囁きがまとわりつくものです。ていうかまあ実際そうです。

ただそうだからといって、最初からそれを望むことはないと思います。関数型に無理にこだわらなくても書けます。型は制約ですので、それを定義する側は知らなくてはいけないことが多いですが、使う側にとっては型に縛られているからこその安心感があります。

それらを享受しつつ、ベターJava程度のものとして使っていく方が導入としては気楽かと思われます。

文法は多岐に渡っているように見えるだけ

新しいプログラミング言語に触れれば、当然新しい文法を知らなければなりません。とは言え、文法上は他のプログラミング言語と類似していることも少なくないため、受け入れやすいことも多々あります。

ただ個人的な感想で言うと、Scalaの文法は比較的複雑だと思います。というか、ひとつのことに対する書き方が多いです。

しかし書き方が多いだけで、噛み砕いてしまえばなんてことが無かったりします。私の感じたことで言うと、関数の呼び出しでしょうか。例えばこんな関数があります。

def execIf(condition: => Boolean)(exec: => Unit): Unit =
  if (condition) {
    exec
  }

名前渡し(Call-by-name)を使っていたりはしますが、引数リスト (condition: => Boolean)(exec: => Unit) の2つを取る関数です。呼び出し方は以下の通りです。

execIf(age >= 20)(println("age is over 20"))

そして以下のようにも呼べます。

execIf(age >= 20) { println("age is over 20") }

{} を使っていますね。Scalaの {} は式の集合で、その集合の最後の式の結果が {} 自体の結果となります。つまり:

execIf(age >= 20)({ println("age is over 20") })

と自由に {} で囲ってもなんら問題はありません。そしてこの場合関数呼び出しのための () を省いても構いません。よって:

execIf(age >= 20) { println("age is over 20") }

と書けるわけです。これであたかもifのように見える構文となります。そして名前渡しを利用することによって関数側で参照されるまで評価されないように出来ます。さらに {} は式の集合なので、複数の行を書いても構いません。

とまあこんな風に関数呼び出しひとつ取ってもいろいろな書き方があります。コードを読むときや、「こう書きたいんだけど……」というときにどう定義すれば良いか分からなかったりします。ただとあるルールといくつかの手法の組み合わせなことが多いです。

これはひとつひとつ学んで飲み下していく他ありません。私のおすすめはScalaスケーラブルプログラミング、いわゆるコップ本です。頭から順に読めばほとんどの疑問は解決するかと思います。覚えるということに近道もなければ魔法もないと思うので、Scalaに興味があるのであれば是非とも。

コンパイラは魔法ではない

Scalaを使う側は気楽だと言いましたが、ずっと使う側でいるなんてことはないと思います。例えフレームワークやライブラリを書かないとしても、プログラミングは抽象化の連続であり、いつかどこかで抽象化しなくてはいけないときが来ると思います。

そしてScalaにおける抽象化は、型との闘いです。型パラメータ、型クラス、上限境界/下限境界、変位指定などなど。この辺りを駆使すると、使う側にとって嬉しいものが出来上がります。それとコードでの表現力がグッと高まります。

この辺りも小さく始めていけば良いとは思いますが、いずれは遭遇するもの。このとき大切なのがコンパイラの気持ちになって考えるというものです。……自分で言っておいて何ですが、胡散臭いですね。

ただまあ言えることとして、コンパイラも魔法ではありません。とあるルールに沿っているわけです。そのルールが複雑でないとは言い切れませんが、複雑怪奇というほどではなく、人間が追うことは十分可能だと思います。

さきほど挙げた型にまつわる概念ですが、これらは型推論があるからこそ実用的なものであると考えています。関数(メソッド)を呼ぶときに型を指定することってあまりないと思いますが、これはどこかに型の情報があって型推論が働くからです。どうやって働いているかですが、ちゃんと自分の頭で考えてあげると十分辿れます。何度も言いますが魔法じゃありません。

とは言いつつ、implicit conversionのように「え、そんなところまで頑張っちゃうの!?」的なやつもあるにはあります(感じ方には個人差があります)。

まあ結局は「Scalaスケーラブルプログラミングを読もう!」となってくるのですが、この辺りは読んだだけでは掴み切れないものかもしれません。しかしやっていくうちに案外染み付いてきます。やっていきましょう。雑でマッチョな考えですが、そういうものだと思います。

私感

今まで述べたものはあくまで個人の感想です。私がここ1年で感じたことばかりです。

「うっわ意味分からん……」というのは何度も感じました。コンパイラと4時間格闘した末に妥協のコードを書いたことなんてザラです。しかし向き合うことで「分かった!!!」と大声上げたくなることもありました。

Scalaは難しいと思います。少なくとも私の頭でいきなり理解に及ぶことはありません。「これ考えた人は天才だな……。それを私が理解できるのか……」という卑屈なことは何度も思いました。いや、本当にライブラリなどを書けるようになるのか心配でしたからね!?それでも少しずつやっていったらなんとか出来ていったわけです。

Scalaに詰められたおもしろい考え方、それを理解して次第に書けるようになってゆく喜び、その上で現実の問題にぶち当たっていく。そのあたりに私は楽しいと感じるみたいです。

最後に

何をおもしろいと捉えるかは人それぞれですが、知ることが好きな人であればScalaのそれは非常におもしろいと思います。興味のある方は是非ともご自身の手で触れてみてほしいです。

それとですね、私が言っていますし、Re.Ra.Kuで使われているプログラミング言語とその選択理由でも触れられていますが、リラクではサーバサイドにScalaを採用しています。「もっと詳しい話聞かせて」だとか、「遊びに行ってみたいな〜」という方は是非お声掛けください。私宛には@takkkunにリプライいただければOKです。

そんなこんなでScalaの楽しさを分かり合える方が増えればと密かに思っております。それでは。

開発環境でDockerをちっちゃく導入する - Re.Ra.Ku アドベントカレンダー day 19

Re.Ra.Ku アドベントカレンダー 19日目です。

こんにちは、神場です。

前回まででSwift関連の記事を2つほど書かせていただきましたが、今回はDockerの記事です。

開発環境でのDocker

開発環境でDockerを導入する場合、アプリケーション、DBなどに分けてdocker-composeでコンテナを立てるのが一般的かと思います。

RailsPostgresqldocker-compose.ymlの例

version: '2'
services:
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/app
    ports:
      - 3000:3000
    links:
      - db  
  db:
    image: postgres
    restart: always
    environment:
      POSTGRES_PASSWORD: password
      POSTGRES_USER: me
    volumes:
      - ./postgres:/var/lib/postgresql/data
    ports:
      - 5432:5432

このようにアプリケーションのレイヤーも含めてコンテナ化する例が多いと思うのですが、場合によってはアプリケーションレイヤーを除いてコンテナ化するのが良い場合もあるのではないか?というのがこの記事の内容です。

(上の例であれば、postgresのコンテナさえ立てておけばポートフォワードはされているので、アプリケーションのコンテナを立てなくてもホスト側からpsql -U me -h 127.0.0.1で接続出来ます。)

アプリケーションレイヤーを除いてコンテナ化した場合のメリット

思いつくものをいくつか列挙してみます。

ちょっと動作を試したい・見たいときにコンテナにsshで入る必要がない

これは要はアプリケーションレイヤーまでコンテナ化すると、例えばある単体テストを個別に実行したい、コンソール(RubyirbScalasbtなど)を起動したいという時にdocker exec等でコンテナに入っておく必要があるということです。手元でサクッと試したい、という場合にローカルのほうが楽な場合があるという感じでしょうか。

公式のイメージがあるものだけを使えばDockerfileを書く必要がない

MySQLPostgreSQLなど有名どころのものであればすでにイメージがあるので、自前でDockerfileを書く必要はなくなります。ただ、もちろんその代わりアプリケーションを起動するローカルの環境は整える必要があるので、以下の条件

  • アプリケーションを起動するまでの初期設定が複雑である
  • 使っている言語にバージョン管理やパッケージ管理(Rubyならrbenvbundler)のエコシステムが揃っていない

に当てはまるときは、素直にDockerfileを書いたほうが良さそうです。

buildする必要がない

Dockerfileがないのでもちろんbuildもなくなります。

まとめ

上記のことを踏まえてまとめると、

  • アプリケーションの初期設定が容易である
  • 開発の初期段階で、まだDockerfileが頻繁に変更されるフェーズにある
  • プロジェクト全体としてはDockerを使っていないが、個人的には使いたい
  • アプリケーションレイヤーをコンテナ化すると開発環境で気になるほどパフォーマンスが下がる(実際にどのぐらい下がるのか未検証ですが。。)

といったような場合には検討しても良さそうです。限定的ではありますし、環境を完全に揃えたい場合はもちろんDockerfileを書くべきですが、最初の導入としてまずはアプリケーション以外のレイヤーだけをコンテナにするという選択肢もあるのではないかと思います。

BottomNavigationViewのカスタマイズ - Re.Ra.Ku アドベントカレンダー day 18

Re.Ra.Ku アドベントカレンダー 18日目です。

こんにちは。安部です。

BottomNavigationViewのカスタマイズを試してみました。

ちょっと無理やりやってる感じの箇所もあります。

Version

使用したSupportLibraryは25.1.0です。

BottomNavigationViewの基本

基本的な使い方は以前、私の書いたものを参照してください。

BottomNavigationViewを試してみる - Qiita

アイコンとテキストの色を状態によって変更する

未選択の状態、タップされてる状態、選択された状態で色を変更する方法です。

f:id:STAR_ZERO:20161217224859p:plain:w200

res/color/bottom_navigation.xml を下記のように作成します。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true" android:color="#FF0000" />
    <item android:state_pressed="true" android:color="#00FF00" />
    <item android:color="#FFFFFF" />
</selector>

次にそれをレイアウトで指定してあげます。app:itemIconTintapp:itemTextColorです。

<android.support.design.widget.BottomNavigationView
    android:id="@+id/navigation"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/colorPrimaryDark"
    app:itemIconTint="@color/bottom_navigation"
    app:itemTextColor="@color/bottom_navigation"
    app:menu="@menu/bottom_navigation" />

常にテキストを表示させる

メニューの数が3つを超えると選択されているメニュー以外のテキストが表示されなくなってしまいます。それを無理やり表示する方法です。

正直よろしくない方法だと思います。現状で頑張った結果です。

MatrialDesignの仕様を確認すると3つ超えるとテキスト出てないので、別に無理やりやる必要はないのですが、なんか要望とかであがってきそうなパターンだなって思ってやってみました。

f:id:STAR_ZERO:20161217225142p:plain:w200

BottomNavigationView navigationView = (BottomNavigationView) findViewById(R.id.navigation);
BottomNavigationMenuView menuView = (BottomNavigationMenuView) navigationView.getChildAt(0);
for (int i = 0; i < menuView.getChildCount(); i++) {
    BottomNavigationItemView itemView = (BottomNavigationItemView) menuView.getChildAt(i);
    itemView.setShiftingMode(false);
    itemView.setChecked(false); // Viewの状態を反映させるために呼んでいる
}

スクロール時に隠す

RecyclerView等をスクロールしたときにBottomNavigationViewを隠す方法です。Toolbarでよくあるやつですね。

f:id:STAR_ZERO:20161217225309g:plain:w200

CoordinatorLayout.Behaviorを継承した下記のクラスを作成します。スクロールに合わせてViewの位置を変更しています。

public class BottomNavigationBehavior extends CoordinatorLayout.Behavior<BottomNavigationView> {

    private int defaultTop;
    private int defaultBottom;
    private int defaultHeight;

    public BottomNavigationBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, BottomNavigationView child, int layoutDirection) {
        defaultTop = child.getTop();
        defaultBottom = child.getBottom();
        defaultHeight = defaultBottom - defaultTop;
        return super.onLayoutChild(parent, child, layoutDirection);
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, BottomNavigationView child, View directTargetChild, View target, int nestedScrollAxes) {
        return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
    }

    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, BottomNavigationView child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        ViewCompat.offsetTopAndBottom(child, dyConsumed);
        if (dyConsumed > 0 && child.getTop() > defaultBottom) {
            child.setTop(defaultBottom);
        } else if (child.getTop() < defaultTop) {
            child.setTop(defaultTop);
        }
        child.setBottom(child.getTop() + defaultHeight);
    }
}

次にレイアウトファイルです。ちょっと長いですが全部のせておきます。

BottomNavigationViewapp:layout_behaviorに先程のクラスを指定します。これでスクロール時に隠れるようになります。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">

        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:popupTheme="@style/AppTheme.PopupOverlay" />

        </android.support.design.widget.AppBarLayout>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scrollbars="vertical"
            app:layout_behavior="@string/appbar_scrolling_view_behavior" />

        <android.support.design.widget.BottomNavigationView
            android:id="@+id/navigation"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:background="@color/colorPrimaryDark"
            app:itemIconTint="@android:color/white"
            app:itemTextColor="@android:color/white"
            app:layout_behavior="com.star_zero.example.bottomnavigation.BottomNavigationBehavior"
            app:menu="@menu/bottom_navigation" />
    </android.support.design.widget.CoordinatorLayout>

</LinearLayout>

まとめ

BottomNavigationViewは制限がけっこうあって、5つより多くのメニューを設定しようとするとExceptionが投げられてクラッシュしたり、アイコンやテキストの大きさを自由に変えたりがかなり厳しいです。

あとはバグがそれなりある気がします。試した感じSnackbarがうまく表示できていない感じでした。Issue Trackerにもあがっています。

現状ではアプリの仕様によっては実現が難しいものが出てくるかもしれませんので慎重に導入を検討したいですね。

Re.Ra.Kuで使われているプログラミング言語とその選択理由 - Re.Ra.Ku アドベントカレンダー day 17

丸山です。

アドベントカレンダーも17日目、だいぶ佳境に入ってきましたね!本日は弊社ではどのようなプログラミング言語が使われていて、そこにはどのような判断が働いているのか、ということについて書いていきたいと思います。

iOSアプリケーションと Androidアプリケーションの開発言語

iOSアプリケーションとAndroidアプリケーションの開発には、Swift(古いものは一部Objective-C)、AndroidアプリケーションはJavaで書かれています。プラットフォーマー(GoogleとかAppleとか)がサポートする環境に素直に乗っている形ですね。

ネイティブアプリの開発にはそんなに選択肢があるわけではないのですが、Xamarinに乗ってC#、あるいはReactNativeでJavaScript、Kotlinを利用する、など、プラットフォーマーが公式に用意した環境以外にも選択肢があります。

公式の用意する環境に乗ると、「そもそもJavaの表現力が……」とか「NSFoundationつらくない?」とかいろいろなデメリットがある一方、そうでない環境には「慣れ親しんだ言語で書ける」とか「AndroidとiOSで一部コードを共有できる」とか「高い表現力を持つ言語で書ける」とかいろいろなメリットがあります(CocoaTouchを理解しなくていい?それは幻想です)。

一方で、プラットフォーマーが用意したものではない環境には「MSが飽きたら終わり」「Facebookが飽きたら終わり」みたいな怖さもあります。また、どこかでプラットフォーマーの用意する環境へのブリッジが必要になり、ある意味「一枚余計なレイヤー」が発生することになります。組み合わせる技術要素は多くなれば多くなるほどトラブルの原因は増えます。

そのようなことを考えると、プラットフォーマーが用意したものではない環境に乗ってしっかりと開発するためには、プラットフォーム依存のフレームワーク(CocoaTouchとかAndroidSDKとか)に対する深い理解だけではなく、その環境に対する深い理解も必要となってきます。

なので、技術選定には、このコストを支払うことができるかどうか、というのがかなり重要なポイントになってくると思います。「俺はめちゃめちゃC#が得意なんだ、C#の中身ならなんでも知ってる」という人がすでにメンバーに存在する、とか、「ReactNative?中身全部読んだよ。俺の書くJavaScriptはめちゃめちゃメンテナブルだしな」みたいな人がすでにメンバーに存在する、とか、そういう「強み」がすでにある場合ならば、このコストを支払うのは問題にならないかもしれません。あるいは、サーバーの運用などに関してもC#やnodeJSのプロフェッショナルがいて、なおかつ少ないメンバーですべてのレイヤーを見なければいけないなどの場合、一気通貫で同じ言語で開発したい、という強いモチベーションが生まれたりすることもあるでしょう。そのときには、「このコストを支払ってでも統一言語でやっていく」というのは合理的な選択になりそうです。

弊社の場合、すでにiOSのプロフェッショナルとAndroidのプロフェッショナルがメンバーにいるので、そこであえて他のプラットフォームの乗っかるということにメリットは見いだせませんでした。その為、素直に公式の環境に乗っています。個人的にはXamarin気になってはいます。

APIサーバーの開発言語

HTTPを喋ってネイティブアプリやブラウザ上のSPAアプリケーションと通信するAPIサーバーは、Scalaで書かれています。

ネイティブアプリやブラウザで動くSPAアプリケーションにはどうしても言語選択の自由の幅が少なくなりますが、サーバーサイドは何で書いても自由です(えっServerSideSwift?ちょっと勇者すぎませんかね……)。

Scalaを選択するメリットとしては、高い表現力と安全性が挙げられるでしょう。一方でデメリットとして、「むずかしい」とか「いろいろな書き方ができすぎて宗教戦争が起こりそう」とかいろいろあるのですが、幸い今のところメンバーのバランス感覚(どこまで抽象度を上げて書くのかとか)はメンバーごとに大きく乖離することはなく、「仕事で書くならだいたいこのへんがちょうどいいバランスだよね」というなんとなくの合意が取れているように思えます。

弊社のメンバーの共通の得意言語はRubyなので、Rubyで書くというのもアリっちゃアリなんですが、「技術的に尖ったメンバーを集めたい、テックな企業にしていきたい」という経営側からの要望と、私の趣味と他社での運用実績が一致した結果Scalaでやっていこうという感じになっています。実際現状の人材面を見るに、この選択は悪くなかったなあと思っています。

その他細かい部分

デプロイのスクリプトや雑な集計とかがPerlだったり、DBのマイグレーションのサポートをRubyでやっていたり、サーバー管理のための細かいツールをGolangで書いたりしています。

このあたりは「このライブラリが使いたいからこの言語を使う」という理由で言語を選択したり、「雑な集計なら雑に書き慣れてるPerlでやりたい」っていう思いだったり、「いろんな環境で動かすコマンドになるからバイナリ一枚ぺろっと置けば動いてほしい」という理由があったりします。

まとめ

弊社では様々な要求に合わせて、適材適所で様々な言語を利用してアプリケーションの開発や運用を行っています。

合理的な理由があれば新しい言語もどんどん取り入れていく環境ですので、「オッいいですね、ここはちょっと働いてみたいですね」という感じで興味のある方はぜひご連絡お待ちしております。twitter: @neko_gata_s が一番連絡つきやすいと思います。まずは飯でもいきましょう。

MockWebServerを使ってAndroid通信をテストする - Re.Ra.Ku アドベントカレンダー day 16

Re.Ra.Ku アドベントカレンダー 16日目です。

こんにちは、安部です。

MockWebServerを使うと通信部分のモックが簡単にできるので、使い方を紹介したいと思います。

okhttp/mockwebserver at master · square/okhttp

(試してませんが、Android以外のJavaプロジェクトでも使えると思います。)

セットアップ

build.gradleに追加します。今回サンプルで使うOkHttpとRetrofitも追加してます。

testCompile 'com.squareup.okhttp3:mockwebserver:3.4.1'

compile 'com.squareup.okhttp3:okhttp:3.4.1'
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'

OkHttp

OkHttpを使った通信のテストのサンプルです。

テスト対象コード

URLをもらって通信して結果を返却するだけのコードです。実践的ではないですが、使い方をわかりやすくするために単純にしています。

モックのURLを渡せるようにエンドポイントのURLはコンストラクタで設定しています。

public class Connection {

    private String baseURL;

    public Connection(String baseURL) {
        this.baseURL = baseURL;
    }

    public String run() throws IOException {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(baseURL + "test").build();

        Response response = client.newCall(request).execute();
        return response.body().string();
    }
}

テストコード

簡単なテストコードです。

基本的な使い方はレスポンスを設定して、URLを取得して、そこにアクセスする感じです。

public class ConnectionTest {

    private MockWebServer server;

    @After
    public void tearDown() throws Exception {
        if (server != null) {
            // モックサーバーを停止
            server.shutdown();
        }
    }

    @Test
    public void run() throws Exception {
        server = new MockWebServer();
        // レスポンスで返したいものを設定
        MockResponse response = new MockResponse()
                // header
                .addHeader("Content-Type", "text/plain")
                // status code
                .setResponseCode(200)
                // body
                .setBody("レスポンス");

        server.enqueue(response);

        // モックサーバーのURL設定と取得
        HttpUrl url = server.url("/");

        // 実行
        Connection connection = new Connection(url.toString());
        String result = connection.run();

        // レスポンスを確認
        assertThat(result, is("レスポンス"));

        // リクエストを確認
        RecordedRequest request = server.takeRequest();
        assertThat(request.getPath(), is("/test"));
    }
}

Retrofit + RxJava

RetrofitとRxJavaを使ったサンプルです。

テスト対象コード

想定として、GitHubのユーザー情報を取得する感じのものです。

RxJavaのObservableを返す形になっています。使う側は取得したObservableをsubscribeする感じになるかと思います。

public interface GitHubService {

    @GET("users/{user}")
    Observable<User> getUser(@Path("user") String user);

}
public class GitHubAPI {

    private String baseURL;

    public GitHubAPI(String baseURL) {
        this.baseURL = baseURL;
    }

    public Observable<User> getUser(String user) {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseURL)
                // Gson
                .addConverterFactory(GsonConverterFactory.create())
                // RxJava Adapter
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();

        GitHubService service = retrofit.create(GitHubService.class);

        return service.getUser(user);
    }
}
public class User {

    public String id;

    public String name;
}

テストコード

MockWebServerを作るあたりは先程の例と変わりませんが、Observableをテストする方法として、TestSubscriberを使っています。TestSubscriberを使うとだいぶテストが書きやすくなると思います。

public class GitHubAPITest {

    private MockWebServer server;

    @After
    public void tearDown() throws Exception {
        if (server != null) {
            // モックサーバーを停止
            server.shutdown();
        }
    }

    @Test
    public void getUser() throws Exception {
        server = new MockWebServer();

        MockResponse response = new MockResponse()
                .addHeader("Content-Type", "application/json")
                .setResponseCode(200)
                .setBody("{\"id\":123456,\"name\":\"Kenji Abe\"}");

        server.enqueue(response);

        HttpUrl url = server.url("/");
        GitHubAPI api = new GitHubAPI(url.toString());

        // Test用のSubscriber
        TestSubscriber<User> testSubscriber = TestSubscriber.create();

        api.getUser("STAR-ZERO").subscribe(testSubscriber);

        // 終了するまで待つ
        testSubscriber.awaitTerminalEvent();
        // 完了を確認
        testSubscriber.assertCompleted();

        // onNextにあたるイベントからデータ取得
        User user = testSubscriber.getOnNextEvents().get(0);

        assertThat(user.id, is(123456));
        assertThat(user.name, is("Kenji Abe"));

        // リクエストを確認
        RecordedRequest request = server.takeRequest();
        assertThat(request.getPath(), is("/users/STAR-ZERO"));
    }

}

まとめ

MockWebServerを使うことで通信部分のテストが可能になります。使い方もそこまで難しくはないと思うのでオススメです。