コネヒト開発者ブログ

コネヒト開発者ブログ

Kotlin IDE Pluginに共通で定義管理できるplugin-common.xmlが出来ていた

f:id:tommy_kw:20181223172957p:plain

はじめに

本記事はコネヒト Advent Calendar 2018の24日目のエントリーです!

qiita.com

メリークリスマス!Androidエンジニアの富田です。今回はKotlin IDE PluginのKontributeに関する小話で、plugin-common.xmlが生まれてちょっとだけめんどくさい作業が減ったよという内容を紹介します。

Kotlin IDE PluginのKontributeって?

以下のようなCode InspectionIntentionなどの機能についてのKontributeを表します。 f:id:tommy_kw:20181223105545g:plain github.com

詳しい内容については割愛させていただきますが、Kotlin IDE PluginやKontributeについて興味を持った方はぜひ、しらじさんの下記資料をご覧ください! photos.google.com

Kontributeする上でここが煩わしかった

github.com

改めて上記のPRを見ていただくと、複数のplugin.xmlが存在することに気づきます。Kotlin IDE Pluginを開発する上でIntelliJ IDEAとAndroid Studioに対応しなければならないため設定情報(今回はInspection情報)を複数のplugin.xmlに記述する必要がありました。

gist.github.com

以前は上記の設定を下記全てのplugin.xmlに追加していました。ファイル数が多いため自動で追加するGradleタスクがないのかなと探してみたものの、結局なさそうだったのでどうしたものかなと悩んでいました。

  • plugin.xml
  • plugin.xml.173
  • plugin.xml.181
  • plugin.xml.183
  • plugin.xml.as31
  • plugin.xml.as32

KotlinConf 2018にて質問してみた

今年10月に開催されたKotlinConf 2018にて、JetBrainsの方に自動で生成するGradleタスクはありますか?と質問したところ、ないですよという回答を頂きました。それから引き続き愚直にコピペを繰り返していると、10/22にplugin.xmlがリファクタリングされて、plugin-common.xmlが生まれていたことに気づきました!

github.com

各plugin.xmlに以下のようにplugin-common.xmlをincludeすることで共通化されます。Gradleタスクを用いて自動で生成することしか考えていなかったので、なるほど!と勉強になりました! gist.github.com

さらにidea/src/META-INF以下で管理されていたplugin.xmlなどのリソース周りはidea/resources/META-INF以下に移動されていました。移行スクリプトってこう書くのねとコードを読むだけでも面白かったので興味のある方はぜひ! github.com

plugin-common.xmlが生まれてどうなったの?

github.com 上記は先月出したPRになりますが、plugin-common.xmlだけ設定していることがわかります。煩わしい作業が減ってちょっと楽になりましたね!

まとめ

簡単にではありますが、最近のKontribute事情を紹介しました。やっぱりわからないことはカジュアルに聞かないとダメだなと反省しつつ、引き続きKontributeを続けてアウトプットしていきます!

qiita.com

明日最終日のコネヒト Advent Calendar 2018はいとしょさんになります!お楽しみに!

プロダクション環境ですぐに活かせそうなAWS re:Invent 2018のSageMakerアップデート5選

f:id:tatsushim:20181223233656p:plain

時間のない方向けにざっくり言うと

  • 11月にSageMakerのお話で登壇したけど、12月のAWS re:Invent 2018でたくさんアップデートがあった
  • 既存機能の強化に留まらず、「データの準備」から「モデルの変換」もサポートされるようになり、SageMakerの守備範囲がグッと広がったアップデートだと感じた
  • この投稿はその中でもママリのプロダクションですぐに活用できそうな5つのアップデートについて内容をまとめた

この記事はコネヒトアドベントカレンダーの23日目の記事です。 qiita.com

こんにちは!

CTOの島田(@tatsushim)です。
先月11/1「AWS dev day」に登壇させていただきました。 そのときの資料はこちらです。

