Medley Developer Blog

株式会社メドレーのエンジニア・デザイナーによるブログです

オンライン診療アプリ「CLINICS」の開発で重視している3つの習慣

こんにちは、開発本部の横尾です。

これまで介護施設の口コミサイト「介護のほんね」の立ち上げや医療介護の人材採用システム「ジョブメドレー」のディレクター・コンテンツ編集などを経験し、現在はオンライン診療アプリ「CLINICS(クリニクス)」のディレクターをしています。 

 CLINICSでは、慎重さとスピード感を両立して開発できるよう、3つの習慣を取り入れています。半年ほど実施してみて手法としてまとまってきましたので、ブログでも紹介させていただきます。 

背景

CLINICSは、スマートフォンやPCからビデオチャットでかかりつけ医の診察を受けられるオンライン診療アプリです。医療機関向けにはブラウザで予約管理や問診、診察、会計を行うことができる機能も提供しています。CLINICSを開発する際は、医療に関するプロダクトゆえに特に意識しなければいけない点がいくつかあります。

一つは人の命に関わるということ。オンライン診療は症状の落ち着いている方が対象となりますが、少しの使いにくさやミスも誰かの生命・生活に何らか影響するかもしれない緊張感が常にあります。

もう一つは、老若男女幅広く使われていること。病院にかかったことのない人はほとんどいないように、さまざまな人がCLINICSを利用しているので、どんな人でも不安なくスムーズに使えることが重要になってきます。

このような背景から、CLINICSでは慎重にゴールを設定し、丁寧に検証しながら開発を進める必要があります。一方で、あまりに慎重・丁寧すぎても何もできなかったり迷走したりしてスピード感を失いかねません。そこで、両者のバランスをうまく取り開発を推進するために次の3つを行っています。

CLINICS開発をうまく進める3つの習慣

1. プロダクトプリンシプルを共有する

プロダクトプリンシプルは、サービスとしてのやること・やらないことを簡潔に言語化したものです。

例えばCLINICSのプロダクトプリンシプルには「1UUの重み、活用率100%を目指すこだわり:一人の人の命を預かっているという使命感を持ち、一人でも多くの人が使え、今まで使えていた人が使えなくなることが一人でもないようにする」というフレーズがあります。

前述の通り、CLINICSは人の命に関わるプロダクトであり老若男女幅広く使われています。そのためあえてターゲットを絞らず、すべての人が使えるプロダクトを目指していることをこのフレーズで共有しています。これにより、施策検討段階では一部の人にとってとても使いやすいが使いこなせない人がいるUIと、ほぼすべての人がそこそこ使えるUIがあった時、私たちは迷わず後者を選択でき、すぐに実装に取り掛かることができます。

プロダクトプリンシプルの作成にあたっては、日頃寄せられる患者の声・医療機関の声をよく聞き、医療の成り立ちや医療業界の構造なども深いところまで学習するようにしました。患者にとって、医療機関にとって、そして社会にとってのあるべきプロダクト像を理念として映し出すようにしています。

2. 年間・四半期ごとのロードマップを策定する

ロードマップは、いつ頃どんな課題に取り組むのかを概ね年間・四半期ごとに定めたものです。こちらは比較的多くの開発現場で取り入れられている方法ではないでしょうか。

ロードマップをチームで意識するようにすると、先々に開発・リリースする機能を一人ひとりが理解でき、先回りして技術調査を進めたり関連する施策の効果分析の優先度を上げたりといったことができます。また、営業チームなど社内のほかの部署から開発提案があった際にはロードマップを見ながら今後の見通しを案内できるようになり、 他チームのメンバーからの信頼を得ながら開発に専念することができます。

ロードマップを策定する際は、プロダクトプリンシプルを開発内容として落とし込みつつ、事業として伸ばすべき数値や直近で解決すべきお問い合わせなども考慮するようにしています。また、四半期ごとにKPT法を使ってロードマップを振り返り、開発の期間や体制を見直すようにしています。

3. 週次で計画し、日次で進捗を追う

週次の計画と日次の進捗確認には、スタンディングミーティングを行っています。

CLINICSではGitHubのプロジェクト機能を使ってカンバンを作成しています。任意のタイミングでロードマップで挙げた課題をだいたい1週間程度で区切りのつくサイズのissueに落とし込み、カンバンのbacklogにセットします。毎週月曜日の午前中に週次定例を行い、その週に着手するissueをbacklogからin progressに移します。火曜以降は毎日issueごとに進捗を確認し、終わったものをdoneに移していきます。

このように週次・日次で計画と進捗確認を細かく繰り返すことで、一つひとつのissueをスピーディに進めるリズムができます。ミーティング時点で一つのissueも進んでいないということがないようにしようという意識が働き、個々人のパフォーマンスもアップします。

まとめ

今回は、開発推進のためにCLINICSで取り入れている習慣をご紹介しました。 

この3つの習慣は、どれか1つのみを行うのでもだめで、3つセットで実施することが今のCLINICS開発のカギになっています。プロダクトプリンシプルだけでは理想論のように思えたことも、ロードマップや計画になるとより具体的になって着手しやすくなり、また、計画で示しきれない改善の方向性はプロダクトプリンシプルに立ち返ることでお互いの認識をそろえやすくなります。

そうすれば不必要なルールの制定や社内コミュニケーションから解放され、日々やるべきことに集中でき、結果的により早くより優れたプロダクトをユーザーに届けることができると考えています。

メドレーでは、こうした様々な手法を取り入れながら新しい医療体験を提供するプロダクトを、一緒に創るディレクターやエンジニア、デザイナーを募集しています。

ご興味ある方は、まずはお気軽に話を聞きにお越しください!

www.wantedly.com

www.medley.jp

SkyWay UG Tokyo #1 に参加してきました

こんにちは、開発本部 平木です。2017/12/04(月)に SkyWay UG Tokyo #1 という勉強会がありました。弊社から開発本部の宍戸がセッション枠で発表させていただきましたので、イベントレポートをお送りします。

はじめに

弊社の運営サービスの1つである オンライン診察アプリ「CLINICS」 では運用初期の頃から医療機関と患者さんとの診察にSkyWayを使ったビデオチャットを導入しており、現在もWeb / iOS / Androidの各プラットフォームでSkyWayが稼動しています。 そのようなご縁もあり今回の勉強会では、CLINICSでSkyWayをどのように利用しているかをお話させていただきました。

f:id:dev-medley:20171221191518j:plain

セッション

それでは、各セッションの流れなどをご紹介していきたいと思います。

はじめに

勉強会の冒頭、UGのキックオフと参加者の自己紹介タイムがありました。

UGキックオフ

NTTコミュニケーションズの仲さんから、SkyWay UGをなぜ作るのか?というキックオフから会がスタートしました。

SkyWayの大きなミッションとして リアルタイムコミュニケーションを身近にする というものがあり、そのためには 開発者にサービス自体がフレンドリーであるべき であるという理念があるそうです。

開発者フレンドリーであるための一環として 開発者コミュニティの運用をしていく ということになりこの SkyWay UG Tokyoが発足されたという経緯が説明されました。

確かにこのような形で使っているサービスのUGが開催されると、サービスの開発者の方々や参加している他のユーザさん達と気軽に交流できるなというのを今回参加して実感しました。

SkyWay UGは東京だけではなく 関西福岡 でも続々と発足するとのことで、他の開発者サービスと同じように盛り上がっていくのではないでしょうか。

参加者自己紹介

キックオフが終わったところで、参加者の自己紹介タイムとなりました。

