メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックリーディングカンパニーのエンジニアブログです。PHPからRubyへ絶賛移行中!継続的にアウトプットを出し続けられるようにみんなでがんばりまっす!

「MedBeer -Rails開発での技術的負債との付き合い方-」を開催しました!

身長体重が変わっていないにも関わらず、5年前より体脂肪率が4.5%増加したエンジニアの村上(@pipopotamasu)です。

本日は9/12(水)に開催したMedBeerというイベントを報告したいと思います。

medpeer.connpass.com

f:id:ec0156hx39:20180921184837j:plain f:id:ec0156hx39:20180921165847j:plain @GINZA SIX 12F 株式会社リンクアンドモチベーション内 イベントスペース

MedBeerとは?

MedBeerとは、年1回くらいのペースでメドピアが主体となって行なっている技術イベントになります。 基本的にRailsに関することをテーマに発表を行い、今回で4回目の開催となりました。

第1回から着々と規模が大きくなっており、前回は約60名、今回は約120名程が参加されました。

また「Beer」という名をつけているので、10種類ほどのビールとそれにおつまみ(今回はからあげエンジニアが参加するということでしたので、日本で初めて外食メニューとして唐揚げを出した三笠会館の唐揚げを用意しました)を用意していました。

MedBeer -Rails開発での技術的負債との付き合い方-

サブタイトルにも書いてある通り、今回のテーマは「Rails開発における技術的負債」です。 このテーマでメドピアからは2名、またClassi株式会社CTOの佐々木さん、クックパッド株式会社の小室さんをお招きし、計4名で上記テーマに沿って発表を行いました。

Rails Good Parts, Bad Parts

まず初めのトップバッターはメドピアの技術顧問、前島真一(@willnet)さんです。

f:id:ec0156hx39:20180921183445j:plain

発表では「負債の増加を防ぐにはどうしたら良いか?」という観点から、Railsで負債の増加を防ぐ方策を提示していただきました。 重要な要素を一部抜粋すると以下のようになります。

  • Railsが用意する便利機能と、要注意な機能の扱い方を知ることで負債を貯めずに開発することができる
  • そのためにはレビューや社内教育などを通してRailsに習熟することが大事

また発表の中では、前島さんの主催するクリーンなRailsのコードを議論するコミュニティ「clean-rails.org」を紹介していただきました。 皆さんもRailsの設計や実装で悩んだ時など、こちらで相談したらいかがでしょうか?

発表資料は以下になります。

小さな成功体験を積み重ねてチームで負債に立ち向かう

2番目は、Classi株式会社でCTOである佐々木 達也(@sasata299)さんに、負債返却についてClassiで行なった事例について発表していただきました。

f:id:ec0156hx39:20180921184318j:plain

負債返却に必要なものは「負債返却のリソース」と「負債返却いけるいけるという気持ち」の2つが必要で、後者を中心に話していただきました。 既存コードの改修のためにモブプロ/ペアプロの導入や、整地部というコードをリファクタリングする活動を設けたりと、とても参考になる事例を含む発表でした(ちなみに整地部はメドピアで取り入れようとする動きがあります)。

発表資料は以下になります。

メドピアにおけるライブラリアップデート

3番目は私、メドピアの村上大和(@pipopotamasu)が発表させていただきました。

f:id:ec0156hx39:20180921183639j:plain

「メドピアにおけるライブラリアップデート」という題の通り、メドピアでどのようにライブラリのアップデートを行なっているかを発表しました。 メドピアではGemをアップデートするためのフローがあり、1ヶ月毎にそのフローに沿ってアップデートを行い、バージョンが最新と乖離しないように(=負債をためない)しています。具体的にどんなフローでアップデートを行なっているのかの説明と、そのフローをJavaScriptのライブラリに適用させようとしていることをメインで話しました。

発表資料は以下になります。

クックパッドの巨大 Rails アプリケーションの改善

最後に発表したのは、クックパッド株式会社の小室 直(@hogelog)さんです。クックパッドの巨大なRailsアプリケーションの改善について発表していただきました。

f:id:ec0156hx39:20180921184340j:plain

お台場プロジェクトというクックパッドの巨大レポジトリの改善プロジェクトのお話がメインでした。「巨大なRailsアプリケーションの改善は計測と可視化から」という言葉通り、CIにかかった時間・アプリのロード時間・コード量・依存Gem数など詳細にデータをとり、改善結果がそれらの数値にどう影響あったかを逐次確認できる という状態にして、プロジェクトメンバーのモチベーション向上やメンバー以外への成果の共有にも繋がるため、とても参考になりました。

発表資料は以下になります。

終わりに

冒頭にも書きましたが、今回のMedBeerは今までで最も規模の大きいのものになりました。 多くの皆さまに参加いただき、他社の事例も知ることができ、実りの多いイベントでした。 また、急遽会場を貸していただき、当日の準備も手伝っていただけたリンクアンドモチベーション社の皆さまも本当にありがとうございました。 様々な人の協力もあり、第4回MedBeerも良いイベントにすることができました。

今回参加された人もされなかった人も、次回のMedBeerでお待ちしてます!!!


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

開発環境のレンダリング完了時間を1/10にした話(Rails)

ビール大好きですが、3回に1回は年齢確認をされる26歳エンジニアの村上(pipopotamasu (pipopotamasu) · GitHub)です。

今回はリクエストからレンダリング完了までの時間を減らした取り組みについて書こうと思います。

f:id:ec0156hx39:20180820162919p:plain

きっかけ

メドピアではメインプロダクトをRuby on Railsで開発しています。 Railsで開発を始めてから2年半、すくすくとアプリケーションが成長してきています。 しかし成長していくにつれ問題が出てくるのが世の常、以下のような問題が生じてきました。

「ページ読み込みが遅い...」 f:id:ec0156hx39:20180816143017p:plain

Webアプリケーション開発者は開発環境で1日に何十回とページ読み込みをさせると思いますが、Railsの成長と共にそれがめちゃくちゃ遅くなってきたのです。

環境

メドピアのフロントエンド環境はassets pipeline(browserify-railsを用いてビルドしている)で管理している部分と、Webpackerで管理している部分があります。 今回の取り組みは、JavaScriptをassets pipelineからWebpackerへ移行している過渡期に行いました。

問題は何か?

さて、ページの読み込みがめちゃくちゃ遅いという問題にぶちあたりましたが、これだと問題が大き過ぎるのでもうちょっと細分化してみましょう。 以下はリクエストからページレンダリング完了までの流れになります(超ざっくりです)。

f:id:ec0156hx39:20180817113222p:plain

①~⑤まで確認してみたところ、②と④、⑤に時間がかかっていることがわかりました。

まずdev toolの計測を見てみましょう。 f:id:ec0156hx39:20180817131401p:plain