現場で用いたSageMakerについて発表させていただいたのですが、私の発表の翌月にre:Inventがあり、SageMakerも大きくアップデートがありました。
今回は、プロダクション環境ですぐに活かせそうなAWS re:Invent 2018のSageMakerアップデートを5つご紹介します。

5つのアップデート

1. データ準備サポートツールの追加( Amazon SageMaker Ground Truth )

  • 「この画像は犬の画像であるか、そうでないか」を分類するタスクがあるとします。これを機械学習によって分類するためには、まずはじめに「この画像は犬ある」というラベルが付いた画像と「この画像は犬ではない」とラベルが付いた画像が必要です。
  • こうした状況で、データ(この例では画像)に対してラベル付けを支援するのがAmazon SageMaker Ground Truthで、利用するメリットは下記の2つです。
    • ①: ラベリングツールがAmazon SageMaker Ground Truthで提供されるため、ラベルをつけるツールの購入・自作・管理が不要
    • ②: S3にラベル付けしたいデータをアップロードして、Amazon SageMaker Ground Truthでラベルをつけると結果がS3にあがるため、データの管理が明確
  • また、一部のデータを人の手でラベル付けした上で、その元データから自動で付与するような機能も存在しています。

2. Gitのインテグレーション

  • SageMaker上でGitリポジトリを登録しておけば、ノートブックインスタンス起動時にリポジトリがgit cloneされるようになりました。
  • 今まではコンソールからGitを利用するか、Jupyter Notebookで書いたコードを手動でGit管理する必要がありましたが、JupyterLabのGit extension設定がデフォルトでされる形になるので、視覚的な管理が可能になりました。

3. 学習・推論用コンテナのアップデート

  • TensorflowコンテナがPython3に対応しました。
    • (日本語処理をしてる側からすると嬉しいアップデート!)
  • scikit-learnのコンテナが追加されました。
    • ただしほとんどのアルゴリズムで分散学習に対応してないため、学習は単一インスタンスで行うことになると思います。

4. 学習ジョブモニタリングのサポート

  • 学習時のモデルの精度をグラフ化できるようになりました。
    • 正規表現でルールを書いて、標準出力から精度を抽出する必要があります。
  • ビルトインアルゴリズムの場合は最初から設定されているので正規表現は不要です。
  • 注意点としては、1分おきのメトリクスになっている点です。現状の仕様ではこれは1分未満はできないそうです。

5. 推論時にGPUの部分的な利用をサポート( Amazon Elastic Inference )

  • GPUリソースをEC2やSageMakerのインスタンスに一部割り当てることができるサービスのようです。
    • (最初何言ってるかわからなかった)
  • イメージとしては、PythonでボトルネックになってるところをCythonで書くみたいな感じで、コードとしてGPUを使用する箇所を明記することで、そこだけGPUを利用してくれるようです。
  • こちらにサンプルコードがありますので、イメージとしてはこちらをご覧になっていただくのが良いかと思います。
  • 画像分類の推論をリアルタイムで処理したいニーズには便利です。

終わりに

他にも強化学習、DeepRacer、AWS Marketplace for Machine Learningなど触れたい部分はたくさんあるのですが、今回は比較的すぐに実応用可能なアップデートに絞って内容を共有させていただきました。
機械学習の分野でますます活用が増えていくであろうSageMakerには今後も注目ですね。
以上、島田(@tatsushim)がお送りしました!
今年も、ここまで1日も切らさずにコネヒトアドベントカレンダーは続いております。ここまで継続してくれているメンバーに感謝です。
明日は富田(@tommykw)さんのターンです!

DockerとJavaScriptの付き合いかた

f:id:dachi023:20181220161955p:plain

こんにちは。エンジニアの安達 (@dachi_023) です。会社用アカウントとして @ry0_adachi を用意していましたが全然呟かなくなっちゃったので辞めました。複数アカウントの運用って面倒ですね...。はい、コネヒト Advent Calendar 2018 の20日目はDockerとJSです。