30人近くの参加者が順々に自己紹介していくという形だったのですが、個人的には珍しいなと思うのと同時に、懇親会で何をやっている方なのか?というのが分かりつつ話をすることができたという良さがありました。(参加者として自己紹介する勉強会は初めてでした!)

参加者の感想としては

  • 自分達と同じWeb業界の方がメイン…というわけでもなく、組み込みなどを仕事にしている方なども1/3くらいの割合でいたのが印象的だった
  • 実際にサービスやプロダクトにSkyWayを使っているという参加者の方が多かった
    • とはいえ使ってないが、興味があって参加したという方も
  • 中には弊社のCLINICSに興味があると言っていただけたりもした

という感じでした。ハードウェアにSkyWayを組み込んで使うということをやっている方も多く、改めて幅広い開発者に受け入れられてるプラットフォームだと感じました。

SkypeからSkyWayへ

最初のセッションは会場を提供されていた レアジョブ のInoueさんからの発表でした。

Inoueさんは現在は主にフロントエンド開発をされているそうで、このセッションもJavaScript SDKの知見がいろいろされた有意義なものでした。

レアジョブさんでは創業時からSkypeを使ってサービスを運用していたそうなのですが、サードパーティに依存しすぎるとビジネスとして危ういということで、SkyWayへの切り換えをしていっているそうです。

SkyWayは9月からトライアル版から正式版を提供されていますが、まだなかなか情報が少ないということで、新JavaScript SDKで開発していくなかで得たTipsが発表されました。

実サービスを運用してる方ならではの、 ビデオ・マイクのミュートビデオチャット中のページ離脱防止策 などの対応などは実用的で役立つものでした。

Inoueさんもこの後のLTで発表された仲さんもおっしゃっていましたが、SkyWayというかWebRTCのデバッグには chrome://webrtc-internls が重要だと改めて思った次第です。

オンライン診察アプリの作り方

speakerdeck.com

弊社の宍戸の発表です。

まずはメドレーと運営しているプロダクト紹介から始まり、そもそもCLINICSで実現したかったビデオチャットはどのような条件があったか?SkyWayを選んだ理由、SkyWayとFirebase Realtime Databaseを併用したオンライン診察の実装内容などをご紹介しました。

駆け足でのご紹介にはなりましたが、実際に商用サービスでの事例ということもあり、おかげさまで好評でした。

スライド中でも紹介したSkyWayとFirebaseを使った通話制御は他のプロダクトでも使ってることが多いらしく、相性的にもお勧めなのではないでしょうか。

LT

セッション枠が終わりLTとなったのですが、このLTがSkyWayの中の方達が担当するという豪華なものでした。

WebRTCで統計情報を収集する

qiita.com

NTTコミュニケーションズ仲さんのLTです。 chrome://webrtc-internls で表示されるような統計情報をJavaScript SDKを使って表示することができるという解説とデモでした。

WebRTCのデバッグや、ユーザからの問い合わせなどにすぐに使えそうな実践的な内容でした。が、現バージョンでは残念ながら、Android / iOSではこの統計情報が取れない…とのことだったので、将来的には取れるようになると弊社でもデバックが捗るので首を長くして待ちたいと思います。

紹介されているcallstats.ioは知らなかったので、参考になりました。行く行くは使ってみたいですね。

reCAPTCHAとAPI認証で手軽に利用できて不正利用に強いアプリを作ろう

www.slideshare.net

NTTコミュニケーションズ大津谷さんのLTです。SkyWayでは2018/9月にAPI認証機能がリリースされたため、この機能とreCAPTCHAを使いAPIキーの不正利用を防ぎながらログインが不要なサービスを作れるというものでした。

reCAPTCHAはこういう風に使えるんだ!という驚きと同時にプロトタイプなどをサッと作ったりするの大変よさそうな機能だと感じました。

まとめ

以上、SkyWay UG Tokyo #1 のレポートでした。SkyWayを現在使っている・使いたいというような開発者には気軽に情報交換もでき大変よいイベントでした。また、SkyWay開発陣がユーザからの意見などをすぐに吸い上げてくれるのが改めて分かりました。

次回の東京での開催はまだ未定なようですが、ご興味ある方はぜひいかがでしょうか。

弊社では、SkyWay(WebRTC)を使った開発に興味があるエンジニアさん、ビデオチャットのUIを良くしたい!と思っているデザイナーさんの募集をしています。ご応募お待ちしています。

www.medley.jp

www.wantedly.com

www.wantedly.com

PrometheusでRailsアプリケーションのパフォーマンスが簡単に見れますという発表をしました

こんにちは。エンジニアの宍戸です。先日、社内勉強会「TechLunch」にてPrometheusを使ったアプリケーションのモニタリングについて発表する機会がありましたので、その内容を少しご紹介できればと思います。

Prometheus

先日2.0がリリースされたばかりの統合監視ツールです。監視対象からメトリクスを取得し、その情報をGrafanaと連携してグラフィカルに表示したり、アラートマネージャ機能を利用してメトリクスの状況に応じて通知を作成することなどが可能です。また、PrometheusはPull型のメトリクス収集形式をとっていますが、サービスディスカバリ用の設定を入れることで、対象のサーバ群の増減の度に作業すること無く、監視対象を管理することができるなど、現在のアプリケーション稼働環境にあった運用が可能なものとなっています。(Prometheus自体に関する情報はかなり多くの方が記事を書かれているので、詳細はそちらにお任せしたいと思います🙏)

Prometheusは前職時代にもお世話になっており、そのときにはGoで書かれたサーバのモニタリングに利用していました。当時から便利に利用していたこともあり、弊社のプロダクトはRailsを利用しているため、今回はPrometheusを利用して、Railsアプリケーション自体のパフォーマンスに関するメトリクスを取得してみました。

アプリケーションパフォーマンスのモニタリング

アプリケーション自身のパフォーマンスの情報を定常的にモニタリングしておく事は、潜在的ボトルネックの発見や、アクセス状況の変化、リリース単位でのパフォーマンスの変化(改善/悪化)を早期に発見することに繋がると考えています。

アプリケーションのモニタリングは有料ツールを含めてかなり多くの選択肢があります。NewRelicのAPMは代表的なツールの一つで、実は現時点ではこちらを利用してモニタリングを行っています。またその他様々なツールでも同じようなインサイトを得ることができます。アプリケーションパフォーマンスのモニタリング(マネジメント)ツールについてはこちらに良くまとまっていましたので、合わせて見ていただくと良いかもしれません。

メトリクスについて

Prometheusも様々な機能がありますが、監視対象となるサーバはExporterと表現されます。このExporterに対して、Prometheus Serverがデータを取りに来る格好です。

https://github.com/prometheus/client_rubyというライブラリが公式に提供されています。こちらにあるPrometheus::Middleware::CollectorPrometheus::Middleware::Exporter という2つのRack middlewareを利用することで設定に数行追加するだけで以下に記載した情報をPrometheus Serverから利用する準備が整います。

key 意味
http_server_requests_total アプリケーションの処理したリクエスト総数
http_server_request_duration_seconds_bucket あるエンドポイントの要求処理時間のヒストグラム
http_server_request_duration_seconds_sum 上記の合計値
http_server_request_duration_seconds_count あるエンドポイントへのリクエストの総数
http_server_exceptions_total アプリケーションで発生した例外の合計数

ここで出て来るバケットというのは特定の範囲のデータを格納する領域のようなものです。 ヒストグラムの計算などの際に利用します。

これらを用いて、

  • 直近10分のエンドポイント毎のレイテンシ
    • rate(http_server_request_duration_seconds_sum{path=~"/api.*"}[5m]) / rate(http_server_request_duration_seconds_count{path=~"/api.*"}[5m])
  • APIのエンドポイント毎のステータスコードが200以外となったレスポンスの数
    • sum(http_server_requests_total{job="rack-example", code!~"^2..$", path=~"/api.*"} offset 10m ) by (path)