青色が待機時間、サーバーから応答が返るまでの待ち時間です。最初のHTML取得で結構時間がかかっていることがわかります。 ピンク色はブロック時間、ネットワーク接続の待ち行列で費やした時間を表しています。

最初のHTML取得までの時間、つまり②の時間が遅いことについては、開発環境であるためRailsのconfig.cache_classesをfalseにしていてコードのキャッシュをさせないようにしているので、遅いのはある程度仕方がないのですが、それにしても遅い...。サーバ側でのViewのレンダリングにかなりの時間を費やしています。

# config/environments/development.rb

config.cache_classes = false

f:id:ec0156hx39:20180820005252p:plain

またdev toolの計測を見てみると、CSSやJavaScriptといったアセットを取得しに行く時間、つまり④、⑤も異常に遅いです、全リソースを読み込むのに15秒もかかってるorz。そもそも読み込もうとしているJavaScriptが多すぎますね。

原因は何か?

②が遅い原因

アプリケーションログを遡っていくと、あることに時間を費やしているのがわかりました、browserifyのビルドです。 JavaScriptに何かしらの変更を加えた時に、browserifyのビルドが走っていてそれが遅く感じる原因のようです。 f:id:ec0156hx39:20180820005453p:plain

どうやらbrowserifyはWebpackなどと違い、デフォルトで差分ビルドができず(watchifyというプラグインが必要)、JavaScriptを変更した後にエントリーポイントで読み込むファイルを全ビルドしていたためにかなり遅くなっていました。 読み込んでいるJavaScriptの種類にもよりますが、約4~6秒ほどかかっていました。

④と⑤が遅い原因

読み込もうとしているJavaScriptが多すぎて、ブラウザの同時接続数(Chromeは6)をオーバーしているためです。前述したdev toolのピンク色がブロック時間、つまり接続待ちを表しています。

そもそも何故こんなにも読み込むJavaScriptが多いのでしょうか?

どうやらdevelopment環境のデフォルトで、assets pipelineで本来一纏めにされるJavaScriptがデバック用にバラバラで読み込まれる設定がされているようです。

# config/environments/development.rb

config.assets.debug = true

これにより読み込むJavaScriptが多くなりすぎて、接続待ちに時間が多く費やされていました。

対策

browserifyのビルドが遅い問題

watchifyを導入して、差分ビルドをさせるようにするという案もありましたが、最終的にWebpackに置き換えるという形にしました。 これについては別に記事を書いているのでよろしければご覧ください。

tech.medpeer.co.jp

最終的にbrowserifyの全ビルドから、Webpackの差分ビルドに置き換えたことにより、 4~6秒 → 1.5秒 に短縮することができました。

f:id:ec0156hx39:20180820010534p:plain

読み込むJavaScriptが多すぎる問題

デバックモードもそれはそれでデバックしたい時には便利なので、単純にconfigの値をfalseにするだけだと切り替えが面倒です。

# config/environments/development.rb

config.assets.debug = false # デバッグしたい時にtrueに書き換えてappサーバを再起動する必要がある

しかしconfig.assets.debugがfalseの時、Railsのjavascript_include_tagヘルパーのdebugオプションをtrueにすることで、デバックモードに切り替えることができます。

railsguides.jp

このオプションを利用して、debugしたい時だけURLに特定のパラメータを付与するとdebugモードに切り替わるヘルパーを作成しました。

  def javascript_include_tag_with_debug_option(js, options = {})
    javascript_include_tag js, options.merge(debug: params[:js_debug].present?)
  end

このヘルパーを利用することで、通常時のリクエストはdebugモードはOffですが、以下のように特定のパラメータを付与するとOnにできるようになりました。 https://hostname.jp/articles?js_debug=on

終わりに

今回は開発環境の話でしたが、本番環境でもリソース取得時間の改善の余地がまだまだあります。 「サイトの読み込み時間を爆速にする」ことに興味がある方は是非一度メドピアに遊びに来てください。

また9月12日にメドピアでRailsの技術的負債をテーマにした「MedBeer」というイベントを開催します。

medpeer.connpass.com

負債の予防・返却に興味がある人、ビールが飲みたい人は是非参加してください。


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

開発合宿@伊豆大島に行ってきました!

こんにちは。メドピアのサーバサイドエンジニアの小林です。

まずはじめに、この度の西日本豪雨で被災された方々にお見舞い申し上げますとともに、被災地の一日も早い復興を心よりお祈り申し上げます。

今回のブログでは、メドピアでは恒例となっているエンジニア・デザイナー開発合宿に参加してきましたので、レポートさせていただきます。

メドピアでは、これまでにも年2〜3回ぐらいのペースで開発合宿を開催しています。

この夏は、東京の離島、伊豆大島へ…!メドピアとしても合宿で島へ行くのは初めてのことでした。

1日目

東京港竹芝ターミナルから船に乗っていざ、出発! 船は予想通り(予想以上に?)揺れるので酔い止め薬必須です。

船に揺られること2時間、着いたのは伊豆大島の北の玄関口、岡田港です。

f:id:marikokobayashi:20180711194653j:plain

ここから更に三原山に登り、車で30分程経った頃、標高500mほどに位置する大島温泉ホテルさんに到着。

【公式サイト】伊豆大島・三原山温泉/大島温泉ホテル

f:id:marikokobayashi:20180711195031j:plain

到着…したは良いものの…あたりは一面の霧でした…!肌寒い…!

私はあたり一面を山に囲まれた田舎出身者ですが、今回は事前にちゃんと標高を調べていなかった中、実家の標高より高い場所に来てしまい *1、軽装で来た(薄手のカーディガンは持っていましたが…)ことを若干後悔しました。

しかし、今回は観光に来たのではなく合宿。山を降りようにもこの霧の中ではいまいち降りる気も湧いてきません。 合宿所にこもるにはうってつけではないでしょうか…!?!?

f:id:marikokobayashi:20180711210044j:plain
名物の椿フォンデュの天ぷらが美味しかったです。ところてんが食後のデザートのあとに出てきました

今回は、ホテル内に全員が集まれる会議室や広間のような場所は借りていないため、借りた客室の中で一番広い部屋にいったん集まって、それぞれが何をやるか発表しあって合宿スタートです!

f:id:marikokobayashi:20180711210732j:plainf:id:marikokobayashi:20180711210218j:plain
1部屋に集まってやること発表会

2日目

2日目も、特に皆出かける予定もなく、宿でもくもく…。

f:id:marikokobayashi:20180711211007j:plainf:id:marikokobayashi:20180712153054j:plain
もくもくしている様子
ちなみに温泉は昼は3時から夜の12時まで入れるようになっていましたので、3時になるとすぐにお風呂に入り出す人もいました。
f:id:marikokobayashi:20180711210927j:plainf:id:marikokobayashi:20180711211022j:plain
2日目のご飯の様子

最終日

最終日はすぐにやってきます。3日間あるとはいえ、1日目の夕方に宿に到着して3日目の朝には宿を出発。 ご飯・睡眠・お風呂の時間も考えると、開発にあてられた時間は実質1日強といったところです。