まえがき

DockerfileにJS (とかCSSとかHTMLとか) のビルド処理を書いてコンテナ立ち上げてブラウザで見えるところまでの話です。

本記事では最低限これができていればそんなに遅くならないよねってものをいくつか書いています。コードは GitHub に上がってますのでそちらを見ていただいてもOKです。このコードは create-react-app で生成したものをビルドしてコンテナ上のNginxで公開するという簡単なものですが、実際にママリのProduction環境に投入したものもJSに限って見てみればやってることはほとんど一緒になったので、この手の問題はあんまり難しく考えないほうがいいんだろうな、というのが現状の結論です。

今回はやってませんがSSRする場合は他にも考慮すべき点がありそうなのでもうちょい複雑かもなと思ってます。

Dockerfileを書くときに気にしてること

めちゃくちゃ普通なんですけど一応書いておきます。

  • キャッシュをなるべく使えるようにする
  • イメージのサイズを小さくする

JS起因でDockerのビルドが遅いのはだいたいインストールとビルドのせいなのでキャッシュが効くべき時に効くのが大事そうです。あとはイメージサイズを小さくするためにどのイメージを使うかとか、ビルドの過程で使ったツールなどをイメージに含まないかとか、そういうのを意識してます。

たとえば

上記は GitHub に上げたDockerファイルですがこの12行だけでも気にしてることを満たせているはずです。

  • yarn [install] でキャッシュを効かせたいので実行前にCOPYしてくるのは package.jsonyarn.lock だけにして、ビルド時に使うものはその後にCOPYしてくる
  • multi-stage build を使うことでnode_modulesやビルド前のファイルなどを最終イメージに残さない

実際にdocker buildした結果はこんな感じです。

キャッシュ無

$ time docker build -t dachi023/docker-create-react-app:test .
real    1m5.287s
user    0m0.375s
sys 0m0.249s

フルキャッシュ

real   0m2.172s
user    0m0.165s
sys 0m0.116s

一部キャッシュ (JSのコードを編集)

real   0m18.399s
user    0m0.229s
sys 0m0.147s

あとは $ docker run -itd --name docker-create-react-app -p 8080:80 dachi023/docker-create-react-app:test などとやると起動までちゃんとするのでサクッと何かを作りたい時などにご活用ください。

まとめ

キャッシュが効いててイメージが軽ければ開発時のストレス減りそうだね、というお話でした。アプリケーションが複雑になっても基本この2つがブレないようにしていればいいのではないかと思ってます。Dockerチューニングでやれることはまだあるかと思いますが、まずは効き目のある部分からやっていきましょう!

明日は nkuroda さんの引っ越し話です!技術が絡んでくるかもしれないし絡まないかもしれません、ご期待ください! 👋

How to Write Testable Code in Golang

f:id:itosho525:20180823212423p:plain

はじめに

本記事は コネヒト Advent Calendar 2018 の19日目のエントリーになります。

こんにちは!先日PHPカンファレンス(通称: ペチコン)でLT登壇させていただいた @itosho です。

ペチコンのことも書きたいのですが、ペチコンでもGoの話をしたので、今日はGoのテストを話をしたいと思います。具体的には、今年公開した gdp というCLIツールのテストを書く時に工夫したことを紹介します。

ちなみに、gdpがどういうツールなのかは以前 Go製のCLIツールを公開しましたという記事で説明しているので、もし興味がある方はこちらも読んでいただけると嬉しいです。

開発初期のコード

gdpにはリモートリポジトリに指定したtagが存在確認を行う処理があるのですが、当初はこんな感じのメソッドを用意していました。*1

呼び出される側

func IsExistTagInRemote(tag string) bool {
    out, err := exec.Command("git", "ls-remote", "--tags", "origin", tag).CombinedOutput()
    if err != nil {
        return false
    }

    if string(out) == "" {
        return false
    }

    return true
}

呼び出す側