などをPromQLというPrometheusの独自クエリ言語を用いて集計することができます。デフォルトで提供されるダッシュボードで即座に確認できるのでとても便利です。

実際に発表した際のスライドはこちら。 speakerdeck.com

まとめ

今回はPrometheusを利用したRailsアプリケーションのモニタリングについてご紹介しました。

Prometheusがどうという話はあまりできませんでしたが、ここまで手軽に準備できるもんかというのは正直びっくりしました。他のツールを利用していない状態でアプリケーションの運用をしている場合には、特にRails等であれば簡単に監視を始められるので、導入を検討する価値はあるのではないかと思いました。

現在は比較的安定してサーバを稼働させることが出来ていると思いますが、日頃からこういったメトリクスを見つつ、今後の安定運用につなげていきたいと思います。

フロントエンドエンジニアがIonicを触ってみた〜メドレーTechLunch〜

こんにちは。開発本部の大岡です。オンライン診療アプリ「CLINICS」の開発を担当しているエンジニアです。2017年6月にメドレーに転職してきて初めて気づきましたが、僕は人見知りでした。

メドレーでは、定期的にTechLunchという社内勉強会を実施しています。今回僕が担当になりましたので、その時の内容をご紹介させていただければと思います。テーマは「フロントエンドエンジニアがIonicを触ってみた」です。

色々な事情を考えずに好き放題言っています。個人的にウェブアプリが好きということもありウェブアプリよりの偏ったことを書いていると思います。ご了承ください。

なぜIonicを触ろうと思ったか

ウェブアプリ・ネイティブアプリ(このエントリーではiOS/Android各プラットフォーム固有の言語を使って開発したものを指しています)それぞれにメリットデメリットはあると思いますが、ゲームのようなグラフィックごりごりのものでなければウェブアプリで十分に感じています。

最近では、両方に展開しているもののネイティブアプリにコストを割いてしまっているのか、モバイルのウェブアプリのUIが本当にひどかったり最適化されていないと感じる例があります。最適化もしてないし、導線はネイティブアプリに向けてるのにネイティブアプリの方が使われてるじゃん!って言われてもウェブアプリの気持ちになると「そりゃそうでしょ。。。」って感じです笑

とはいえ、いくらウェブアプリが好きでもネイティブアプリを作らざるをえないとなった場合にiOS/Androidそれぞれ作ることが適切なのかなと思ってしまいます。そこでクロスプラットフォーム開発できるものを探していると、自分のスキルセットに合いそうなものがいくつかありました。その中で今回は、Ionicを触ってみることにしました。

今回TechLunchで発表したスライドは以下です。

speakerdeck.com

スライドと重複してる箇所もありますが、テキストでも解説してみます。ご興味がありましたら以下もどうぞ。

ウェブアプリとネイティブアプリ

ウェブアプリとネイティブアプリはよく比較されますが、その違いは以下のような感じかなと思います。

項目 ウェブアプリ ネイティブアプリ
動作速度 遅め 早め
バイスの機能 使えるものもある 利用可
開発コスト 普通 多め
審査 無し 有り
インストール 不要 必要

動作速度は遅めとはいえ、ゲームではなくECサイトのようなものであれば、そこまで気にするほどではない思います(作りにもよると思いますが)。デバイスの機能に関してはChromeなら割と使えます。開発コストはワンソースでいけるのでネイティブアプリよりは優れているかなと思います。

あとは何と言っても、ブラウザとURLさえあればどこからでも使えるっていうのがメリットです。最近ですとウェブアプリをネイティブアプリっぽく動かすProgressive Web Application(以下PWA)というワードもよく目にするようになりました。PWAは動作速度の面やプッシュ通知・オフラインでも利用できたりとウェブアプリの弱点を補ってくれるのですが、PWAに必要な技術であるService Workerの実装がされていなかったりと環境により十分に恩恵を受けることができない場合があります。

個人的に以下のようなパターンの場合

  • ネイティブの実装が必要
  • 重要だけど機能に更新がはいることがほぼない
  • アプリを使う上で毎回必要なわけではない

アプリによっては、この部分だけアプリに切り出して、ウェブアプリで必要になったら呼び出すとかでもいいのかなと思います。

上記のようなことをネイティブアプリとして実現できるものにApach Cordova(以下Cordova)があります。ウェブアプリはWebView(ネイティブアプリ内でウェブページを表示する部品みたいなもの)に表示し、ネイティブ機能はプラグインとして提供されているのでそれをJavaScriptから呼び出すことが可能です。

Ionicとは

ウェブの技術を用いてネイティブアプリの開発を可能にするフレームワークです。 主な特徴は以下です。

実際に触ってみた

公式のGet startedにもあるようにブラウザでアプリを表示するまでは3ステップです。 ※Node.jsがインストールされていることが前提

# 1. ionicとcordovaのインストール(この時のIonic CLIは3.18.0)
 $ npm install -g cordova ionic

# 2. アプリケーションの作成
$ ionic start [アプリ名] [テンプレート]

# 3. アプリケーション起動
$ cd [アプリ名]
$ ionic serve

テンプレートもよく見る形のものは用意されているのでこだわらなければサクッとそれっぽいものが作れます。下の画像はtabsテンプレートを使用しました。

f:id:medley_inc:20171122191851g:plain

Android/iOSの開発環境が整った状態ですと以下のコマンドで、エミュレータを起動しネイティブアプリとしてインストールすることができます。

# 1. 対象のOSを追加
$ ionic cordova platform add [ios/android]

# 2. エミュレータ起動かつインストール
$ ionic cordova run [ios/android]

# 2.5 ウェブアセット更新と連動してアプリ更新させる場合
$ ionic cordova run [ios/android] --livereload

ここまでは本当に簡単です。起動時に--livereloadオプションをつけるとウェブのアセットが更新されるとアプリ内の画面も更新されるので便利です。ネイティブの機能を利用したくなった場合も多くのプラグインが提供されているのでウェブの知識だけでもある程度のアプリは作れると思います。

便利そうだし簡単にアプリ作れそう!となりますが、やりたいことがプラグインで提供されていなかった場合は自分で作成しないといけません。プラグインの作成はiOS/Androidそれぞれで作らないといけないため知識もそれぞれ必要です。そして、このプラグインを作るのがウェブの知識だけだと割と苦戦します。

SkyWayを利用してビデオチャットアプリを作ってみた

SkyWayとはNTTコミュニケーションズが提供しているWebRTCを利用したビデオチャット等ができるサービスです。今回このSkyWayを利用して、Androidのみですがビデオチャットアプリを作ってみました。

Androidプラグイン作成

SkyWayのプラグインがなかったのでプラグインを作成します。画面のあるプラグインの作成に結構はまりました(基本的なプラグイン作成の説明は、検索すればすぐに見つかると思うので省略させていただきます)。解決して思うようには動いたのですが、これでいいのかわかりません。いい感じの解決法があったら教えてください。

今回はSkyWayのAndroidサンプルコードに少し手を入れて利用させて頂きプラグインを作成してみました。ざっくりディレクトリ構成は以下のようになりました。

    plugin-skyway
    ├── platform
    │   └── android
    │       └── AndroidStudioで作成したプロジェクト(SkyWayサンプルソース)
    └── plugin
        ├── package.json
        ├── plugin.xml
        ├── src
        │   ├── android
        │   │   ├── cordova-plugin-skyway.gradle
        │   │   ├── MainActivity.java
        │   │   ├── PeerListDialogFragment.java
        │   │   ├── SkyWay.java        
        │   │   ├── layout
        │   │   │   ├── activity_main.xml
        │   │   │   └── fragment_dialog_peerlist.xml
        │   │   └── libs
        │   │       └── skyway.aar
        │   └── ios
        └── www
            └── SkyWay.js