最終日の朝、宿を出発したあと、帰りの船に乗るまで時間がありましたが、あいにくの雨でしたので、一旦皆で元町港の近くにある御神火温泉さんに立ち寄り、休憩所で開発をしたりそれぞれ好きなように過ごしていました。

f:id:marikokobayashi:20180712201548j:plainf:id:marikokobayashi:20180711211250j:plain

その後、出発1時間ほど前になってやっと雨が止んできたので、岡田港にバスで向かい、近辺でお土産を購入したり、アイスを食べたりと束の間の夏気分を味わいました。

f:id:marikokobayashi:20180711211439j:plainf:id:marikokobayashi:20180711211411j:plain
べっこうずしが美味しかったそうです(私は食べれなかった)

成果発表会

成果発表会は後日会社で行いました!FirebaseやGraphQL、Google Cloud Speech APIなどのライブラリを試した人、資格取得に向けた資料作りをした人、パフォーマンスチューニングに挑んだ人などがいて様々な種類の発表がありました。エンジニア・デザイナー以外のメンバーにも聞いていただけました!

f:id:marikokobayashi:20180711212134j:plainf:id:marikokobayashi:20180711212238j:plainf:id:marikokobayashi:20180712152154j:plainf:id:marikokobayashi:20180711212207j:plain

まとめ・感想

今回は伊豆大島ということで、最初は「夏の海!観光!」といった期待をしていた人が少なくなかったようですが、実際は天気もあまり優れず山の上にこもりきりということになりました…!! そのぶん開発自体に集中できたという人も多いのではないかと思います。

私個人としては、今回は3人1チームで1つ新サービスを立ち上げるということにチャレンジしました。

私は合宿に参加するのが1年前から数えて3回目になりました。 これまでにも合宿中に新サービス作りにチャレンジしようとしたこともありましたが、今まではRails開発経験の浅さもあって(と言ってしまうと言い訳じみていますが…)人に見せられるような形での新サービスを3日で作り上げるということはできなかったのですが、今回は3人のチームで、各々の分担を決めて限られた日程の中でスケジュール立てして作っていったので、発表のときには「新サービスこんな感じで作ったよ!」と紹介することができ、達成感がありました。

合宿に参加する意義としては、新しいライブラリを試したり、普段業務で後回しになって手を出せずにいるようなリファクタリング等に取り組める場として使えるというのももちろんですが、「短期間でどうやって人に見せられるような成果を出せるか?」の経験を積むことが出来るという側面もあるように感じました。

今後も開発合宿を定期的に開催していただける予定のようなので、次回あたりには是非私の地元の県での合宿の開催を検討してもらえないかな〜と密かに狙っています。

以上、メドピア開発合宿@伊豆大島の様子をお伝えしました!

f:id:marikokobayashi:20180711213126j:plain


是非読者になってください(︎ ՞ਊ ՞)︎


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

Rails未経験/経験年数が2年以内のポテンシャルエンジニア絶賛募集中です!*2

Rails開発してみたい人、新しい技術に意欲的な人、リードエンジニアに教育されてみたい(?)人、医療に関わるサービス開発を行ってみたい人、その他メドピアに興味を持った方などは、是非、コンタクトを取って頂ければと思います!

*1:長野県民は実家の標高を言えるらしい(詳しくはググってください)(私も急に聞かれたけどおおよそ正確な値を言えました)

*2:リードエンジニアも募集中です!

poltergeistからheadless chromeへ移行する時に気をつけること

こんにちは。メドピアのRuby(Rails)化をお手伝いしている@willnetです。最近は子育てに忙しくしています 👶

先日、メドピアで利用しているcapybaraのjavascript driverをpoltergeistからheadless chrome(selenium-webdriver)に移行しました。driverを変更するにあたって既存のテストコードをいくつか修正する必要があったので、そこで得た学びを共有したいと思います。

なぜ移行したのか

ここ数年、Railsでエンドツーエンドのテストを書くときにはpoltergeistを使う、というのがデファクトスタンダードだったはずです。それ以前はみんなcapybara-webkitを使っていましたが、poltergeistはバックエンドにPhantomJSを使っており、Qtに依存しているcapybara-webkitと比べてビルドが簡単だったことから徐々にシェアを増やしていったように思います*1

そんな中、だいたい1年ほど前にChromeのバージョン59からヘッドレスモードが実行できるようになりました。これをうけて、PhantomJSの開発が停止されました。PhantomJSをメンテするのはだいぶ大変だったみたいです。

これは当然、依存しているpoltergeistにも影響します。PhantomJSの依存をやめる構想もあったようですが、リポジトリを眺めている限りではあまり進捗しておらず、さらにpoltergeistの開発自体あまりアクティブではないように見えます。

ここまでの経緯を考えると、headless chromeに切り替えるのは無難な選択と思われるのですが、移行時期には考慮が必要でした。headlessモードが使えるchromeがリリースされた当初は、chromedriver(chromeを別プロセスから動かすのに必要なもの)に大きめのバグがあり、すぐに移行するには不安な状況でした。しかしそれから1年ほど経過した今なら問題なく移行できるのではないか?となったのでした。

そんなわけで、フィーチャスペックのときに使うドライバをpoltergeistからheadless chromeに変更しました。

f:id:willnet:20180619182309p:plain

移行に必要なこと

リリースから1年経ってこなれたと言っても、poltergeistからheadless chromeへの変更は単純に入れ替えただけでは完了しません。テストコードの変更も必要です。

僕たちはcapybaraのインターフェースを経由してheadless chromeやpoltergeistを利用しているので、移行してもテストの書き方は基本的には変わりません。ただ、ドライバによって対応しているメソッドとしていないメソッドがあるため、poltergeistのときは動いていたコードがheadless chromeでは動かないケースがあるのでした。そういうところは動くように書き換えてあげる必要があります。

大抵の箇所は機械的に置換していくようなやり方でOKなのですが、テストの内容によってはもっと頑張らなければならないところがあるのでそれを紹介します。

ファイルダウンロードのテストを変更する

例えばリンクをクリックするとcsvがダウンロードできるようなページがあったとします。poltergeistを利用している場合、次のようにレスポンスヘッダを見て、Content-Typeが text/csv であることをもってテストを成功をみなすという方法があります。

context 'CSVダウンロード用のページに遷移したとき' do
  before { visit csv_download_path }

  it 'かつ"CSVダウンロード"をクリックしたらCSVファイルがダウンロードできること' do
    click_on 'CSVダウンロード'
    expect(page.response_headers['Content-Disposition']).to eq("attachment; filename=\"download.csv\"")
    expect(page.response_headers['Content-Type']).to eq("text/csv")
  end
end

上記のコードは、headless chromeに切り替えるとうまく動きません。どうやらheadless chrome(正確にはselenium webdriver)はレスポンスヘッダを見るAPIを提供していないようです。バグとかではなくそういう設計方針の模様