func main() {
    err := Validate(tag) // tagは事前にコマンドライン引数から取得
}

func Validate(tag string) error {
    if !IsExistTagInRemote(tag) {
        return errors.New("tag is not exist in remote")
    }

    return nil
}

問題点と解決方法

このコードでも、やりたいことは実現出来ます。しかし、テストのことを考えるとこのコードには問題があります。何故なら、 Validate() メソッドが IsExistTagInRemote() メソッドに依存しているからです。IsExistTagInRemote() メソッドは外部(リモートリポジトリ)との連携があるため、Validate() メソッドのテストコードを書く際に IsExistTagInRemote() メソッドの結果を変更するのが難しく、結果としてテストしづらくなります。これがまだローカルのテストであればよいのですが、例えば、CIのテストとなると、場合によってはgitのセットアップから始める必要があり、非常に手間がかかります。

このような問題を解決する手段として、Goでは Interface を利用します。Validate() メソッドを IsExistTagInRemote() メソッドに依存させるのではなく、Interfaceに依存させます。そうすることで、モックが使えるようになるため、テスタビリティを向上させることが出来ます。

Interfaceを利用したコード

概念だけだと分かりづらいと思うので、実際のコードをご覧ください。

呼び出される側

type Git interface {
    IsExistTagInRemote(tag string) bool
}

type Cmd struct {
}

func (c *Cmd) IsExistTagInRemote(tag string) bool {
    out, err := exec.Command("git", "ls-remote", "--tags", "origin", tag).CombinedOutput()
    if err != nil {
        return false
    }

    if string(out) == "" {
        return false
    }

    return true
}

Git というInterfaceにIsExistTagInRemote() というメソッドを持たせるようにしました。GoではメソッドリストがInterface内のメソッドと一致する型はそのInterfaceを満たしていることになるため、 Cmd 型は Git Interfaceを実装していることになります。

呼び出す側

func main() {
    cli := &CLI{
        git: &Cmd{},
    }

    err := cli.Validate(tag) // tagは事前にコマンドライン引数から取得
}

type CLI struct {
    git Git
}

func (cli *CLI) Validate(tag string) error {
    if !cli.git.IsExistTagInRemote(tag) {
        return errors.New("tag is not exist in remote")
    }

    return nil
}

CLI 型にGit Interfaceを持たせて、Validate() メソッドはこのInterfaceを利用します。初期化時に Cmd型を与えているので、実際にはCmd型の IsExistTagInRemote() メソッドが実行されますが、Validate()メソッドはそれを知らない(あくまでIntefaceを利用している)ので、冒頭のコードにあった依存をなくすことが出来ました。

Interfaceを利用した際のテスト

では、これで本当にテスタブルになったのか確認していきましょう。Validate() メソッドのテストは以下のように書けます。

type MockCmd struct {
    Git
}

func (m *MockCmd) IsExistTagInRemote() bool {
    return false
}

func TestValidate_NotExistInRemote(t *testing.T) {
    cli := &CLI{
        git: &MockCmd{},
    }

    err := cli.Validate()

    expected := "tag is not exist in remote"
    if !strings.Contains(err.Error(), expected) {
        t.Errorf("Output=%q, Expected=%q", err.Error(), expected)
    }
}

ポイントは Git interfaceを満たすモックを準備していることです。Validate() メソッドのテストでは、このモックを利用することで、Gitのセットアップなしにテストを行うことが出来るようになります。冒頭のコードでは依存があったため、このようなモックを準備することが困難でした。しかし、Interfaceを利用することで、モックが使えるようになり、結果としてテストコードを書くことが出来るようになりました!

まとめ

ここまでInterfaceを利用したテストについて説明してきました。Interfaceを利用することで、依存性を分離することができ、コードをシンプルにすることが出来ました。コードをシンプルにすることのメリットはテストのしやすさだけではありません。例えば、Gitの操作をコマンドベースではなく、APIベースに変更した際、呼び出す側(今回の場合だと CLI 型)は具体的な実装に依存していないので、コードの変更を最小限にすることが出来ます。「Interfaceを制するものがGoを制す」とも言われているように、Interfaceは非常に便利な機能です。