plugin-skyway/platformに各OSのプラグイン用プロジェクトがある感じです。plugin-skyway/pluginがIonicのプロジェクトにインストールされるディレクトリです。

はじめは、plugin-skyway/plugin/src/androidにAndroidStudioで作成したプロジェクトを置いてました。しかし、Ionicのプロジェクトに作成したプラグインをスンストールすると不要なものまで含まれてしまったので、plugin-skyway/platform/androidにプロジェクトを作成し必要なファイルのみをplugin-skyway/plugin/src/androidにコピーすることにしました。

AndroidStudioでプラグインを開発する際は、一度IonicのプロジェクトをAndroidでビルドし、Ionicプロジェクト内のplatforms/android/CordovaLib/build/outputs/aar/CordovaLib-debug.aarプラグインのプロジェクトで取り込まないといけないようです。

いざIonicプロジェクトにインストールしてビルドするとConstraintLayoutが無いとかSkyWayのSDKのバージョンがどうとか怒られますが、Androidの開発の仕組みが理解できていなかったので下記の「パッケージRは存在しません」というエラーに時間を取られました。

    .../MainActivity.java:258: エラー: パッケージRは存在しません
                    Canvas canvas = (Canvas) findViewById(R.id.svLocalView);

のようにR.javaがないと怒られます。 AndroidStudioからGUIでポチポチと画面を作るとR.javaというのができいい感じに解決してくれるようなのですが、今回のようにxmlのみを移動させるだけだとR.javaが作られません。

そこでR.id.svLocalViewのようなR.の箇所を下記のように置き換えて、やっとビルドが通るようになりました。

getResources().getIdentifier("svLocalView", "id", getPackageName())

最終的に以下のようなものができました。 SkyWayボタンをタップするとネイティブの画面が立ち上がり、AndroidのSkyWay SDKを利用してテレビ電話をすることができます。 クローズボタンをタップするとネイティブ画面が終了し、WebView部分に戻ってきます。実際触ってみた感じフルネイティブアプリと違和感なく感じます。

f:id:medley_inc:20171122192030g:plain

※アニメーションGIFの容量が重くなったので相手側でのコード入力中の空白時間を削ったり編集した影響で左下の緑と赤の画面が飛んだりしてますが、実際は滑らかです

まとめ

今回触ってみたというより指先が触れた程度ですが、全然Ionicでもいけそうな雰囲気を感じました。普通に触ってる分にはネイティブ部分なのかウェブ部分なのかわからなかったです。ただし、銀の弾丸ではないので、実際に開発に導入する場合はメリット・デメリットをちゃんと把握する必要があります。 ウェブアプリ寄りのことを言ってはきたものの、結局はウェブだろうがネイティブだろうがどんなツールを使おうが、使ってくれる人が求めるものを提供できればいいと思います。今後も何かあったときの引き出しのために色々なツールなど触っていこうと思います。

お知らせ

12/6(水)、こんなイベント(というか飲み会)をやります。 今回のブログの話が詳しく聞きたい、医療ヘルスケア領域の開発ってどんな感じだろう、社会貢献性の高いプロダクトに関わりたい、など思っているエンジニア・デザイナーの方、ビール片手に開発の話で盛り上がりませんか?

www.wantedly.com

その日は予定が入っているんだけど話を聞きたいという方は、こちらの「話を聞きたい」ボタンからどうぞ。

www.wantedly.com

提供価値によって異なるデザインプロセス

最近PS4グランツーリスモスポーツをやり始めて、自宅のネット環境の遅さに気づいたデザイナーのマエダです。前回はDLSについてご紹介させていただきましたが、今回はメドレーに入社して感じた「デザインプロセスの違い」について自分なりにまとめてみました。

あとで読みたい人向けに、エレベーターピッチ風にまとめると、

[ CLINICS ] というサービスは
[ 患者と医療機関向け ] それぞれサービスを提供しているが
[ 提供価値の違い ] によって
[ デザインの役割が異なる ] ことに気づいた

特に [ 医療機関向け ] は
[ UIが重要 ] となり
[ 伝えることを目的としたWebサイト ] とは違って
[ UIデザインの良し悪しがプロダクト全体の品質に関わる ] ため
[ 事業や技術を理解 ] した[ デザインオリエンテッド ] が求められる

というような内容です。

ユーザーによって異なる提供価値

メドレーに入社してから、オンライン診療アプリ「CLINICS(クリニクス)」というサービスのデザインを主に担当しているのですが、患者と医療機関側で提供しているプロダクトの内容が異なります。

f:id:medley_inc:20171116131612p:plain

  • オンライン診療の内容を伝え、利用を促すためのWebサイト(患者・医療機関双方)
  • 患者がオンラインで通院を行うためのアプリ
  • 医療機関側がオンラインで遠隔診療を行うためのシステム

サービスの入り口となるWebサイト

Webページの役割としては、オンライン診療というものがどういったもので、CLINICSを利用するとどういう課題解決につながるのか、というサービスの特徴を患者と医療機関双方に「伝えるためのデザイン」が必要となります。

デザインするにあたって注力すべきポイントは、装飾やイメージ画像などシンボリックなデザインとストーリー性のある導線設計をすることで、視覚的に特徴を伝わりやすくし、またコンバージョンポイントへ誘導するため、ボタン等は目立たせるなど、適切に情報を伝え、行動を促すためのデザインが重要になります。

患者がオンライン診療を行うためのアプリ

アプリは患者がオンライン診療をするための「病院検索」→「予約」→「問診」→「診察」→「決済」の機能をシームレスに繋げるためストレスのかからないユーザー体験を提供することが重要です。

そもそもオンライン診療のメリットとして、待ち時間の軽減や、落ち着いた環境で診察ができるなどが挙げられるため、そこに至るまでのユーザー体験を台無しにしてしまうUIでは元も子もなくなってしまいます。

アプリでは「伝えるデザイン」よりも「機能的なデザイン」が必要になりますが、ユーザーの行動を途切れさせないよう、不必要な要素や導線を極力排除して、非常にシンプルなUI設計をおこなっており、機能自体を主張しないデザインを心がけています。前回このブログでもとりあげたDLSも、そういった設計思想のもと開発を進めていたからこそ実現できたと思っています。

f:id:dev-medley:20171117113501p:plain

医療機関向けの遠隔診療システム

医療機関側に提供しているシステムは、オンライン診療を行うためのツールです。 Webサイトのように「伝えるデザイン」やコンバージョン重視ではなく、「より機能的に使いやすいデザイン」が重要になります。 このような画面をBootstrapのようなUIテンプレートそのままの見た目で構築してしまうと、技術的に素晴らしいものができたとしても、使い勝手が悪く貧弱な機能と見られかねないため、デザイナーが管理画面のUI設計に携わることは、ビジネス的にも非常に重要な要素です。

特にCLINICSの医療機関向けのシステムは、実際にオンラインで患者の診察を行うツールのため、医療機関側が診療行為をつつがなく終えられるようなUI設計が重要です。

MVP的な手法で、とりあえずリリースして検証を重ねていくというスタンスがとれないため、リリースするまでに機能的に不備がないか、医療従事者が迷わず正しく使えるUI設計になっているかなど、社内や実際の医療機関のテストを繰り返し、試行錯誤を経てリリースするという、プロダクトデザインをしている感覚になり「伝えるデザイン」とは違った思考でデザインに取り組んでいます。