レスポンスヘッダを見れないとしたら、実際にダウンロードしたファイルの内容を確認するしかありません。こちらのエントリを参考にしつつ、実際にダウンロードしたファイルの中身をチェックする方法に変更しました。

まず、次のようなヘルパーメソッド群をモジュールとして定義し、フィーチャスペックで使えるようにします。

module DownloadHelper
  TIMEOUT = 10
  PATH    = Rails.root.join('tmp/downloads')

  module_function

  def downloads
    Dir[PATH.join('*')]
  end

  def download
    downloads.first
  end

  def download_content
    wait_for_download
    File.read(download)
  end

  def wait_for_download
    Timeout.timeout(TIMEOUT) do
      sleep 0.1 until downloaded?
    end
  end

  def downloaded?
    !downloading? && downloads.any?
  end

  def downloading?
    downloads.grep(/\.crdownload$/).any?
  end

  def clear_downloads
    FileUtils.rm_f(downloads)
  end
end

RSpec.configure do |config|
  config.include DownloadHelper, type: :feature
  config.before(:suite) { Dir.mkdir(DownloadHelper::PATH) unless Dir.exist?(DownloadHelper::PATH) }
  config.after(:example, type: :feature) { clear_downloads }
end

これにより、先程のテストを次のように変更することができます。

context 'CSVダウンロード用のページに遷移したとき' do
  before { visit csv_download_path }

  it 'かつ"CSVダウンロード"をクリックしたらCSVファイルがダウンロードできること' do
    click_on 'CSVダウンロード'
    expect(download_content).to eq "row1,row2\n"
  end
end

さてこれで解決…と思いきや、もうひとつ対処が必要だったりします。どうやら、 headless chromeはデフォルトでファイルダウンロードをしないようです。そこでstackoverflowの回答を参考に、ファイルダウンロードを許可するようにしました。具体的には次のようにconfig/rails_helper.rbに記述しています(bridge変数以下がファイルダウンロードを許可しているコード)。

Capybara.register_driver :headless_chrome do |app|
  driver = Capybara::Selenium::Driver.new(
    app,
    browser: :chrome,
    desired_capabilities: Selenium::WebDriver::Remote::Capabilities.chrome(
      login_prefs: { browser: 'ALL' },
      chrome_options: {
        args: %w(headless disable-gpu window-size=1900,1200 lang=ja no-sandbox disable-dev-shm-usage),
      }
    )
  )
  bridge = driver.browser.send(:bridge)
  path = "session/#{bridge.session_id}/chromium/send_command"
  bridge.http.call(
    :post, path,
    cmd: 'Page.setDownloadBehavior',
    params: {
      behavior: 'allow',
      downloadPath: DownloadHelper::PATH.to_s,
    }
  )
  driver
end

Capybara.javascript_driver = :headless_chrome

これでファイルダウンロードをheadless chromeでテストできるようになりました。

ブラウザ上で見えない要素に対応する

headless chromeは、poltergeistと比べて「ブラウザ上で見える要素であるか否か」にシビアなようです。例えば、position: fixed; left: 1000px としているDOM要素があるとします。このとき、ブラウザのウィンドウサイズが(800, 600)のように小さく要素が画面外になる場合は、その要素は見えないという扱いになります。

この場合は単にウィンドウサイズを大きくしてあげればよいのですが、それでは対応できない場合は次のようにvisible: falseをつけて、見えない要素の中で指定のDOMが存在するかを確認する必要があります。

expect(page).to have_css('.super-right-dom', visible: false)

このように「画面外にあるので見えない」というのはわかりやすいのですが、なにをもって「見える」「見えない」と判断しているのか難しいなと感じるケースがあります。例えばあるページではbxSliderを利用して画像をカルーセル表示しているのですが、スクリーンショットで見えていることが確認できるDOM要素もheadless chrome的には見えない扱いをされていました。

やむを得ずこれもvisible: falseとして対応しました。しかしこのあたりはまだ深く調査できてないので、詳しい方いたら教えていただけると嬉しいです。

細かい変更点

次のような細かい点にも対応しました。このへんは単に置換するだけなので軽く箇条書きで済ませます。

  • triggerメソッドがない
    • trigger('click')を使わず、clickメソッドを使うようにした
  • ウィンドウのリサイズのお作法が違う
    • before: Capybara.page.driver.browser.resize(320, 580)
    • after: Capybara.current_session.current_window.resize_to(320, 580)
  • ブラウザのダイアログが表示される部分は、polgergeistでは自動でacceptされるが、headless chrome(というかselenium webdriver)ではどのように対応するかをaccept_alert {} みたいに書く必要がある

まとめ

polgergeistからheadless chromeへの移行において、気をつける点と変更が必要な点について紹介しました。headless chromeに移行することで、今後メンテナンスが続いていくであろうという安心感が得られます。しかし、セットアップには今回紹介したようにコツをつかむ必要があります。

この記事がこれからheadless chromeへ移行する人や、移行に苦労している人にとって参考になれば幸いです。


是非読者になってください(︎ ՞ਊ ՞)︎


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

Rails未経験/経験年数が2年以内のポテンシャルエンジニア絶賛募集中です!*2

Rails開発してみたい人、新しい技術に意欲的な人、リードエンジニアに教育されてみたい(?)人、医療に関わるサービス開発を行ってみたい人、その他メドピアに興味を持った方などは、是非、コンタクトを取って頂ければと思います!

*1:PhantomJSもQtに依存しているけど、PhantomJSはバイナリが配布されているのでビルドせずにインストールできるというのが大きかった

*2:リードエンジニアも募集中です!

Rails × ECS でオートスケーリング&検証環境の自動構築

マリオカートでカーブを曲がるときに体を傾斜させてしまうCTO室 kenzo0107 です。

今回は 2018/04/02 にリニューアルしたイシコメの Rails × ECS についてです。

イシコメとは?

「イシコメ」は、医師10万人の声でつくるヘルスケアメディアです。
医師と一般の方々をつなげることで、医療情報格差を埋めることを目指しています。

MedPeerの10万人の医師会員に協力いただいたアンケート結果をもとに編集部で記事を執筆し、医師監修の上で配信。多くの医師の声を反映することで、より正しい情報を提供しています

ishicome.medpeer.jp

リニューアル経緯

リニューアル前は以下のような構成でした。

f:id:kenzo0107:20180618120335p:plain

  • フロントに Laravel 5
  • バックに Drupal
  • Docker on EC2
  • コンテナイメージの S3 でのプライベート管理

Docker がまだ出てきて間もない頃、当然、AWS ECS がリリースされておらず
果敢に技術的にチャレンジした痕跡が多々ありました。

ですが、 当時の構成では以下の問題がありました。

  • イメージの管理やデプロイがし辛い
  • スケーリングが考慮されてない