とは言え、常にInterfaceを利用するべきかと言うとそうではないと思います。Interfaceを利用したコードはどうしてもコード量が多くなってしまいます。ですので、例えば、一回きりしか使わない使い捨てコードの場合は冒頭のように愚直に書いたがほうがよいと思います。gdpは社内で頻繁に利用されているツールであり、OSSとして公開しているので、可能な限り*2よいコードを目指しました。

ちなみに、このあたりの話は以前勉強会でも発表させていただいたことがあるので、興味がある方はこちらもご覧いただけるととっても嬉しいです。

speakerdeck.com

参考サイト

Goのこの手の話は五億番煎じくらいなので、たくさんお世話になりました。

明日の予告

明日のアドベントカレンダーは @ry0_adachi さんが登場するよ!

*1:なお、コードは必要な箇所のみ掲載しています。

*2:まだまだ改善の余地はあるので、Pull Requestをお待ちしております。

読書会でチームの改善が捗った話

f:id:yanamura:20181213002738p:plain:w320

本記事はコネヒト Advent Calendar 2018の14日目のエントリーです!

こんにちは!エンジニアの柳村です。

わたしの所属するチームではチームメンバー全員がスクラムマスターをできるようになろう!という目標でいろいろなことをやっています。その一環としてアジャイルコーチングという本の読書会を行っています。もともとは、単にスクラムやアジャイルのコーチングの知識をつけることが目的だったのですが、チームの開発プロセスの改善がどんどん進むという効果もあったのでそれについてご紹介します。

読書会の進め方

  • 事前準備
    • 各自が1章ずつ読み、その中で「学び」、「疑問」、「試したい」「うちのチームだとどうか」などといった視点で気になったことを付箋に書いてきます。
  • 読書会
    • 30分間
      • (2回目以降)前回のtryの確認
      • 全員の付箋をシェア
      • 疑問点について議論
      • tryすることを決める

付箋はこのように番号(章のどこの節か)書いておくと整理しやすかったです! f:id:yanamura:20181213002038p:plain:w300

ポイント

ポイントは、「tryすることを決める」ことと「前回のtryの確認」です。

本を読んで学びを得たり、疑問が解消するだけでも効果はあるとは思いますが、それを実行に移さないとせっかくの勉強もあまり意味がありません。読書会では最後に実行可能なtryをいくつか出すようにしています。「実行可能な」というところもポイントで、実行できるものでないと結局やらずに終わってしまい意味がありません。大きなtryが出た場合は小さくスライスするなどして対応しています。

また、tryを出してもやらずに忘れられてしまうということも起こります。それを防ぐために次の読書会の最初に前回のtryをやったかチェックする機会をつくっています。

結果

やってみた結果、例えばこのようなtryがあげられて実際にチームで実行しました。

try
5章:デイリースタンドアップ パーキングロットをつくろう
7章:前もって計画する バックアップボードの運用
8章:見える化する ブロッキングしてるタスクを見える化
13章:ふりかえり カラータイムライン、感情セイスモグラフを使う

やってみてうまくいったものもいかなかったものもありましたが、どちらにせよチームとしては学習することができましたし、これまでと比べて格段にチームボードやデイリースタンドアップ、振り返りなどの改善が進んだなと思っています。この読書会自体も最初からこのような形ではじまったわけではなく、改善を重ねてこうなりました。

もっと効率的だったり効果的なやり方もあると思いますので、おすすめの読書会の進め方があればぜひ教えてください!

Connehito Marché #4 ~サービスデザイン市~ を開催しました!

こんにちは。
サーバーサイドエンジニア兼チョコレート大臣*1の結城(@super_manner)です〜。 この記事はコネヒト Advent Calendar 2018の12日目の記事です。