機能重視なサービスこそ、直感的にシンプルなUIが重要

このようにCLINICSという1つのサービスを構成する要素の中でも、ターゲットユーザーや提供価値の違いによって、デザインアプローチや重要視する視点が異なります。

たとえば、自分も業務でよく利用するプロトタイピングツールのinVisionもログイン前は、利用シーンやより魅力的なサービスであるということを伝えるためのデザインをしており、ログイン後は直感的にわかりやすいシンプルなUI設計で、ログイン前後でサービスのUIが異なります。

inVisionも機能は豊富ですが、プロトタイピングを行う上で非常にシンプルな操作性を提供しており、無駄な説明や導線がなくても直感的に使えるUIは、継続して利用できる安心感にもつながり、見習うべきUIだなと思っています。

f:id:medley_inc:20171116131754p:plain

(inVisionの画面の違い)

CLINICSの医療機関向けシステムも受付管理やスケジュール、オンライン診療を行う機能など複数の機能をひとつのサービスとして提供しているため、UI的に複雑になりかねません。複雑な機能をまとめ、画面上にシンプルに落とし込めるかどうかというのを吟味し、作っては壊し、作っては壊しを繰り返しします。

これならいける!とおもったUIも、機能面での見落としなどがあったりすると導線に矛盾が生じたり、シンプルに表現したつもりが、逆に使い勝手の悪いUIになってしまったり。UIを考えるというのは、感性に訴えるデザインとは違ったよりロジカルな思考が必要で、デザインしながら四苦八苦して、ぶつぶつ独り言を言うことが多くなりますw

デザインオリエンテッドなプロダクト開発

Webサービスであればいかに目標としているコンバージョン率を高めるかどうか、分析〜調査〜開発というサイクルをベースとした運用になりますが、特に医療機関向けのシステムの場合は、ムダな機能や使いづらいUIだとサービス的に不安要素を抱かせる要因にもなり、ビジネスの成功可否に直結します

そのため、リリースまでにUIを磨けるだけ磨き、実際に医療機関の現場に出向いてどういうフローで診察を行っているのかなどヒアリングしたり、出来上がった機能を試してもらうなどし、十分に検討した上でリリースしています。こうしたデザインを重視した開発が行える点はメドレーだからこその開発体制かもしれません。

このようなデザインオリエンテッドなプロダクト開発を行う上で重要なポイントは、事業理解とエンジニアとの密な連携です。表面的なデザインではなく、実際に使われる利用シーンを想像しつつも、体験することが難しい医療サービスだからこそ、前提の知識やヒアリング、ユーザーテストなどが重要となりますし、機能的な部分に関してはエンジニアと仕様について議論したり、ユースケースを踏まえてどのようなUIに落とし込むべきかを考えながらデザインに落とし込んでいきます。インタラクティブな表現などinVisionでも表現しきれない細かい動きや導線などは、実装時にエンジニアに伝わりやすいようフロントエンド部分のコーディングを自ら行うことでコードを通じてエンジニアとコミュニケーションが取りやすくもなるので、フロントエンドも把握しておくことは重要です。

まとめ

個人的には「伝えるデザイン」と「機能的なデザイン」で、明確に思考をわけて考えてデザインしてきたわけではなかったのですが、提供すべき価値の違いによって左脳と右脳それぞれ使い分けてデザインしているかもしれないということに気付かされました。これは以前デザイナーの小山がブログで書いたシステム1(自動的に直感で動く”早い思考”)とシステム2(手動で論理的に動く”遅い思考”)が自分の中で振り子のように行き来してるだろうなと感じたので、興味のある方は「思考とデザインスキル」も読んでもらうとわかりやすいです。

developer.medley.jp

最後に

ふだんビールばっかり呑んで適当な人とレッテルを貼られているマエダですが、今回は真面目なことを書いてみましたがいかがでしたでしょうか。このブログを書いてて正直疲れたので、システム2が働いてるに違いないと思います。こんな私と一緒に仕事がしたい、呑みたいというデザイナーやエンジニアさん。応募お待ちしております。

www.wantedly.com

www.wantedly.com

「フロントエンド開発に再入門する」タスクフォースの進め方

こんにちは。開発本部の宍戸です。 メドレーでは定期的に、テーマに沿って組織の技術的な底上げを行うための機会(タスクフォースと呼んでいます)を行っています。そのタスクフォースの1つとして先日、フロントエンド開発力のベースアップを目的としたタスクフォースを行いました。本記事では、その取組みについてご紹介したいと思います。

背景

メドレーには現在20人弱のエンジニアが在籍しており、その約半数がサーバーサイド出身者です。また普段の開発においては、一つの機能をフロントからサーバーサイドまで一貫して一人が担当するケースが多くあります。サーバーサイド出身者のフロントエンド開発のスキルセットには多少ばらつきはあるものの、普段の開発業務ではレビュー等でそれぞれサポートしつつ開発を行っています。

しかし、フロントエンドの基礎的な部分や最新の流れまで聞かれると不安なメンバーも少なくありません。フロントエンド出身のメンバーが主導し、改めて基礎や最新情報に関して整理・フォローを行うことで、組織全体のフロントエンドの開発力を高めていきたいと考えました。

タスクフォースの目的

今回のタスクフォースは『フロントエンドの基本や最近のトレンドに関して学ぶ』ことで『(フロントエンド開発における)技術選定、設計、実装ができる基礎を身につける』、そしてこれらをもとに『新規のプロジェクトで設計段階から自走できるようになる』ことを目的としました。

その中でも特にここ数年、変化の流れが早かったJavaScriptを中心にトピックを選定しました。

参加者は、これまでサーバーサイド開発を中心に行ってきたメンバー数名です。背景でも触れたとおり、業務経験はそれぞれある前提でのスタートであったため、基礎をみっちりというよりは、基礎的な話から最近の話題までを一通り確認しながら、各自の持っている知識の整理と土台を固めることで、今後の設計や技術選定を行う際の指針を得ることを目的としました。

進め方

今回のタスクフォースは期間を3ヶ月と定め、毎週1時間程度集まって行いました。

毎週、事前に講師陣が選んだ資料を読んでおき、当日は講師陣が参加者の不明点、疑問点に対してフォローアップするという形式で進めました。その他には、社内のプロダクトでの利用事例なども交えながらテーマに関する質問会のような形で進むことが多かったです。

また毎回のタスクフォースの時間のあとに、参加者がその日の内容をまとめた議事録形式の資料を作成し、参加者全員と共有することで、その日に話された内容や、それぞれの理解度を再度確認するようにしました。

内容

およその流れは上記の通りですが、約3ヶ月でどのようなテーマに触れてきたのか、一部をご紹介します。

フロントエンドの基礎

序盤はこちらの資料を利用させていただきながら進めました。

ブラウザの仕組み、HTML/CSSの基本的な話のおさらい、JavaScriptの話に関連して、これまでに出てきたAltJSについてもいくつか特徴や何故流行ったのかなどについて読み込みました。

この中でも、ブラウザの仕組み: 最新ウェブブラウザの内部構造でDOMの解析、レンダリング、レイアウトといったブラウザ内部で具体的に何が行われているのかといった話はここで確認できてよかったという声が多くありました。

JavaScript(基礎〜ES2015以降)

JavaScriptの話題への導入編としてこちらを資料として読み込みました。

このパートではJavaScriptの基礎は、あえてES3〜5をベースにすることでJavaScriptと他言語との違い・特徴を再確認していきました。上記の内容を踏まえ、今では使わない書き方などについてはその理由も確認しながら進めていきました。