また、
Google 検索結果の医療や健康に関する検索結果の改善 が追い風となりトラフィックが伸び、スケーリングへの配慮が重要になりました。

webmaster-ja.googleblog.com

上記の問題を AWS ECS・ECR の恩恵を受けることで解決しようと考えました。

また、
開発促進をすべく、弊社で開発の知見のより多い Rails へのリプレイスを行う運びとなりました。

まず結論

f:id:kenzo0107:20180618121513p:plain

※ 左側がユーザサイド、右側が管理画面になります。

  • イメージ管理は ECR
  • デプロイは ecs-cli
  • CloudFront > ALB > Nginx > Rails というルーティング*1
  • Tasks, EC2 をオートスケーリング
  • RDS Aurora MySQL へリプレイスし Read Replica オートスケーリング

メディアサイトというサイト特性もあり、アクセスが集中しスパイクすることを鑑みて CloudFront でのキャッシュを有効にし、オートスケーリングできるところはする様にしました。

リニューアル直後の 2018年4月頃、麻疹が流行した際に麻疹関連の記事へのアクセスが急増した際は、本当にこの構成にしておいてよかったと思いました。

ishicome.medpeer.jp

続いて ECS でサービス構成するに当たって考慮したことをまとめました。

ECS への準備で考慮したこと

  • コンテナ設計
  • デプロイ
  • ロギング
  • オートスケール
  • バッチ処理
  • 検証環境

コンテナ設計

Nginx

Nginx をルーティングに挟んだのは以下の理由からです。

  • Rate limit の設定を細かくコントロールしたかった。*2
    • AWS WAF では最低でも 5分間に 2000 リクエスト以上でアクセス制限可能
  • IP 直指定回避

Rails

Rails コンテナについて以下の点を設計考慮しました。

secrets.yml

yaml_vault で暗号化・復号するようにしました。*3

  • KMS のエイリアスキーを作成し
  • そのキーでの暗・復号権限を IAM Group で管理する

こうすることで
権限の付与時には IAM Group に含める、
権限の剥奪時には IAM Group から除外する、
と管理が楽になりました。

Task Definition には基本秘密情報を載せない様にしました。*4

  • 実行コマンド
// secrets.yml の暗号化 (env: production)
yaml_vault encrypt \
  config/secrets.yml \
  -o config/encrypted_secrets.yml.production \
  --cryptor=aws-kms \
  --aws-region=ap-northeast-1 \
  --aws-kms-key-id=<kms alias> \
  --aws-profile <profile>

// secrets.yml の復号 (env: production)
yaml_vault decrypt \
  config/encrypted_secrets.yml.production \
  -o config/secrets.yml \
  --cryptor=aws-kms \
  --aws-region=ap-northeast-1 \
  --aws-kms-key-id=<kms alias> \
  --aws-profile <profile>
Dockerfile (Rails)

RAILS_ENV を渡してビルドし各環境毎に処理分けさせてます。

FROM ruby:2.5-alpine