今日は、先日行われたコネヒトマルシェという勉強会のレポートをしたいと思います!

f:id:supermanner:20181211181248j:plain

今回のマルシェのテーマ

今回は少しプログラミングから離れ「サービスデザイン」をテーマに開催いたしました。
昨今注目されている分野ということもあり、非常にたくさんの方にLTをしていただくことができ、 5分枠と10分枠でなんと合わせて10名という過去最高の盛り上がりをみせました🎉

LT内容

LT内容について簡単にご紹介させていただきますね。

デザインをしていく上で行動心理を学ぶと楽になる話

1番目の発表は, yusuke_hata_79さん。
ジャムの法則を例に、ユーザーが選択する際のストレスを減らしてあげることが大事ということを説明してくださいました。 選択をするUIにヒエラルキーをつけることで、申込みのコンバージョンがアップされたそうです!すごい!

※資料未公開

プロトタイピングtips!

note.mu

2番目の発表はRisaHiyamaさん。
Graffityの開発を行った際に得たプロトタイピングのtipsを共有してくださいました。女子大生を巻き込んだり、母校の文化祭に出店するなど行動力の化身ですね!確証をもってアプリの開発に臨むために大切なことを教えていただきました。

チームの成長の流れを掴む話

www.slideshare.net

3番目の発表はAyumuNishibeさん。
チームで成果を出すのはサービスデザインにおいて大切なこと。 定量データ・定性データ双方を読み解き、みんなで一丸となってデータと向き合うことの重要性を改めて考えさせられる発表でした!

リサーチあるあるから見る、UXリサーチの使いどころ

www.slideshare.net

4番目の発表はvegemakiさん。
バリバリの関西弁を操りながら、様々なUXリサーチ手法を可愛らしいイラストと共に解説してくださいました。UXリサーチをこれから始める方は、この資料を読むだけでも基本的なところが理解できます!

tock pop(トックポップ)ロゴ&サービス紹介動画のデザイン

www.slideshare.net

5番目の発表は稲毛誠さん。
ロゴ制作時の試行錯誤のプロセスをかみくだいで解説してくださいました。たくさんのアイデアに非デザイナーの私は圧倒されました...。また、動画制作についても制作の際の工夫を伝授してくれました!

ブランディングのためのUXライティング

speakerdeck.com

6番目の発表はRina_Satoさん。
「ことば」のデザインについてのお話。UXライティングというものをお恥ずかしながら存じ上げなかったのですが、日本語の与える印象一つでこんなにも変わってくるのか〜と驚きました。

チームの議論の土台をつくるためにデザイナーのわたしができること

7番目の発表はNatsukiWatanabeさん。
サービスデザインをするためには、チームで価値を作るということが重要。 デザイナーとしてのサービスデザインの関わり方を、ママリの開発事例をもとにお話しくださいました。日々のチーム開発でも活躍してくださっています!

国内最大のハンドメイドマーケットを支える同僚を支える技術。

8番目は鹿さん。
実は、画面の向こうのユーザーだけがユーザーじゃない。一緒に働いている同僚もユーザーなんだよ、ということを何度も繰り返されていた点が非常に印象に残りました! 鹿さんのプロダクト愛がたくさんつたわってくる発表でした!

※資料未公開

遊びこころの大切さ。あると楽しい一手間。

9番目の発表はkiyoeshiさん。
新規サービスのロゴデザインをする際に、「ひとさじの遊び」を加えることでたくさんの楽しいアイディアが生まれるという話でした。 こちらは、近日きよえ氏がnoteに公開するとのことなので、詳しくはそちらをご覧ください!

※資料未公開

幼稚園児はできてる超高速PDCA

www.slideshare.net

10番目の発表はmiteoさん。
マシュマロチャレンジの結果をもとに、以下にPDCAを早く回していくのが重要かということをお話しくださいました。机上の空論で固めがち….耳が大変痛く、気をつけようと思いました。

懇親会