その後はLearn ES2015 · Babelを参照しながら、Promise, Classなどは普段の開発でも当たり前のように利用しているものの、ES2015以降での書き方はES5だとどのようになっていたかもここで同時に学習していきました。(Playground ・ TypeScriptで、その雰囲気を見ることが出来ます)

Screen Shot 2017-10-27 at 16.14.49.png (424.4 kB)

モダンJavaScript

これまでの知識を踏まえ、モダンなJavaScriptの利用(実装)例として、jser.github.iopreact-wwwのソースを読んでいきました。

これまでのJavaScriptの言語自体に関する内容から、ライブラリを利用したコンポーネント間でのデータフローや、コンポーネントのライフサイクルに関する部分まで確認していきました。また初見のプロジェクトであればどのあたりから目を通していくか、などコード全体の読み方についても講師陣からアドバイスがありました。また時折出てくるDOM APIに「なんだっけこれ・・・?」などとなりつつもコードを紐解いていくことで改めてフロントエンドJavaScriptの特徴的な部分を垣間見ることができたように思います。

また最後に、現在開発に利用されているツール群について、Qiita:JavaScriptフレームワーク選定の議論を参考に確認しました。それぞれのツールがどのような背景で使われている(あるいはいない)のかなども合わせて確認をしました。

ここまでのテーマを振り返りつつ、JavaScriptが言語としてどのように変化してきたかを考えた時、webpackやTypeScriptがなぜ使われるのか、ようやく腹に落ちたように思います。また上記資料も、どのようなケースで何を選択するのかや、アプリケーションの寿命とライブラリやツールの寿命といった運用フェーズで理解していく必要のある事項にも触れられており、非常に参考になりました。

実際にやってみて

今回のタスクフォースでは、3ヶ月という期間の中で、1ヶ月半の時点と、全体終了後に振り返りを実施しました。 その中で、参加メンバーからは

  • 業務においては必要な部分に絞った調査で終わってしまったり、周りのコードを参考にしたりすることでなんとなくできた気になってしまっていたが、今回改めて基礎的な知識を学習する、復習する時間としてできてよかった。
  • 断片的でまばらな知識しか持っていなかった部分が資料を読み込むことで理解が深まった
  • 数年前にバズってあとで読まない「あとで読む」ブクマ記事をきちんと読めてよかった
  • フロントエンド経験の長い講師陣の生きた知見(ツラミなども含めて)を聞くことができたのはありがたかった

などの感想が出ました。 私自身も参加していましたが、個人的には基礎部分を改めて固めることができた機会だったように感じています。 また、普段は新しいツールなどの情報のキャッチアップへの意識が向きがちですが、今回のタスクフォースで言語・ツールの変遷を一度通して見ることができたことで、新しく現れる技術がどういった課題を解決するものなのか、これまでに似たツールがあったのかどうかなどを調べる指針が得られたことはよかったのかなと思っています。

一方で、今回のタスクフォースの中では「コードを書く」時間は作りませんでした。これについては振り返りの中でも話題となりましたが、それについてはTodoMVCなどを参考に、自分で手を動かしてみることが必要だろうと今後の課題として挙げられました。このあたりは実務以外での個々の頑張りが必要になってくる部分かと思います。

まとめ

「サーバーサイドエンジニアのフロントエンド開発力の底上げ」をテーマとしたタスクフォースについてご紹介しました。こういった形で、同じようなスキルセットのメンバーが集まり、お互いにわからない部分について話をしたり、経験豊富なメンバーから、個人の経験を含めて話を聞くというのは、改めて非常に貴重な機会だったように思います。

こういった取り組みを繰り返していくことで、個々で尖った部分はもちろん伸ばしつつ、全体の底上げを行いながら、よりよいプロダクト開発に繋げられればと考えています。

お知らせ

メドレーでは、医療介護の求人サイト「ジョブメドレー」、医師たちがつくるオンライン医療事典「MEDLEY」、口コミで探せる介護施設の検索サイト「介護のほんね」、オンライン診療アプリ「CLINICS」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。

www.medley.jp

メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。

CircleCI2.0に移行してビルド実行速度を向上

こんにちは。開発本部の稲本です。医療介護の求人サイト「ジョブメドレー」の開発を担当しているエンジニアです。

最近ジョブメドレーではCircleCI2.0への移行を行いました。移行の方法はもちろん、その際に調べたこと、CircleCIの新機能を利用してどうだったかなどを書いていきたいと思います。

課題感

弊社では、全プロダクト(CLINICSMEDLEY介護のほんねジョブメドレー)でCircleCIを利用しています。

ジョブメドレーではCIによるテスト実行に37分前後掛かっていました(コンテナを2つ利用した実行時間です)。 さらに、開発メンバーが増えて来たこともあり、CIのリソースが足りなくなり開発効率が落ちかねない状況でした。

まぁよくある話ですよね。

コンテナを増やすというのも解決策の一つとしてはあるのですが、速度の改善に期待が出来ると評判も良かったのでCirclecCI2.0へ移行しました。

CircleCI2.0への移行メリット

基本的には速度の改善に期待が出来る、というのが大きなメリットではありますが、公式では以下のように記載されています。

f:id:medley_inc:20171024100735p:plain

抜粋ですが大きな特徴としては以下の点でしょうか。

  • First-class Docker Support: DockerのネイティブサポートとDockerレイヤーキャッシュの導入
  • Workflows: ビルド、テスト、デプロイをジョブとして柔軟に管理できるようになった
  • Advanced Caching: キャッシュの保存とリストアをイメージ、ソースコード、依存関係に対して行うことができるようになった。

この辺りの機能を活用しCIの速度改善へ繋げてみたいと思います。

ジョブメドレーのアプリケーション構成

移行の前提として、ジョブメドレーのアプリケーション構成について記載します。

f:id:medley_inc:20171024100814p:plain

フロントエンドのビルドをyarn+webpackで行い、生成したアセットをpublic/assetsへ吐き出し、manifestファイルのパスをrailsのhelper経由で取得し読み込んでいます。(Rails4.2.x)

このような構成のアプリケーションをCirlceCI2.0でビルド、テスト、デプロイ出来るようにしていきます。

config.ymlの全体像

今回、作成したconfig.ymlはこのような形になりました。

ざっくりは

  • build: bundle install, yarn install
  • code_analyze: rubocop, brakeman, scss-lint
  • rspec
  • deploy: capistrano

を行っており、ブランチによってどのジョブを実行するのかをworkflowsを利用して使い分けています。

詳しい解説は以降、記載していきます。

defaults: &defaults
    working_directory: ~/job-medley
    docker:
      - image: circleci/ruby:2.4.2-node-browsers
        environment:
          TZ: /usr/share/zoneinfo/Asia/Tokyo
      - image: circleci/mysql:x.x.x
        environment:
          TZ: /usr/share/zoneinfo/Asia/Tokyo
      - image: redis:x.x.x
        environment:
          TZ: /usr/share/zoneinfo/Asia/Tokyo