RUN apk update \
  && apk upgrade \
  && apk add --update build-base mysql-dev nodejs tzdata git \
  && rm -rf /var/cache/apk/*

# TZ JST
RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

ENV app /work/app
WORKDIR $app

COPY . $app
RUN bundle install -j4 --retry 6 --without test development --no-cache \
  && npm install --production --no-progress \
  && mkdir -p tmp/sockets \
  && mkdir -p tmp/pids

ARG RAILS_ENV

# docker build 時に db 接続しようとする為、接続しない様、nulldb 指定
RUN chmod +x docker/on_build.sh \
  && sync \
  && docker/on_build.sh

CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

docker/on_build.sh

  • assets:precompile, assets:sync 実行時に S3 にアップロード, CloudFrontで配信 by asset_sync, fog-aws
#!/bin/sh

set -e

# docker build 時に db 接続しようとする為、接続しない様、nulldb 指定
DB_ADAPTER=nulldb bundle exec rake assets:precompile assets:sync RAILS_ENV=${RAILS_ENV}

デプロイ

ecs-cli を採用しました!

採用理由は以下の通りです。

  • ECS 向けの AWS オフィシャルのツール
  • 既存の設定した AWS credential 利用可
  • Task 定義が docker-compose.yml 形式
  • Fargate 対応可

Fargate Tokyo region が待ち遠しいです♪*5

デプロイフロー

f:id:kenzo0107:20180618125321p:plain

  1. master, develop, qa/* に merge をトリガーに CircleCI ビルド
  2. テストがパスしたら CodePipeline 開始
  3. CodePipeline で cap (production|staging) deploy 実行

CircleCI には CodePipeline の開始・更新権限のみの AWS Access Key ID, Secret Key を設定しています。

CircleCI 上からデプロイしないの?

はじめに検討したのですが以下理由により上記構成を選択しました。

  • CircleCI とビルドサーバを分けることで CircleCI はテストに専念できる。
    → デプロイが終わるまでリソースを手放さず他のビルドが遅延するのを避ける。

ゆくゆくは CodePipeline でテストも実行し全てを完結させてみてコスト比較する検証をしたいと思っています。
既にあったら教えてください

ロギング

f:id:kenzo0107:20180618130832p:plain

  1. コンテナから awslogs で CloudWatch に出力
  2. Lambda で日次で CloudWatch のロググループを S3 に保存

Rails コンテナでは環境変数に RAILS_LOG_TO_STDOUT: 1 を設定することで Rails のログを標準出力し、CloudWatch に流す様にしています。

また、お好みですが
lograge で CloudWatch 上のログの視認性が高まりました。

CloudWatch のイベントフィルターで

{$.db >= 1000}

とすることで DBで 1000 msec 以上時間を要したイベントを抽出することができます。

オートスケール

f:id:kenzo0107:20180618131419p:plain

スケールアウト時に考慮したことと

  • まず EC2 インスタンスを増やして、その後、Task を増やす
    → Task が一方に偏ってしまう等の事象が発生してしまう為です。

CloudWatch の監視設定で evaluation_periods を EC2 < Task の様に設定しました。

  • インスタンス単体の CPU 使用率等のメトリクスでなく、Service でのメトリクスで考えること
    → 一時的な偏りでなく、全体的に負荷が高い場合にスケールする、という様にする為です。

  • RDS Aurora MySQL のオートスケールは Read/Write を switch_point で参照先を切り替える様にしました。

バッチ処理

クラスタを指定し ECS Scheduled Tasks で one-off container で定期実行します。

以下 Sitemap 定期更新タスク例です。

f:id:kenzo0107:20180618132440p:plain

検証環境の自動構築

f:id:kenzo0107:20180618133934p:plain

qa/* というブランチ名で push するとそのブランチに紐づくポートで検証環境を自動で構築する様にしました。

これによりエンジニア・ディレクターの検証回数が飛躍的に増え、画面を通じてコミュニケーションすることで認識の齟齬が減りプロジェクトがより円滑に進む様になりました。

現状、インスタンスタイプ m5.large の EC2 インスタンスをスポットで利用することで価格を抑えつつ 3~5 程度の検証環境が動作しています。

構築手順

  1. ALB の空いている Listener Port 取得
  2. 空き Port を元に target group 作成
  3. target group に branch 名をタグ付け
  4. インスタンス登録
  5. ALB Listener 作成
  6. ECS Service 作成
  7. service 名を branch 名がわかるようにラベリング
  8. QA 環境へ deploy

現状不要となった QA は cap で削除用コマンドを用意してますが ブランチ削除時に自動削除される等、検討中です。

ちなみに、どのブランチがどのポートを使っているかは Slack bot が教えてくれます。

f:id:kenzo0107:20180618143727p:plain

まとめ

Rails × ECS へのリプレイスにより自動化できた部分が多く本当に運用が楽になったと感じます。

特に検証環境の自動構築は他プロジェクトでも望まれ導入を進めています。

また、Fargate の検証を実施しておりますので、その折には本ブログにまとめていきたいと思います。

以上です。
参考になれば幸いです。


是非読者になってください(︎ ՞ਊ ՞)︎


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html

インフラ構築・運用や開発プロセスの改善に携わっていただける SRE 募集中です!

SRE(サイトリライアビリティエンジニア) - 採用情報 - メドピア株式会社

Rails未経験/経験年数が2年以内のポテンシャルエンジニア絶賛募集中です!*6

Rails開発してみたい人、新しい技術に意欲的な人、リードエンジニアに教育されてみたい(?)人、医療に関わるサービス開発を行ってみたい人、その他メドピアに興味を持った方などは、是非、コンタクトを取って頂ければと思います!

*1:Nginx を挟んだ理由はコンテナ設計で!

*2:時折、 /phpmyadmin のようなパスでアクセスしてくるような攻撃が1分間に 200 リクエスト程きますが、 AWS WAF の based rule では防げません。

*3:イシコメ リニューアル直後に Rails 5.2 credentials が出た為、secrets.yml での秘密情報管理をしています。

*4:EC2 上で ssm で値を取得し環境変数に設定する方法もありますが Fargate の時どうするのか検証仕切れず、一旦 secrets.yml に寄せる様にしました。

*5:本記事執筆時に Fargate Tokyo Region の 2018年7月 予定がアナウンスされる神タイミングでしたので、執筆を急ぎました

*6:リードエンジニアも募集中です!

RubyKaigi2018に参加してきました

こんにちは!メドピア歴1年、Railsエンジニアの小林です。

今回のブログでは、5月31日〜6月2日に仙台で開催されたRubyKaigi2018に全日参加してきましたので、その感想をお届けしたいと思います。

f:id:marikokobayashi:20180605163917j:plain

このブログを書いた人の背景

私はメドピアに入社することになって初めてRubyを書き始めました。

昨年9月に開催されたRubyKaigi2017時はまだRubyを書いて数ヶ月という状態だったのですが、実はそのときも参加しており、今回のRubyKaigi2018は2回目の参加になります。

なぜ前回、まだ右も左も分からない状態で参加したかというと、自分が仕事で一番使っているプログラミング言語の大規模な国際会議が日本国内であるのであれば、外から盛り上がりを見ているよりは実際に体験してみたいと思ったからです。

また、Rubyを書いている仲間を見つけて交流したいという気持ちもあり、参加しました。

今年は、去年のそんな私の姿が評価されたこともあってか(?)、RubyKaigiへ行きたいと表明したメンバー全員分の参加費・交通費・宿泊費が会社で補助していただけることになりました!

tech.medpeer.co.jp

本当は見たセッションすべての感想を書けたら良いのですが、長すぎてまとまりがなくなってしまいそうなので、本記事では特に印象が強かった部分だけを書かせていただきます。

個々のセッションについてもう少し知りたい方は、RubyKaigi中に参加したメンバーがそれぞれ見たセッションを速報としてブログにまとめましたので、こちらをご参照いただければと思います。

tech.medpeer.co.jp

(2日目以降のブログは上の記事にリンクがありますので、辿って下さい!)

速報性を大事にしていたため、突っ込みどころもあるかと思いますが、これはおかしいだろうという箇所がありましたら、ご指摘いただければと思います!

Matz基調講演について

1日目で特に印象に残ったことは何と言ってもMatzのキーノートです。今回は諺についてのお話。

Matzが大事にしている諺として下記の3つが挙げられていました。

  • 名は体を表す
  • 時は金なり
  • 塞翁が馬

名は体を表す

Rubyのカンファレンスに参加するようになってから、Matzのお話を聴講したことは何回かありますが、そのたびにMatzは毎回「名前重要」という話をしているということに気づきました。

名は体を表す、といいますが、確かにRubyがRubyという名前ではなかったらその言語に対して持つ印象も違っただろうと思います。持つ印象が違えば、コミッターやユーザーがその言語をどのように扱うかも変わってくると思います。そうであれば今のRubyとは全く別の言語になっていたかもしれません。

正しい命名付けを行うことは、正しく概念を理解していることでもあります。良いプログラマーは、良い命名者でもあります。

「概念がどういう局面で使われるか十分に理解していれば比較的簡単に名前をつけられる」「よいプロジェクトの名前はコミュニティの旗印になったり、求心力になったりする」、とMatzは説いていました。

ちなみに、もし今のネット社会の中で新たに言語の名をつけるとしたら、Rubyはググラビリティが悪いのでつけないだろうということでした。

確かにMatzが2014年に作ったもう一つの言語であるStreemは、実在する単語を少しいじったものであるので、ググればすぐMatzのリポジトリが出てきてググラビリティが良いですね。ちなみに某言語…おっとだれか来たようです。

時は金なり

時間は、誰にでも平等に1日24時間与えられています。時間をどう使うかは、どういう人生を歩むか、を決定づけます。

プログラミングで自動化できることは自動化していったほうが生産性の高い人生を歩むことができると思います。

Rubyには便利なメソッドがたくさんあるので、時間を有効活用するのを助けてくれる傾向があります。

また、プログラミングにおいては、問題が発生したときに早く解決できたほうが時間の短縮になります。問題が自分ひとりでは解決できないこともあると思いますが、そういった意味では「ほとんどの人にとって『質問に答えてくれる人が身近にいる言語』が一番いい言語」でしょう。

Rubyはコミュニティが活発なので、質問に答えてくれる人を比較的見つけやすい環境であると言えると思います。

処理速度については、Rubyは遅いと言う人も居ます(Rubyでググると「Slow」が候補として上がっています)が、現在もコミッターの方々がパフォーマンス改善を続けています。

書きやすさと処理速度についての良いバランスを取っている言語だと思います。

塞翁が馬

塞翁が馬というのは、中国の故事『准南子』に出てくる小話のことで、「人間万事塞翁が馬」とも言われているかと思います。

内容は、人生には良いことがあってもその後悪いことが起きることもある、悪いことがあってもその後に良いことが起きることもある、先のことはわからない、というお話です。

読む人によっては、良いことがあっても浮かれすぎるな、悪いことがあっても落ち込みすぎるな、という解釈もあるかと思います。

Rubyは多くの人に使われる言語となりましたが、先のことはわかりません。

他の言語に取って代わられ、Rubyユーザーが縮小してしまう未来もないとは言い切れません。

そんな中でも今後も時代に合わせた進化を続けることで、時代に合わせたRubyのあり方を模索していきたい、といった趣旨のお話でした。

私も実は個人的に以前からこの諺を気に入っており、人生そのものだと感じているため、座右の銘の一つとしておりました。

Matzの考え方には深く頷ける部分が多くあり、そのように強い信念、理念を持った言語設計者が作った言語だから多くの人に広まったのだなあと改めて思いました。

--

また、セッションの締めにはMatzは「Rubyに『言語仕様として』型が入ることはない」とおっしゃっていました。型推論についてが今議論されていますが、それはあくまで推論であるということです。

Rubyを使う人は型を使いたくない人も一定数いると思うので、もし言語仕様としてそこを変えてしまうとしたらかなりの既存のユーザーが置いてけぼりになるのではないかと思います。

時代の流行を気にしながらも、Rubyとしてそぐわないところは無理に採用しないというMatzの意志を感じました。

Ruby Committers vs the Worldについて

2日目の締めはRuby Committers vs the World。

壇上にRubyコミッターが勢揃いする光景は圧巻です。

コミッターたちが、寄せられた質問などについて答えてくれました。

このようにRubyコミッターが勢揃いすると、Matzの意見が必ずしも通っているわけではないということに気付かされます。

特にMatzが最近Ruby2.6に追加したというエイリアス「then」の命名の良し悪しについては熱い議論が交わされていました。

普段Rubyコミッターたちがどのように議論してRubyを改善・機能追加しているかの様子が垣間見れたような気持ちになります。

Matzが愛されて(いじられて?)いる様子には、ただのいちRubyユーザーである自分も何だかつられて笑ってしまいました。

f:id:marikokobayashi:20180604155312j:plain

セッションの見かたについて

今回2回目の参加でしたが、去年の1回目の参加後は「なにがなんだかわからない…参加費を無駄にしてしまったのでは…」と絶望して帰ってきたのですが、2回目に参加してみると「あれ、もしかしたら前回の反省がちょっと生きているかも、少しは成長したかも」と思いました。

具体的には、「すべてを1回でわかろうとしない、絶望しない」というところが大事なポイントかもしれません。

イベントに参加する人は、Rubyの内部実装やC言語を深くわかっている人だけでなく、私のようなRailsを使っているだけというWeb系エンジニアも多くいると思いますが、そんな中でも「知識不足ゆえにまったく理解できなそうなセッション」と「Web寄りやビジネス寄りで、現時点での知識でも少しは理解できるところがありそうなセッション」があると思うのです。

割り切って「現時点で少しは理解できるセッション」に絞って聞いてみるのも戦略としてありかなと思いました。

(もちろん自分の知識不足に喝を入れるために全然わからないセッションを聞きたい、という人はそれもアリかと思います)

また、セッションは全部の時間で聞こうとすると1日に6~7個聞くことになると思いますが、どれも全部内容が濃く、それぞれを深く理解するためには、無理に全部の時間に聞かなくても良いと思いました。

ありがたいことに、最近はセッションの動画がのちに公開される(今年も公開していただけるようです)ので、会場では体調を整え、自分の体力と相談しながら適度に休憩を入れるのも、長い3日間を乗り切るポイントかなと思います。

ステッカーについて

メドピアは今回ゴールドスポンサーとして参加したので、ステッカー置き場にステッカーを置かせていただきました。メドピアのキャラクター、メドベアちゃんがRubyと戯れている様子がステッカーになっています。

今後Ruby関連のイベントでスポンサーする機会があればそのときにも持っていくと思うので、今回もしもらい損ねたという人がいましたら、その機会に是非!

f:id:marikokobayashi:20180604154754j:plain

英語について

セッションの大半は英語なため、英語力に課題を感じました。RubyKaigi直前は英語力を高めるため下記のPodcastを聞いてみた*1のですが、それでもやはりセッションを1回聴講しただけで理解するのは難しいなと感じました。

Ruby Rogues Archive | Devchat.tv

5by5 | Ruby on Rails Podcast

のちにセッションの動画が上げられたら、繰り返し見て、理解を深めたいと思います。

観光について

RubyKaigiは、東京開催以外だと地元の美味しい料理を楽しんだり観光を兼ねられるのが1つの魅力でもありますね。

2日目の夜は、会社のメンバーみんなで牛タンを食べてきました。

予約していたお店は行ったことがある人はおらず不安でしたが、結果的にとても美味しく皆満足していました!

f:id:marikokobayashi:20180604154814j:plain f:id:marikokobayashi:20180604154845j:plain

ちなみに最終日翌日は日曜でしたので、個人的に仙台周辺を観光してきました。

時間のある方は松島まで行かれた方もいらっしゃるようですが、私は以前から行ってみたかった仙台城跡を見てきました。

城跡なので実際にお城の中に入れるわけではないのですが、伊達政宗公の騎馬像の実物を目の前にしたときは改めて「仙台に来たんだな〜!」と感じました。

f:id:marikokobayashi:20180604154920j:plain

仙台城跡まではバスで行くこともできましたが、国際センター駅から歩いていくと傾斜がある山を少し登ることになり、運動不足気味なエンジニアには適度な運動になるかもしれません!(女性や子供でも登れていたので、それほど苦しいレベルではないと思います)

全体を通して

今回も、RubyKaigiにはコミッターからRubyを最近書き始めた人まで、たくさんのRubyを使う人が参加していました。

そんな中で、自分がお話させていただいた中では「RubyKaigiに一度参加してみたけれど、自分の目指す方向性はRubyを極めたいわけではなくビジネスに使いたいだけだった」と改めて認識する方がいらっしゃったり、「参加してみてもっとRubyの内部実装を知りたくなった、C言語を勉強したくなった」という方がいらっしゃったり、参加した方の分だけそれぞれの感想があり、それぞれ素敵だなと思います。

セッションを聞くことも楽しいですが、このようにたくさんの方がそれぞれのRubyとの関わり方をしているのだということに思いを馳せられるのもRubyKaigiの良いところだなあと思いました。

f:id:marikokobayashi:20180605163304j:plain

セッション以外にも、休憩時間にもブースを見て回ったり、ペアプロの様子を見たり、サイン会があったり、朝食やお弁当を各スポンサーさんに出していただいたり、セッション終了後は1-3日目それぞれPartyに参加できたりと盛り沢山な内容で、Kaigiでもあり、お祭りでもあるような3日間でした。

個人的には、去年は知り合いが少なく少し寂しかったのですが、今年は会社のメンバーがたくさん参加したので、あまり寂しい思いをすることがなく過ごすことができました!

f:id:marikokobayashi:20180605163344j:plain f:id:marikokobayashi:20180605163835j:plain

また、同じコミュニティに顔を出し続けると少しずつ知り合いが増えてきて、いろいろな方とお話できるようになり、前回参加したときより少しではありますが自分がRubyistな方々と近づけたのかなと思いました。

来年は福岡、4月18〜20日の開催とのことです。来年も多くのメドピアメンバーが参加し、また多くのRubyistと交流できる会になれば良いなあと思っています。

*1:理解したとは言っていない

swift4.1へのバージョンアップとつまづいたこと

こんにちは。メドピアのエンジニアの保立です。
メドピアでは、医師専用コミュニティサイト「MedPeer」のiOSアプリを3月26日にリリースしました。
詳しくはこちら

メドピアのiOSアプリは、swift4.0で開発を始めましたが、先日swift4.1にバージョンアップしたので、今回のブログ記事では、swiftのアップグレードのためにやったことについて紹介します。

swift4.0から4.1の変更点

3月29日にswift4.1がリリースされました。変更内容とメドピアでの対応について記載します。

変更1) SE-0157 Support recursive constraints on associated types (関連する型の再帰的制約をサポート)

変更内容

swift4.0以前では、「プロトコル内で、プロトコル自身を再帰的に参照する」ことを制限していましたが、swift4.1以降では、その制限が解除されました。

// swift4.0以前では、コンパイルエラーとなる
protocol Sequence {
    // `SubSequence` が、再帰的に `Sequence` を参照している
    associatedtype SubSequence: Sequence
        where Iterator.Element == SubSequence.Iterator.Element, SubSequence.SubSequence == SubSequence

    func dropFirst(_ n: Int) -> Self.SubSequence { ... }
}

ちなみに、swift4.0以前では以下のように実装する必要がありました。わかりづらい。。。

protocol Sequence {
    associatedtype SubSequence

    func dropFirst(_ n: Int) -> Self.SubSequence { ... }
}

struct SequenceOfInts : Sequence {
    func dropFirst(_ n: Int) -> SimpleSubSequence<Int> { ... }
}

struct SimpleSubSequence<Element> : Sequence {
    typealias SubSequence = SimpleSubSequence<Element>
    typealias Iterator.Element = Element
}
対応

プロトコル内でassociatedtype を使って再帰的に参照している箇所を修正。

変更2) SE-0185 Synthesizing Equatable and Hashable conformance (EquatableとHashable適合性の合成)

swift4.1化のメインで、最もメリットを享受できる変更だと思います。

変更内容

Equatable / Hashable プロトコル内に実装する必要があった決まり文句を、暗黙的に実装してくれるようになりました。

struct Question: Equatable {
    let id: Int64
    let title: String

    // swift4.1以降では、 '==' の実装が不要になる。便利!
    public static func == (lhs: Question, rhs: Question) -> Bool {
        return lhs.id == rhs.id && lhs.title == rhs.title
    }
}

今までは、public static func == (lhs: Element, rhs: Element) -> Bool { ... } を明示する必要があったため、プロパティの追加、削除、または変更があった場合、この演算子を更新しなければならないことと、手作業で記述する必要があるため、漏れや誤植で間違ってしまう可能性がありました。
Hashable の場合、以下のようになります。

struct ForumTag: Hashable {
    let id: Int64
    let name: String

    // swift4.1以降では、 '==' も 'var hashValue: Int{} ` も実装が不要になる。超便利!!
    var hashValue: Int {
        return id.hashValue
    }

    static func == (lhs: ForumTag, rhs: ForumTag) -> Bool {
        return lhs.id == rhs.id
    }
}
対応

Equatable / Hashable プロトコルに実装された ==hashValue を使うように修正。

変更3) SE-0187 Introduce Sequence.compactMap(_:) (Sequence.compactMap(_:)の導入)

変更内容

Sequence.flatMap を廃止し、 同じ機能として、 compactMap(_:) を導入する。

対応

コンパイルエラーになったら対応。(MedPeerアプリでは Sequence.flatMap を使っている箇所はありませんでした)

変更4) SE-0188 Make Standard Library Index Types Hashable (標準ライブラリのインデックス型をHashableに)

変更内容

swift4から KeyPath という機能が実装されました。 KeyPath については Qiita が参考になると思います。

対応

MedPeerアプリでは、KeyPath使ってないので対応不要。

(おまけ) SE-0143 Conditional Conformance (条件付き適合)

この変更は、swift4.2以降で実装されます。

変更内容

URL: https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md

型引数が特定の要件を満たす場合にのみ、ジェネリック型が特定のプロトコルに準拠するという概念を表現できるようにします。

例えば、Array に対して、その要素がEquatableである場合に、Equatableプロトコルを実装することができることを表したい時、以下のように実装します。

extension Array: Equatable where Element: Equatable {
  static func ==(lhs: Array<Element>, rhs: Array<Element>) -> Bool { ... }
}

swift4.1以前で、上記のような実装をすると、「 'Array' 自体がEquatableでない」という理由で、コンパイルエラーになります。

error: extension of type 'Array' with constraints cannot have an inheritance clause
extension Array: Equatable where Element: Equatable { }
^                ~~~~~~~~~

これが解決されます。 Standard Library Adoption に記載されている通り、 OptionalArrayEncodable / Hashable 適合など、既存のライブラリにはすでに内部で使われています。

swift4.1に移行する時につまづいたこと

swift4.1への移行はほぼスムーズにできましたが、1点だけ修正が面倒なことがありました。Firebaseなどのサードパーティーツールで、以下のようなWarningが出ることです。

/path-to-app/vendor/FirebaseCoreDiagnostics.framework/FirebaseCoreDiagnostics: Failed to parse executable: Unknown header: 1918975009

メドピアでは、ライブラリ管理にCarthageを使っていたのですが、FirebaseなどはCocoaPodsにのみ対応していたので、FirebaseSDKを直接ダウンロードしていました。それにより、Firebaseのバージョン管理が適切にできていないのが原因でした。
結局、Carthageで管理できないライブラリは、CocoaPodsで管理することで解決しました。 この修正により、以下のライブラリをCocoaPodsで管理するように修正しています。

- Firebase/Core
- Fabric
- Crashlytics
- Repro
- R.swift

まとめ

今回、メドピアでは初めてswiftのバージョンアップを実施しましたが、今後もバージョンアップ等で得た知見をブログに執筆していきます。
iOSアプリはまだリリースしたばかりで、開発したいこと/すべきことが山ほどあるので、メドピアに少しでも興味を持っていただけたら遊びに来てほしいです。


是非読者になってください(ง `ω´)ง


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html