サービスをこよなく愛するデザイナーさんたちが集まって和気あいあいと会話を楽しんでいただきました!
皆さんサービスとデザインが本当に大好きな方たちばかりで、時間ギリギリまでわいわいと盛り上がりを見せておりました😋

参加くださった皆様で写真をパチリ
参加くださった皆様で写真をパチリ

運営もがんばります

さて、コネヒトマルシェも早いもので4回目を無事に開催することができました。
運営一同、毎回振り返りをしてより良い運営を目指していますので、ぜひ興味のあるトピックの市にいらしてくださいね😊
ツイッターもよかったらフォローお願いします! twitter.com

明日は@ry0_adachiさんのエモい記事です!お楽しみに📖

*1:社員のお母様が買ってきてくださったアドベントチョコを渡す係

Kotlin1.3でリリースされたCoroutineを試してみた

2017年11月にAndroidエンジニアとしてjoinした関根です。 最近はiOSアプリの開発も担当しております。 こちらはコネヒト Advent Calendar 2018 - Qiitaの10日目の記事です。

さて、先日Kotlin1.3の正式版がリリースされました。

下記が主要なリリースの一覧となります

  • CoroutineのStable版
  • Kotlin/Native ベータ
  • マルチプラットフォームの拡充
  • Ktor 1.0 ベータ
    • ※現在はStable版となっています

かねてからマルチプラットフォーム化を進めること目標としてきたKotlinですが、今回のリリースで、その未来へ大きく前進したように思えます。 Kotlinからますます目が離せないですね!

さて、それではマルチプラットフォームのご紹介を・・・・といきたいところですが、今回はStableとなったCoroutineの紹介をさせていただこうと思います。 下記が今回のアジェンダです。

  • Coroutineとは何者か?
  • ChannelでのCoroutine間通信
  • Coroutineを利用したPub/Subの仕組み

Coroutineとは何者か?

今回、StableとなったCoroutineはKotlinでのnon-blockingプログラミングを支える機能です。kotlinxパッケージの配下に存在します。 利用目的としては ファイルI/OやネットワークI/Oなどの非同期処理が一番に浮かびます。

// Coroutineの生成
GlobalScope.launch(Dispatchers.Main) {
    progressDialog.isVisible = true
    // asyncでnon-blocking関数化,awaitで待ち受けの開始
    // ※ここで処理は停止されるが、ブロッキングは発生しない
    val bigData = async { loadBigData() }.await()
    progressDialog.isVisible = false
    displayData(bigData)
}

上記のようにblockingが発生しうる処理を直列的な記述を維持しながら、non-blockingな処理を実現できるのがCoroutineの強みだと思っています。また下記のようにすると並行処理もお手軽に実現できます。

// Coroutineの生成
GlobalScope.launch(Dispatchers.Main) {
    progressDialog.isVisible = true

    // asyncでnon-blocking関数化,awaitで待ち受けの開始
    // ※ここで処理は停止されるが、ブロッキングは発生しない
    val bigDataInt = async { loadBigDataInt() }
    val bigDataString = async { loadBigDataString() }
    val bigDataPair = Pair(loadBigDataInt.await(), loadBigDataString.await()) // ここで並行処理が実施される

    progressDialog.isVisible = false
    displayData(bigDataPair)
}

Coroutineが発表されて以降、RxJavaと比較されることが多いですが、筆者としては、どちらを利用することにメリットが大きいかを適材適所で判断し、利用していくのが良いと捉えており、 今回のような単純な非同期処理だけであれば、Coroutineでも十分に有用であると考えております。

ChannelでのCoroutine間通信

アプリ開発においては非同期処理は様々な理由で発生します。 例えば、先ほど例に上げたようなAPI通信に関わるネットワーク I/Oもその一例です。 他にはバックグラウンド処理との同期をしたい場合や、OSからブロードキャストされてきたメッセージングのハンドリングなど。 そのようなユースケースの場合、異なるライフサイクルで存在するコンポーネント間での、メッセージ授受の仕組みが肝要になってきます。この仕組みはCoroutineのChannelという機能を利用することで実現が可能です。