version: 2
jobs:
  build:
    <<: *defaults
    steps:
      - checkout
      - restore_cache:
          key: job-medley-app-{{ checksum "Gemfile.lock" }}
      - run:
          name: bundle install
          command: bundle install --jobs=4 --path=vendor/bundle
      - save_cache:
          key: job-medley-app-{{ checksum "Gemfile.lock" }}
          paths:
            - vendor/bundle
      - restore_cache:
          key: job-medley-yarn-{{ checksum "yarn.lock" }}
      - run:
          name: Yarn install
          command: |
            echo "Node $(node -v)"
            echo "Yarn v$(yarn --version)"
            yarn config set cache-folder ./yarn_cache
            echo "Yarn v$(yarn cache dir)"
            yarn install
      - save_cache:
          key: job-medley-yarn-{{ checksum "yarn.lock" }}
          paths:
            - node_modules
            - yarn_cache
      - persist_to_workspace:
          root: ~/job-medley
          paths:
            - ./*
  code_analyze:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/job-medley
      - run:
          name: run rubocop
          command: bundle exec rubocop
      - run:
          name: run brakeman
          command: bundle exec brakeman -qz
      - run:
          name: run scss-lint
          command: bundle exec scss-lint
  rspec:
    parallelism: 2
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/job-medley
      - restore_cache:
          key: job-medley-elasticsearch
      # rspecでesのコマンドを一部実行しているため、primary container側へinstall
      - run:
          name: Elasticsearch install
          command: |
            wget https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-x.x.x.tar.gz && \
              tar -xvf elasticsearch-x.x.x.tar.gz && \
              if [ -z "`elasticsearch-x.x.x/bin/plugin -l | grep analysis-kuromoji`" ]; then \
              elasticsearch-x.x.x/bin/plugin -install elasticsearch/elasticsearch-analysis-kuromoji/x.x.x; fi
      - save_cache:
          key: job-medley-elasticsearch
          paths:
            - ./elasticsearch-x.x.x
      - run:
          name: database create
          command: bundle exec rake db:create
          environment:
            RAILS_ENV: test
      - run:
          name: run test
          command: |
            circleci tests glob 'spec/**/*_spec.*' \
              | circleci tests split --split-by=timings --timings-type=filename \
              | tee -a /dev/stderr \
              | xargs bundle exec rspec \
              --profile 100 \
              --format RspecJunitFormatter \
              --out rspec/rspec.xml \
              --format progress
          environment:
            RAILS_ENV: test
            TEST_CLUSTER_COMMAND: elasticsearch-x.x.x/bin/elasticsearch
      - store_artifacts:
          path: artifacts/
      - store_test_results:
          path: rspec/
  deploy_qa:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/job-medley
      - run:
          name: run deploy
          command: |
            sh scripts/init_deploy.sh
            BRANCH="${CIRCLE_BRANCH}" bundle exec cap develop deploy deploy:restart
  deploy_only:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/job-medley
      - run:
          name: run deploy
          command: |
            BRANCH="${CIRCLE_BRANCH}" bundle exec cap production deploy deploy:restart
workflows:
  version: 2
  workflows:
    jobs:
      - build
      - code_analyze:
          requires:
            - build
          filters:
            branches:
              ignore: /^sandbox.*|^master$|^staging$/
      - rspec:
          requires:
            - build
          filters:
            branches:
              ignore: /^sandbox.*|^master$|^staging$/
      - deploy_qa:
          requires:
            - code_analyze
            - rspec
          filters:
            branches:
              only: develop
      - deploy_only:
          requires:
            - build
          filters:
            branches:
              only: /^sandbox.*|^master$|^staging$/

DockerImageの選定

元々、Dockerを使わずにCIを回していましたが、CircleCI2.0へ移行するに辺りDockerへの利用に切り替えました。

Specifying Container Images

DockerImageはDockerHubへ登録されているCircleCI公式のもを利用しました。

アプリケーションの一部でReactを使用しており、フロントのビルドはyarn+webpackを利用しています。その為、以下のimageを選択しました。

  • circleci/ruby:2.4.2-node-browsers
    • nodeのインストールと、E2Eのテストに必要なソフトウェアがインストールされています。
    • ※詳細はこちらの記事を参考にさせていただきました。

その他、現在利用しているMySQLのバージョン、ElasticCacheRedisのバージョンと合わせたimageを選択しました。

注意点としては、複数のDockerImageを利用する場合、一つ目に指定したimageがprimaryとして扱われます。

以下の例ですと、Rubyのimageを最初に指定し、MySQL、Redisのimageを指定していますが、MySQLコマンド自体はRubyのimageに含まれていないため、Rubyコマンドを実行できてもMySQLコマンドを実行することは出来ません。

※詳細はこちらに記載されています。

docker:
  - image: circleci/ruby:2.4.2-node-browsers
    environment:
      TZ: /usr/share/zoneinfo/Asia/Tokyo
  - image: circleci/mysql:x.x.x
    environment:
      TZ: /usr/share/zoneinfo/Asia/Tokyo
  - image: redis:x.x.x
    environment:
      TZ: /usr/share/zoneinfo/Asia/Tokyo

また、用意されているimageをカスタマイズする必要がある場合は、Dockerでカスタムimageを作り、publicで良ければDocker Hubへ登録、privateが良ければAmazon EC2 Container Registryへ登録しておくことで呼び出すことも可能になっています。

※Using Custom-Built Docker Images circleci.com

※Using Private Images circleci.com

buildの設定とcache

CIで実行するアプリケーションのbuildに関してです。

checkoutgithubからコードをcheckoutし、その後の定義でアプリケーションのインストール、キャッシュ保存、キャッシュの展開を行っています。

steps:
  - checkout
  # Rails application setup
  - restore_cache:
      key: job-medley-app-{{ checksum "Gemfile.lock" }}
  - run:
      name: bundle install
      command: bundle install --jobs=4 --path=vendor/bundle
  - save_cache:
      key: job-medley-app-{{ checksum "Gemfile.lock" }}
      paths:
        - vendor/bundle
  • save_cache: keyにGemfile.lockを指定することでキャッシュキーとして扱い、pathsに設定したパスをキャッシュするようにしています。
  • restore_cache: keyと一致するキャッシュがあれば、save_cache時に指定したパスを展開し直しています。
  • run: こちらはrailsのアプリケーションインストールしているだけです。

CircleCI1.0よりもキャッシュ管理を柔軟に行えることがわかります。

circleciコマンドによるRspecの並列実行

rspecによるテストの実行に関してです。 circleci コマンドを利用することでテストの並列実行を効率的に行うことが出来るます。

今回は --split-by=timings --timings-type=filename のオプションを指定し、ファイル名ベースでの分割でテストを実行します。

- run:
    name: run test
    command: |
      circleci tests glob 'spec/**/*_spec.*' \
        | circleci tests split --split-by=timings --timings-type=filename \
        | tee -a /dev/stderr \
        | xargs bundle exec rspec \
        --profile 100 \
        --format RspecJunitFormatter \
        --out rspec/rspec.xml \
        --format progress
    environment:
      RAILS_ENV: test
      TEST_CLUSTER_COMMAND: elasticsearch-x.x.x/bin/elasticsearch
- store_artifacts:
    path: artifacts/
- store_test_results:
    path: rspec/
  • store_artifacts: 以前からもある機能ですが、テスト結果の成果物を保存するパスになります。
  • store_test_results: こちらはテストの実行結果を保存しておくことで、コンテナ間でrspecの実行時間にばらつきが出ないよう、対象のファイルを最適に振り分けてくれるようなのですが、workflowsを利用するとサポートされないようです。

※参考: https://circleci.com/docs/2.0/configuration-reference/#store_test_results

このような形でArtifactsが保存されています。 f:id:medley_inc:20171024101642p:plain

また、artifactsにはcoverageとcapybaraのscreenshotなどを保存しています

- simple_cov
    if ENV['CI']
      SimpleCov.coverage_dir File.join(ENV['CIRCLE_WORKING_DIRECTORY'], 'artifacts', 'coverage')
      SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
        SimpleCov::Formatter::HTMLFormatter
      ]
      SimpleCov.start do
        add_filter '/vendor/'
        add_filter '/spec/'
        add_filter '/config/'
        add_filter '/db/'
      end
    end
  • capybara
    • Capybara.save_and_open_page_path = File.join(ENV['CIRCLE_WORKING_DIRECTORY'], 'artifacts', 'capybara') if ENV['CI']