// 送受信用のChannelを生成しておく
val channel = Channel<Int>()

GlobalScope.launch { // 送信側のCoroutine
    for (x in 1..5) channel.send(x * x)
}

GlobalScope.launch { // 受信側のCoroutine
    repeat(5) { println(channel.receive()) } // 5つデータを取得して表示する
}

このようにChannelはCoroutine間で値を安全に授受する仕組みです。 本例以外にもChannelを利用すると、パイプライン処理やFan-in/Fan-outパターンの実装を、楽に記述することができるようになります。こちらはKotlinのサンプルでも紹介されていますので、詳しくはそちらをお読みください。 https://kotlinlang.org/docs/reference/coroutines/channels.html#channels-experimental

なお現時点で、Channelはexperimentalとなっており、今後の開発状況によっては破壊的変更が加わる可能性が残っています。 十分ご検討の上ご利用ください!

Coroutineを利用したPub/Subの仕組み

さて、今回はChannelを利用してPub/Subの仕組みを試してみましたので紹介しようと思います。 ソースコードの全体像は下記です。

class PubSub<T> {
  // 1. Pub/Sub用のChannelを生成
    val bus = BroadcastChannel<T>(1)

    // 2. Channelへの送信
    fun send(item: T) = bus.sendBlocking(item)  

    // 3. CoroutineScopeを受け取りsubscribeを開始する
    fun subscribe(scope: CoroutineScope, onConsume: (T) -> Unit) {  
        scope.async(Dispatchers.IO) { // 4. IOスレッドを利用する指定
            Timber.d("async:"+Thread.currentThread().name)
            bus.openSubscription().consumeEach { // 5. 受信の開始
                onConsume(it)
            }
        }
    }
}
・・・

// 受信
val pubSub = PubSub<Int>()
pubSub.subscribe(this) {
    launch(Dispatchers.Main) { // 6. UIスレッドで実行
      // 何かUIに関わる処理
    }
}

// 送信
pubSub.send(1)

全体的な流れを見てみると以下のようになります。

1. Pub/Sub用のChannelを生成

BroadcastChannelは1対多でのデータ授受を目的としたChannelです。 EventBusのように一つのEventを複数の画面やクラスから購読する際に利用します。

2. Channelへの送信

今回の例ではsendBlockingで値を送信していますが、 呼び出し元がCoroutineの場合はsendメソッドを利用しましょう。

SendChannel.send - kotlinx-coroutines-core

3. CoroutineScopeを受け取りsubscribeを開始する

CoroutineScopeはCoroutine自身とその子Coroutineのスコープを決めるものです。 スコープを決めることにより、呼び出し元のLyfecycleに応じて中断処理を行うことが可能となります。 今回のサンプルではActivity単位のスコープとすることを想定しています。

4. IOスレッドを利用する指定

受信待ち受け処理は、I/Oスレッドで走らせています。

5. 受信の開始

openSubscriptionを呼び出すことにより、受信用のChannelが新たに生成されます。 これにより1対多のメッセージ授受を実現できます。 1対1で事足りる場合には通常のChannelを利用すると良いと思われます。

6. UIスレッドで実行

メッセージ受信後の処理をUIスレッドで行うように指定しています。 ここをDispatchers.IOのように指定するとワーカースレッドで処理が行われます。

実装は以上となります。 kotlinとkotlinxのパッケージのみで、Pub/Subが実現できると夢が広がりますね。

終わりに

今回はCoroutineとChannelについての紹介とそれらを利用したPub/Sub実装の紹介をさせて頂きました。 弊社ではRxJavaを利用した実装が多く存在するのですが、Coroutineで代用できる部分がないか、探って行きたいと思います! CoroutineとChannelの良き実装アプローチがあれば、教えてください!!

それでは!!