※CircleCI2.0からCIの環境変数が変わっています。詳細は以下のリンクへ記載されています。 circleci.com

Workflowsの設定

CircleCI2.0からWorkflowsの利用が可能になりました。 circleci.com

コンテナ毎に分割しジョブを実行することで更なる並列実行の効率化、及び、ジョブ間の依存関係まで設定できるようです。 ジョブメドレーではコードの静的解析に少し実行時間が掛かっていることから、多少の改善を図れると考え単純に使ってみたかったこちらの機能を利用してみました。

workflows:
  version: 2
  workflows:
    jobs:
      - build
      - code_analyze:
          requires:
            - build
          filters:
            branches:
              ignore: /^sandbox.*|^master$|^staging$/
      - rspec:
          requires:
            - build
          filters:
            branches:
              ignore: /^sandbox.*|^master$|^staging$/
      - deploy_qa:
          requires:
            - code_analyze
            - rspec
          filters:
            branches:
              only: develop
      - deploy_only:
          requires:
            - build
          filters:
            branches:
              only: /^sandbox.*|^master$|^staging$/
  • build: 各ジョブで実行前に行っておく処理を定義
  • code_analyze: rubocop、brakeman、scss-lintなどの静的解析処理を定義
  • rspec: アプリケーションのテストを定義
  • deploy: デプロイに関する処理を定義

ジョブメドレーでは、ブランチ管理にGit-flowを採用していますが、それとは別にsandboxというテスト環境を用意し運用しています。 developブランチでコード解析やテストをクリアしたコードだけ、masterへ反映し、masterではテストフェーズなしにdeployする構成を取っています。 極力、CIのリソースを節約するように各ブランチごとで実行する処理を分けています。

各ブランチの運用は以下の通りです。

  • feature:
    • コード解析、テスト実行
  • sandbox:
    • デプロイのみ実行(一時レビュー用ブランチ)
  • develop:
    • コード解析、テスト実行、デプロイ実行
  • master:
    • デプロイのみ実行

上記の例では、

  • code_analyze:
    • sandbox、master、stagingブランチ以外は実行
    • requiresで依存関係を指定し、buildが正常に終了しなければ実行されないようになっています。
  • rspec:
    • sandbox、master、stagingブランチ以外は実行
    • requiresで依存関係を指定し、buildが正常に終了しなければ実行されないようになっています。
  • deploy_qa:
    • developブランチでのみ実行
    • requiresで依存関係を指定し、code_analyze、rspecが正常に終了しなければ実行されないようになっています。
  • deploy_only:
    • sandbox、master、stagingブランチのみ利用するジョブ
    • requiresで依存関係を指定し、buildが正常に終了しなければ実行されないようになっています。

どのようなworkflowが出来あがるのか、以下に例を示します。

  • featureブランチの例: f:id:medley_inc:20171024101757p:plain

  • developブランチの例: f:id:medley_inc:20171024101818p:plain

  • master、sandboxブランチの例: f:id:medley_inc:20171024101852p:plain

今回の例だとブランチ毎にworkflowを変えているため、ignore、onlyの書き方で意図せず振り分けされないように考慮は必要ですが、柔軟にworkflowを作れることがわかると思います。(やり過ぎると読み解くのが大変になりそうですね)

Workflowsに関してはこちらに色々なパターンの組み方が記載されているので、こちらを読むとより理解が深まると思います。

ジョブ間でのデータ共有

ジョブを分けてビルドする=何回もアプリケーションの初期化が必要なんじゃないか? と当然疑問に思う点ではありますが、それに対する解決策も用意されています。

build:
  steps:
    ===省略===
    - persist_to_workspace:
        root: ~/job-medley
        paths:
          - ./*

deploy_qa:
  <<: *defaults
  steps:
    - attach_workspace:
        at: ~/job-medley
  • persist_to_workspace: 指定したパスにあるデータを一時的に保管してくれます
  • attach_workspace: 保管済みのデータを展開してくれます

この機能により、ビルドプロセスで生成したものを各ジョブで実行するコンテナへ渡すことが出来ます。 ただし、そもそもビルドプロセスでキャッシュを入れていることもあり、これ自体の効果は殆どありませんでした。 コンパイル済みのデータを受け渡す際には効果を発揮しそうですね。(公式でもそのような利用を想定していそうです)

改善結果

肝心の速度改善結果です。結果は以下の通りになりました。

改善前: CircleCI1.0

  • rspecの実行時間: 26:59 f:id:medley_inc:20171025162233p:plain

以下は、CircleCI2.0へ移行しただけの結果です。このケースではworkflowsを利用していません。

改善後: CircleCI2.0

  • rspecの実行時間: 19:40 f:id:medley_inc:20171024102115p:plain

CircleCI1.0からCircleCI2.0へ移行することにより、約12分程テストの実行時間を短縮することが出来ました。 Workflowsなど特に利用していない、かつ、ビルドフェーズの実行時間も関係しないため、CircleCI2.0を利用するだけで単純にテスト実行速度の向上を見込めることがわかると思います。

続いてWorkflowsを利用した結果です。

改善後: CircleCI2.0 with Workflows

  • rspecの実行時間: 21:14 f:id:medley_inc:20171024102148p:plain

結果は、Workflowsを利用しないケースと、利用したケースでは、Workflowsを利用したほうがrspecの実行時間は長くなってしまいましたが、build-code-analyze-rspecの実行に掛かったトータルの時間に差は見られませんでした。

これは、「circleciコマンドによるRspecの並列実行」のセクションへも記載した通り、store_test_resultsがサポートとされないことにより、コンテナ間での分散が最適化されていない為です。

コンテナ間でrspecの実行時間にばらつきが出ないよう、対象のファイルを最適に振り分けてくれるようなのですが、Workflowsを利用するとサポートされないようです。

実行時間にばらつきが出てしまい、code_analyzeのジョブを分散することで見込んでいた改善時間(約3分)とばらつきにより発生したテスト実行時間のロス( (20:01 - 14:57) / 2 = 2:32 )が大体同じであるため、トータルでの実行時間に差が出ない結果となりました。

ばらつきを出さない方法や、ジョブの分け方については今後も工夫してみたいと思います。 また、フロントエンドのテストをもう少し厚くしていきたいと考えているので、フロントエンドのテスト、サーバサイドのテストをWorkflowsを上手く使いながら分散していければ良いのかなとも思っています。

さいごに

移行に際して、CircleCI2.0の移行ガイドを読みながら進めていましたが、基本的な記法の変更、timezone、environmentの定義方法の変更、variableの変更などが多々有り、ドキュメントを結構読み込まないとどこに何が定義できるのか把握できませんでした。

また、Workflowsの組み立てなど公式に良いサンプルは沢山あるのですが、依存関係の定義を色々試すのに苦労した気がします。

※素直に小規模なアプリを用意して、ローカルでcircleciを実行してみた方が効率良く進められたかもしれません。

※ただ、公式のドキュメントCommunityForumをしっかり読めば余すことなく情報は合ったので非常に助かりました。

CircleCI2.0でどのような事が出来るのか、それはどのように行えるのか。 この記事がその概観をつかむ助けになれば良いなと思っています。

参考リンク

CircleCI2.0へ移行するにあたり、以下の記事を参考にさせていただきました。ありがとうございます。 qiita.com medium.com

お知らせ

メドレーでは、医療介護の求人サイト「ジョブメドレー」、医師たちがつくるオンライン医療事典「MEDLEY」、口コミで探せる介護施設の検索サイト「介護のほんね」、オンライン診療アプリ「CLINICS」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。

www.medley.jp

メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。