dely engineering blog

レシピ動画サービス「kurashiru」を運営するdelyのテックブログ

RailsのCIにかかる時間を少しづつ改善している話

はじめに

こんにちは、delyでサーバサイドエンジニアをやっている山野井といいます。

kurashiruではサーバーサイドにRailsを使用しておりテストはRspecで書かれています。
CIはgithubリポジトリへのpushをフックしてAWS CodeBuild上でテストを走らせています。
またCI上のテストはparallel_tests gemを利用した並列化を行っていて、8プロセスで動いています。

弊社ではプロダクトの品質を保つ為、CIに通らないとデプロイできないルールを設けていまして、CIが完了するまでに時間がかかるとその分デプロイまでの時間もかかってしまうので1分でも早めたい気持ちがあります。

今回はアプリケーションコードには手を加えず、AWS CodeBuild上のCIの実行時間を少しづつ改善している話をしたいと思います。

実践

まずはCIの実行時間を改善する前にどこに時間がかかっているのかを把握する必要があります。

AWS CodeBuildには以下の11項目からなるphasesという概念があります。

SUBMITTED
QUEUED
PROVISIONING
DOWNLOAD_SOURCE
INSTALL
PRE_BUILD
BUILD
POST_BUILD
UPLOAD_ARTIFACTS
FINALIZING
COMPLETED

CodeBuildが実行されると各phaseが順番に実行されるようになっています。
この中で INSTALL, PRE_BUILD, BUILD, POST_BUILDのphaseはプロジェクトのルートに置いた buildspec.ymlに独自に処理を書くことができます。
以下がその例です。

version: 0.2
phases:
  install:
    commands:
      - service mysqld start
  pre_build:
    commands:
      - bundle install
      - bundle exec rake parallel:create
      - bundle exec rake parallel:migrate
  build:
    commands:
      - bundle exec parallel_rspec spec

AWS CodeBuildでは以下の画像のようにAWSのコンソールから各phaseどれだけの時間がかかっているのかを見ることができるので、それを見ながら改善策を考えていきます。

f:id:yamanoi-y:20190627143731p:plain


弊社では PRE_BUILDフェーズで bundle install やdbの作成、migration処理を行っているのですが、ここに10分ほど時間がかかっていました。
よく見ると処理時間のほとんどがmigrationにかかっていることがわかりました。
bundle exec rake parallel:migrateコマンドはcpuのコア数分プロセスを立ち上げコマンドを実行するため、migration処理が8プロセスで実行されていました。
これを1つのDBのみmigrationを行い、migration後のダンプを取得し残りのデータベースに流し込む処理に変えることで10分ほど短縮することができました。

  pre_build:
    commands:
      - bundle exec rake parallel:create
      - bundle exec rake parallel:migrate[1]
      - mysqldump -u root -prootpassword test > dump.sql
      - bundle exec parallel_test -e 'mysql -usomeuser -psomepassword test$TEST_ENV_NUMBER < dump.sql'


Before
f:id:yamanoi-y:20190627145817p:plain
After
f:id:yamanoi-y:20190627145920p:plain


またテーブルに変更が無い限りは前回のDBの状態を用いても問題ないため、db/migrateのgitのhash値からキャッシュキーを生成し、ダンプそのものをキャッシュすることで
初回のmigration処理自体をスキップすることもできます。

pre_build:
  commands:
    - git log --pretty=format:'%H' -n 1 -- db/migrate > ~/mysql-dump-checksum
    - mkdir -p ./.mysql-dump
    - |
      if [ -e "./.mysql-dump/$(cat ~/mysql-dump-checksum).sql" ]; then
        bundle exec parallel_test -e 'mysql -usomeuser -psomepassword test$TEST_ENV_NUMBER < "./.mysql-dump/$(cat ~/mysql-dump-checksum).sql"'
      fi


次に BUILDフェーズですが
BUILDフェーズでは主にRspecの実行を行っています。
今回はアプリケーションコードに手を加えない方法で高速化することを考えていたので特に変更は加えていません。
Rspec自体の速度を改善するには、一般的に遅いテストの洗い出しや、無駄なレコードの作成を行わないこと、無駄な通信を行わないことが挙げられます。
kurashiruでもここの最適化はさほど行っておらず、改善の余地があるので適宜改善していきたいと思っています。

最後に POST_BUILDフェーズですが、ここも5, 6分ほど時間がかかっていました。
AWSのコンソールからビルドログをよく見るとCodeBuildのキャッシュ機能によるs3へのアップロードに時間がかかっていました。
まだ設定の変更を完全に取り込むことはできていないのですが、CodeBuildのキャッシュタイプをs3から最近使用できるようになったローカルキャッシュに変更することで速度改善を試みています。
CodeBuildのローカルキャッシュは今年から利用できるようになった機能で、今まではキャッシュした結果をs3へアップロードしていたものをビルドホスト内に保持することができる機能です。
aws.amazon.com

この機能を有効にすることでs3への通信コストがかからなくなるため、ここにかかる時間を0にすることが確認できています。

最後に

1つ1つが地味な改善ですが、デプロイまでの時間を短縮でき、従量課金にかかる費用も節約できるので今後も時間を見つけて改善していきたいと思います。
最後までお付き合いありがとうございました。

プロジェクト管理を自動化してみたけど思うようにはいかなかった話

こんにちは。delyでAndroidエンジニアをしているkenzoです。
今回はAndroidの話ではなく、担当している別のプロジェクトの管理をいい感じにしようと思っていろいろやったけど、そんなにうまくいかなかった。という感じの内容です。

どんな話

「他部署の依頼でコンテンツを作成するプロジェクト」の管理を自動化した結果、うまくいったこと、失敗したことについてのお話です。 f:id:kenzo_aiue:20190507144040p:plain:w250

なんで自動化したの

今回のお話の背景

弊社では開発部が他の部署から依頼を受けてLP等のWebページを作成しています。
私は他の部署と開発部内の実装者とのやりとりの間に入って管理する取りまとめのようなことをしてきました。
やっていたことをざっくり言うと、依頼を受け、実装者に依頼して、配信の準備をする。という簡単なお仕事で、元々はスプレッドシートで管理をしていました。

発生した問題

新たに高い頻度でコンテンツを作る施策の開始や、開発部側で業務委託の方に実装をお任せするようになる等、次第にやりとりや確認・管理しなければいけないことが増えていきました。
そのため、業務の中でこれらのために使うリソースが増えてきたり、抜け漏れが発生しそうな気配を感じるようになったりと、既存のスプレッドシートを手作業で確認・更新するのにも限界を感じてきました。

どんな自動化したの

GASを使って下記の処理が定期的に実行されるようにしました。

  1. コンテンツの内容等が管理されているスプレッドシートから実装に関するものを抽出
  2. githubのプロジェクトに1をtodoとしてissueを追加
  3. 関係者に共有しているカレンダーに1を追加
  4. 開発側や業務委託の方と共有しているスプレッドシートに1を追加
  5. 素材・成果物が揃ったり、配信が迫ったタイミングでslackに通知
  6. 1の変更に合わせて2, 3, 4の内容を更新
  7. その他もろもろ、、、

これらの組み合わせで、今回のプロジェクトにおける業務フローのある程度の部分を自動化することができました。
f:id:kenzo_aiue:20190507174721p:plain:w250

うまくいったこと

作業削減

issueやカレンダーへの追加や更新を手動で行うという、そこそこ手間のかかる作業(定期的にスプレッドシートを確認して内容を様々な場所に追加・更新)をなくすことができました。

抜け漏れ防止

依頼が発生したり、配信日が近くなったら関係者のslackチャンネルにbotで通知を送りました。
抜け漏れを防ぐことができそうで、日々の心配事が減りました。

うまくいきそうだったこと

やりとり減らせそう

必要なタイミングで必要な人に通知を送ることで、やりとりが減らせそうな気がしていました。

うまくいかなかったこと

想定以上の戻り作業の発生

今回行った業務フローの自動化ではそれぞれの工程が完遂されているかのチェックが抜けていたため、次の工程に進んでからの戻りが度々発生しました。
その結果、その都度手動でのやりとりを行うことになってしまいました。

業務委託先の方との信頼を築けなかった?

あまりやりとりをしていないまま、業務委託先の方とのやりとりもbotでの運用に変更してしまったために信頼を築けなかったかもしれません。
そのためか、締切が近くなっても連絡がつかず、紹介してくれた別の社員に連絡をお願いすることになってしまい、コンテンツが配信ぎりぎりの完成となってしまったこともありました。

こうしたらよかったのかも

「想定以上の戻り作業の発生」に対して

戻りを完璧に防ぐのは不可能だし、いろいろな手立てを講じる工数もあまりありませんでしたが、頻繁に発生していたシンプルなミス(素材の不足や工程忘れ)についてくらいはチェックする仕組みを入れておく必要があったように思います。
また、手順をもっと明確にして初めに伝えておくことや、困った時に参照できるものを作っておくとかでも防げた可能性はあります。
他の業務でも同じことが言えそうですが、必要な内容を伝える・参照しやすくしておくことは大事ですね。

「業務委託先の方との信頼を築けなかった?」に対して

もう少しやりとりをしたり、直接会う等して信頼関係が築けてからslack botの運用を始めるべきだったと感じました。
または、slack botでの通知はあくまで自分へのリマインドにとどめ、やりとりは自分で行う。といった方法を検討してもよかったのかもしれません。
信頼関係のないままslack botだけで完結させようとしてしまったため、なにかあった場合に気軽に言ってもらえるような心理的安全性を高められなかったのではと思います。
このような信頼関係は人と関わる業務全てにおいて大切なことだと思いますが、今回は効率を求めて自動化を進める上でおろそかにしてしまったのが反省点です。 f:id:kenzo_aiue:20190507173532p:plain:w300

まとめ

自動化によって便利になることはいっぱいあるので、これからもどんどんやっていこうと思います。
ただ、自動化するのが難しい部分も多々あると思います。もちろんそこは手作業等で補う必要があるのですが、ともすると、省いたかたちで自動化してしまうこともあるかもしれません。
そうすると、せっかく自動化したのに後々工数を取られたり、ミスに気付かないまま進んでしまうということも考えられます。
特に、今回私がミスった信頼関係における部分、人間的なコミュニケーションを省いてしまうと、理論的にはうまくいきそうに思えても、どこかで綻びが生まれて問題が起きることがありそうです。
業務を効率化する時でも、あえて人の手を介した方がうまくいくこともあると思うので、その辺りを頭に入れつつ今回の反省を活かし、これからも自動化を進めていきたいと思います。

Search Engineering Tech Talk 2019 Spring に登壇しました #searchtechjp

f:id:sakura818uuu:20190426185916p:plain

こんにちは。開発部のsakura(@818uuu)です。

2019年4月23日に開催されたSearch Engineering Tech Talk 2019 Springに登壇させていただきました。

会場は南青山にあるNAVITIMEさんで行われました。
NAVITIMEさんにははじめて行ったのですが、すごくきれいな会場で発表しやすい環境でもあったのでとても助かりました。
ありがとうございました!

あとお茶もらいました。ありがとうございます。

登壇は久々だったのですごく緊張していました。
25分枠だったのでタイムスケジュールのことがものすごく不安でした。

登壇は3名いて私は3番目の発表予定でした。
一人目はNAVITIMEさん、二人目はFessを作ってる方の発表でした。
どちらのサービスもばりばり使ったことあって好きな検索サービスだったので、そんな検索の中の人と一緒の舞台にたてるのが嬉しかったです。

お二人の発表資料はこちらになります。

www.slideshare.net

www.slideshare.net

お二人の発表が終わり緊張は頂点でした。↓登壇直前のツイート

登壇はなんとか無事終えることができました。(よかった……)
すごく話しやすい場を聞いてくださる方が作ってくださったので話しやすかった&楽しかったです。
本当にありがとうございました。

登壇資料はこちらになります。

登壇を終えてハッシュタグを拝見させていただいたのですが色々な感想をつぶやいてくださり本当にありがとうございます。

#searchtechjp - Twitter Search

懇親会でもたくさん検索のお話ができてよかったです: )
すごくすごく貴重な経験をさせていただきました。ありがとうございました!

1px の変化も見逃さない!ビジュアルリグレッションテスト導入で快適フロントエンド開発

こんにちは!dely でフロントエンドの開発をしています @all__user です。
今回は kurashiru のフロントエンド開発に導入されたビジュアルリグレッションテストについてご紹介したいと思います。

【反応を多くいただいた点について記事の最後に追記しました】

目次

ビジュアルリグレッションテストとは

ある変更を加える前後でスクリーンショットを作成し、それらを比較することで意図しない挙動が無いかを検証するテスト手法です。
最終的に描画されたピクセルに少しでも変化があれば検出することができるため、技術スタックを選ばず包括的にテストできます。
検出できるものは限られていますが、スタイルのチェックはもちろん、機能や動作を検証するためのテストとしても非常に優れています。

  • 検出できるもの⭕
    • お気に入りボタンが表示されない
    • 2ページ目以降がエラーページになる
    • 画像が荒い
  • 検出できないもの❌(検証に向かないもの)
    • お気に入りボタンがクリックできない
    • 2ページ目以降へ遷移できない
    • 画像のalt属性が設定されていない

現在の kurashiru のように Rails のテンプレートと Vue を併用しているような状況でも、全体をカバーできます。技術スタックの移行フェーズでは特に効果を発揮すると思います。
テストはどこにどれだけのコストを割くかというバランスが非常に難しいと感じます。
まず最初に導入するテストとしてビジュアルリグレッションテストはとてもおすすめです。

導入の背景

kurashiru は Rails アプリケーションとして Sprockets + Slim + jQuery + CoffeeScript + SCSS で開発されてきました。
ログイン機能の開発をきっかけに部分的に SPA (Single Page Application) を導入し、現在は Webpacker + Vue + TypeScript + SCSS での開発へと移行中です。

tech.dely.jp

そのようにして SPA 化を進めていく中で、機能開発が優先され、なかなかテストに手を付けられないという状況が続きました。
すでにある Capybara + RSpec によるテストケースだけでは、SPA 部分の開発が既存部分へ与える影響や共通モジュールを変更する際の影響の検知を担保できず、テストケースを増やして対応しようとすると、非常にコストがかかるだろうと考えられました。

なんとかしなければと思いつつ開発は続き、規模が少しづつ大きくなるにつれ、手動テストでの動作検証コストが無視できない大きさになってきました。
そして、今まさにテストが必要というところまで来ていると判断しビジュアルリグレッションテストを導入することにしました。

フロントエンドのテスト?

フロントエンドのテストは難しいです。
一口にテストと言っても何を検査し担保したいのかによってテスト手法もまちまちです。

  • ロジック
  • 操作
  • 要素、テキスト
  • スタイル
  • ブラウザ間の差異

HTMLの構造や見た目に依存したテストは、正しくテストを書く難易度も高く、しかも変更により壊れてしまう可能性も高いため、得られるメリットがコストに見合わないと感じることもあります。

SPA移行前後の比較

今回一番達成したかった目的は、SPA移行前後のコードに対する検証です。
何かしらのテストの必要は感じていましたが、移行前後のコード全てに対してテストを追加するコストはかけられません。

ビジュアルリグレッションテストは期待される動作の定義と検証を簡単に行うことができます。
期待される動作の定義はスナップショットを撮るだけです。
現在の状態を期待される動作として定義し、移行後の状態と比較することで、移行前後のコードの挙動に変化がない(またはある)ことを確認できます。

ツール

調べてみたところ様々なツールがありました。
PhantomJS は開発が終了しているため、それをベースにしたツールは今回選択肢から外すことにしました。
また、マルチブラウザのテストを考慮すると Selenium ベースが望ましかったのですが、学習コストやフロントエンドエコシステムとの相性の観点から Node.js, Puppeteer を採用している BackstopJS を採用することにしました。

reg-suit

スナップショットの比較とレポートの作成に特化したツールです。
スナップショットの生成をどのように行うかは自由なため、Headless Chrome を使ったり Selenium ベースのツールを使うなど柔軟に対応できそうです。
CIにも組み込みやすそうです。

github.com

Loki

Storybook に特化したツールです。
スナップショットの生成からレポートの作成、結果の承認まで全部入りのツールです。
ページ全体のスナップショットを利用するテストとは違い、コンポーネントの単体テストという位置づけです。
kurashiru でも最近 Storybook が導入されたのでいずれ試してみたいです。

github.com

Wraith

Ruby ベースのツールで、GitHubのスターも一番多いようです。
スナップショットの生成からレポートの作成、結果の承認まで全部入りのツールです。
メインは PhantomJS ベースのようですが Chrome にも対応しているようです。
BBC News が作っています。

github.com

BackstopJS

スナップショットの生成からレポートの作成、結果の承認まで全部入りのツールです。
今回はこのツールを採用しました。
Node.js 製でメインのブラウザに Puppeteer を採用しています。
Puppeteer の API をそのまま利用できるので、テストケースを async / await で書くことができます。
hideSelector や removeSelector で特定の要素を非表示にしたり取り除いたりすることができたりと、いい感じのパラメータが多く用意されています。

github.com

テストのフロー

f:id:delyjp:20180802000550p:plain

GitHub + CodeBuild + BackstopJS

CodeBuild の GitHub 連携の機能を利用し、特定のルールにマッチするブランチ名が push された時にテストが走るように設定しました。
CodeBuild ではテストに使用する Docker イメージを指定することができるのですが、BackstopJS が提供している Docker イメージがあり、これを利用しています。
これで CI 環境 で Puppeteer を動かすための手間はかかりません。
一つだけ注意が必要なのは、そのままだと日本語フォントが入っていないため、フォントの入ったイメージを作るか、install フェイズなどでフォントを入れる必要があります。

version: 0.2

phases:
  install:
    commands:
      - apt-get update -y
      - apt-get install -y apt-transport-https
      - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
      - echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
      - apt-get install -y yarn
      - apt-get install -y fonts-ipafont-gothic fonts-ipafont-mincho # 日本語フォントをインストール
      - apt-get install -y python-dev
      - curl "https://bootstrap.pypa.io/get-pip.py" | python
      - pip install awscli

  pre_build:
    commands:
      - REGRESSION_TEST_BRANCH_NAME=$(git branch -a --contains $CODEBUILD_SOURCE_VERSION)
      - mkdir -p ./.yarn-cache
      - yarn install --cache-folder ./.yarn-cache

  build:
    commands:
      - yarn reg:testcase-gen # スプレッドシートから backstop.json を生成
      - backstop reference # リファレンスのスナップショット
      - backstop test # テストのスナップショット

  post_build:
    commands:
      - aws s3 cp --recursive backstop_data/html_report s3://xxxxxxxxxxxxxxxx/$CODEBUILD_BUILD_ID/html_report/
      - aws s3 cp --recursive backstop_data/bitmaps_reference s3://xxxxxxxxxxxxxxxx/$CODEBUILD_BUILD_ID/bitmaps_reference/
      - aws s3 cp --recursive backstop_data/bitmaps_test s3://xxxxxxxxxxxxxxxx/$CODEBUILD_BUILD_ID/bitmaps_test/
      - yarn reg:slack # Slackに通知

cache:
  paths:
    - '.yarn-cache'
    - 'node_modules/**/*'

ステージング環境

Reference と Test には kurashiru の開発で利用しているステージング環境を使用しています。
開発者が任意のブランチをデプロイできるようになっています。
データベースの内容によってスクリーンショットに差が出ないように、ビジュアルリグレッションテスト用のインスタンスは同じデータベースを参照するようにしています。

テストケースは Google スプレッドシートで管理

BackstopJS のテストケースは backstop.json というファイルの scenarios プロパティで設定します。

{
  "scenarios": [
    {
      "label": "recipes_show",
      "onBeforeScript": "src/on_before.js",
      "url": "https://example.com/recipes/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
      "referenceUrl": "https://example.com/recipes/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
      "delay": 3000,
      "removeSelectors": [
        ".adsbygoogle",
        ".google-ads"
      ],
      "onReadyScript": "src/on_ready.js",
      "selectorExpansion": true,
      "misMatchThreshold": 0.1,
      "requireSameDimensions": true
    }
  ]
}

もちろんこのまま利用してもよいのですが、backstop.json が巨大になるとメンテナンスが大変そうなので、Google スプレッドシートでテストケースを管理することにしました。

f:id:delyjp:20180802000659p:plain

一行が一つのテストケースに対応しており、上記の JSON の各種パラメータを設定できるようにしています。
また、パタメータとは別に自由に設定できるタグ用のカラムを追加しました。
このタグを利用して、特定のテストケースのみを実行したり、どの画面か、管理サイトかなどで色分けできます。

テスト開始前にこのスプレッドシートのデータを取得し backstop.json を生成します。

各テストケースが見やすく編集もしやすくてとても便利なのですが、テストケースが Git の管理下ではなくなるというデメリットもあります。

結果を S3 にアップロードして Slack に通知

BackstopJS はテスト結果を HTML + アセット群というかたちで生成します。
このようにスクリーンショットの比較結果をとても見やすく表示してくれます。

f:id:alluser:20181207182333p:plain

f:id:alluser:20181207182750p:plain

この HTML + アセット群を静的サイト向けにセットアップした S3 にアップロードし、Slack に URL を通知します。

まとめ

SPAへの移行では実際に多くの意図しない挙動を1px単位で見つけることができました。
このタイミングで導入できてとても良かったです。

ビジュアルリグレッションテストは他のテストを代替するようなものではないですが、技術スタックに依存しないことと、テストケースが壊れにくいというところが大きな利点かと思います。
ビジュアルリグレッションテストを導入して快適なフロントエンドライフを送りましょう!

【追記】

多くの方に読んでいただきとても嬉しいです🙌反応をいただいた点について追記しました!

運用が大変ではないか?

現状、リグレッションテストに関しては、ユニットテストやE2Eテストのように、コミット度にテストを回して、テストケースが全てパスしないとマージできない、という運用はしていません。

雰囲気としては、

「今回だいぶ変更箇所多くなったなー、一応リグレッションテスト見ておくか」
「うわ、差分めっちゃ出てる、こりゃよく分からんね...」
「そうすね、今回はざっと見て問題なさそうだったら、新しいほうをリファレンスにしちゃって下さい」
「了解です!大丈夫そうです!」
「マージ!」

こんな感じです。

テスト実行の有無は都度判断し、もやは有益でない差分については無視しても構わないくらいの運用です。
通常のテストとは違いこのくらいの運用でも十分効果があります。

1pxの違いにそこまで工数かける?

上記のような運用方法が前提であれば、そもそもこの点は解消するかもしれません。

一つの例として1pxの線が挙げられると思います。
デザイン上1pxの線が使われている部分はkurashiruにも多くあります。
1pxのズレで大きなデザイン崩れになることは無いかもしれませんが、1pxの線でも、その線が消えてしまうと大きく意味が変わってしまう、ということはよくあると思います。

その線が誤って消えてしまったりすると、

「すいません!ここの線が消えちゃってました!」
「あっ!すいませんすぐ修正しますー(たぶんあの変更だ...)」

のように、そのページを見て気づいた人からの報告ベースでの修正ということはよくあります。(それ自体は良いと思います🙌)
要は、開発者や関係者が手動で目grepで確かめないといけなかった部分を、ある程度置きかえられるので、ページ数にもよりますが、費用対効果としては悪くないと思います。

広告が差し込まれたり変わっただけでテストが壊れるのでは?

これはその通りなのですが、BackstopJSの機能で対応可能です。
テストケースごとに特定のセレクタにマッチした要素をvisibility: hiddenにしたり、display: noneすることができます。
下のリンク先にあるhideSelectorsremoveSelectorsというオプションです。

github.com

他にも色々な機能があるので、ある程度のケースには対応できると思います。

スモールスタートではじめるSSR

こんにちは。delyでフロントエンドを担当している@all__userです。

今回はkurashiruでSSR(Server Side Rendering)を導入した事例についてご紹介したいと思います。

目次

要約

  • SPAにしたい。SEOのことを考えるとSSRはしておきたい
  • でも全然リソース足りない
  • Railsを温存しつつスモールスタートでSSRできるようにした

という内容です。

経緯・背景

kurashiruはもともとRails単体のアプリケーションでしたが、フロントエンドにVue.jsを採用し、現在では多くのページがSPAへと移行しました。
SPAではSEOの観点からSSRが必要であるとよく言われます。
kurashiruでも調査・議論を重ねた結果、SSRを導入することになりました。

SSRの導入

フロントエンドをSPA(Single Page Application)に置き換えていくタイミングでSSRを導入し、結果としては懸念していたSEOへの悪影響も無く1、無事SPAへと移行することができました。

tech.dely.jp

SPAをやるにあたってとにかく一番頭をもたげたのはSSRどうするか問題でした。

SPAのSEO対策

正直に申しますと、個人的には「SSRはあったほうがいいけど、必須ではない」という主張の持ち主でした。
その内容は概ね以下のようなものです。

  • fetch as googleでレンダリングされていれば問題ない
  • 初期レンダリングコストの削減が目的なら導入・運用コストに見合わない

しかし、いくら調べてみても議論を重ねてみても、SEOに悪影響が無いとは言い切れませんでした。
kurashiruにとってSEOが悪化することはわずかな可能性でも避けたいことでしたが、それと同時に不要なものを導入して複雑化させるのも避けたいことでした。

そんな中、Google I/O '18の中でUser-Agentによってクローラーを識別し、クローラーに対してのみSSRした結果を返すダイナミックレンダリングという手法が紹介されていました。

www.youtube.com

www.suzukikenichi.com

このダイナミックレンダリングの説明の中で、GooglebotによるJavaScriptレンダリングは、現時点で完全ではないことが明示されています。
また、ダイナミックレンダリングがクローキングには当たらないことを示す内容でもありました。

developers.google.com

GooglebotがSPAを完全にレンダリングできない可能性がある以上、SPAへの移行はSSRを前提に考える必要がありました。

SSRのコスト

一般的なSSRの構成は、ユーザーからのリクエストをSSRサーバーで受け、APIサーバーに必要なデータを問い合わせ、ページをレンダリングし、その結果を返すというものです。

一般的なSSRの構成で考えた場合

この構成を採用する場合、以下のような懸念点がありました。

  • metaタグの仕組み(title、description、OGPなど)をRailsからSSRサーバーに移行する必要がある
  • 全ユーザーを対象とする場合はスケールやキャッシュの仕組みなどをNode.jsサーバー用に構成し直す必要がある
  • 非SPAのページと共存する場合、ALBなどの前段で振り分ける必要がある
  • Node.jsサーバーの面倒は誰が見るの問題

kurashiruは単体のRailsアプリケーションとしてすでに十分大きかったため、SSRサーバーにフロントの機能を移すだけで大きなコストがかかります。
社内にNode.jsやSSRの経験が豊富にあれば、この構成を採用する可能性もありましたが、私自身にNode.jsの実戦経験は無かったですし、他のメンバーの技術スタックやリソースを考えても、この構成への移行コストは高く、その時点では採用するメリットはありませんでした。
仮にリソースを割いて移行できたとしても、「もしダメでもやめればいい」という判断がしにくくなる可能性がありました。

Rendertronの採用は見送り

ダイナミックレンダリングの手法として紹介されているうちのひとつが、Rendertronを使った方法です。

github.com

話し合いの中で以下の懸念点が出てきました。

  • キャッシュ効率が高いことが前提となっている
  • クローラーが同じURLを再訪したときしかキャッシュヒットしない
  • 同じURLを再訪する頻度が少なそう
  • だとすると、マシンパワーが必要なためレスポンスタイムが心配
  • お金がかかりそう
  • あくまでクローラーに対してのレンダリング用で、ユーザーにも転用する未来が見えにくい

これらの懸念からRendertronの採用を見送りました。

kurashiruのSSR構成

大枠はRendertronによるダイナミックレンダリングの構成と同じです。
この構成にすることで、既存のRails資産を流用しつつ、部分的にSSRを導入できます。

kurashiruで採用したSSRの構成

  1. Webサーバーがリクエストを受けると、User-Agentからクローラーかどうかを判別し、クローラーの場合はSSRに必要な情報(リクエストパスなど)をJSONにまとめてSSRサーバー(Node.js)にPOSTします。
  2. SSRサーバーがPOSTリクエストを受け取ると、ページのレンダリングに必要なデータをAPIから取得します。
  3. SSRサーバーでデータの取得が完了すると、vue-server-rendererを使用してクライアントサイドと同じコードを実行し、body要素直下にマウントされるルートコンポーネントをレンダリングします。
  4. SSRサーバーでレンダリングが完了すると、レンダリング済みのHTML文字列をJSONにまとめ、Webサーバーに返します。
  5. Webサーバーがレンダリング済みのHTMLを受け取ると、body直下にそのHTMLを埋め込み、完成したHTMLをブラウザに返します。

ルートメタフィールドを利用したデータ取得の仕組み

クライアントサイドと同じように、レンダリングの過程でAPIサーバーから必要なデータをフェッチしますが、クライアントサイドと異なる点として、レンダリングを1ライフサイクル(ルートコンポーネントのbeforeRouteEnter, beforeCreate, created)内で同期的に行う必要があります。
そのため、レンダリングを始める前にすべてのデータのフェッチが完了している必要があります。

この問題を解決するために、例えばNuxt.jsではasyncDataというメソッドを使用することで、非同期のデータ取得をあらかじめ完了させておくことができます。

ja.nuxtjs.org

kurashiruではNuxt.jsを導入していなかったため、同様の仕組みを用意する必要がありました。
コンポーネントのメソッドを利用する代わりに、ルートメタフィールドを利用しています。

router.vuejs.org

簡略化したコードを以下に示します。

// ComponentA.vue
export const generateSsrFetcher = ({ app }: { app: Vue }) => {
  return {
    fetchEndpointA() {
      return app.$api.fetchEndPointA({ id: app.$route.params.id });
    },
  };
};

export default Vue.extend({ /* ... */ });

// routes.ts
const routes = [
  {
    path: '/component_a/:id',
    name: 'component_a',
    component: () => import('./path/to/ComponentA.vue'),
    meta: {
      async getSsrMetadata() {
        const { generateSsrFetcher } = await import('./path/to/ComponentA.vue');
        return { generateSsrFetcher };
      },
    },
  },
];

// renderApp.ts
async () => {
  // ...
  const app = createApp();
  app.$router.push({ path: '/component_a/123' });
  const metadata = await app.$route.meta.getSsrMetadata();
  const ssrFetcher = metadata.generateSsrFetcher({ app });
  const data = await ssrFetcher.fetchEndPointA();
  // ...
}

Nuxt.jsのasyncDataではthisによるVueインスタンスの参照ができないという制約があります。
レンダリング前に実行されるメソッドということを考えると自然なことですが、this.$route.paramsを利用できないなどの不便な点もあります。
これらを緩和するためにAPIからデータを取得する際、ルートインスタンスを参照できるようにしています。2

消極的SSRから積極的SSRへ

SSRの導入に関しては、SEO対策だけでなく、初期レンダリングコスト削減による体感速度の向上など、UX改善という側面もあります。
kurashiruではまだこのようなモチベーションでSSRに取り組めていないので、消極的SSRと呼んでいたりします。

Node.jsを運用する知見が少しづつ溜まってきたこともあり、全てのリクエストに対してSSRするなど、より積極的な導入も考えています。
このあたりは今後の課題です。

まとめ

SPAへの移行を機にSSRを導入した事例についてご紹介しました。
SPA+SSRを同時に導入するということは、大きな変更を2つ同時に行わなければならず、単純明快なメリットが無いと特に採用ハードルが高くなると思います。
この記事で紹介したように、既存資産を流用しながら検証を行うことができれば、SPA+SSRは最良の選択になるかもしれません。

最後までお読みいただきありがとうございました。
SPA化に関しては下記の記事でも紹介していますので、ぜひご覧ください!

tech.dely.jp


  1. 「SPAはSEOに良い影響があった」という意味ではないので注意が必要です。

  2. Vue.js 2.6で入ったserverPrefetchでは、thisによるコンポーネント自身のインスタンス参照が可能なので、置き換えを検討しています。

Firebase Test labでiOSアプリのUnitTestとUITestを行う

こんにちは!クラシルのiOSアプリ開発を行っているtakaoです。

今回はiOSアプリのテストの実行に関する内容です。

現在のクラシルのテスト運用は、Bitrise上でfastlaneのrun_testsというActionを使ってテストの定期実行を行っていますが、結果の確認がしやすい状態とは言えません。特にUITestに関しては画面を確認するために、必要な場合はローカルで実行したりしています。
そこで、何やらFirebaseが提供している Firebase Test lab(以下、Test lab) というサービスを使えば、テスト結果をスクリーンショットや動画で確認することが出来るということだったので、試してみました。
この記事ではTest labでiOSアプリのテストを行う方法とTest labを使うとどういう結果が得られるのかをご紹介します。

準備

Firebase プロジェクトを作成する

Test labを使うためにFirebaseプロジェクトの追加を行っておきます。

サンプルアプリの作成

ボタンを押すとアラートが表示されるだけのサンプルアプリを作成しました。

f:id:takaoh717:20190328184920p:plain:w200 f:id:takaoh717:20190401094519p:plain:w200

UnitTestを書く

簡単なテストを書きます。

func testExample() {
       let model = TestLabSampleModel()
       model.updateTitle(new: "test")
       assert(model.titleLabel == "test")
}

UITestを書く

こちらもボタンをタップしてアラートを閉じるだけの簡単なテストを書きます。 UI Recordingを使ってさっと書きました。

func testExample() {
    let app = XCUIApplication()
    app.buttons["Button"].tap()
    app.alerts["(^o^)"].buttons["close"].tap()
}

アプリのXCTestをビルドする

Test labを利用するためにはXCTestのビルドを行う必要があります。 ビルドされたXCTestを使って、UnitTestとUITestを行います。

XCTest Apple公式ドキュメント

① DerivedData格納場所を確認・変更(任意)

公式のドキュメントの手順では「プロジェクトの Derived Data の場所を構成する」が必要だと説明されていますが、こちらの設定は任意です。 Test labを頻繁に利用する場合に、ビルドされたファイルにアクセスしやすいようにするための設定なので、初回の場合やCIなどでテストを自動化する場合は不要です。

② テストファイルをビルドする

Xcode上でXCTestのビルドを行います。
ナビゲーションから【Product】→【Build For】→【Testing】の順に選択します。

③ アップロード用にテストファイルを圧縮する

Test labにアップロードするために、必要なファイルをまとめて圧縮します。
ファイルはDerivedDataの中にありますが、Xcodeから簡単にアクセスできます。
【File】→【Workspace Settings...】の順に進んで、DerivedDataの箇所にある矢印ボタンをクリックすると、Finderが開きます。

f:id:takaoh717:20190328183523p:plain:w200

その中から以下のファイルを選択して圧縮します。

  • Debug-iphoneos
  • PROJECT_NAME_iphoneosDEVELOPMENT_TARGET-arm64.xctestrun

Firebaseコンソールでテストを実行する

Test labのコンソール画面で圧縮したファイルをアップロードします。
アップロードが完了するとテストの設定を選択する画面になります。

端末の選択

端末のバリエーションが豊富で、タブレットを選択することも出来ます。

f:id:takaoh717:20190327193416p:plain:w300f:id:takaoh717:20190327193451p:plain:w300

画面の向きやロケールも選択可能になっているため、横画面に対応しているアプリや多言語対応アプリなどは全部のパターンを手動でテストするのは大変なので嬉しいと思います。

f:id:takaoh717:20190327193612p:plain:w500

利用可能な端末の種類とOSについても随時更新されていくようです。

Available devices in Test Lab

テスト結果を確認する

テスト結果はこのような感じで表示されます。

f:id:takaoh717:20190327193715p:plain:w400 

サンプルアプリだとスクリーンショットの情報が出なかったため、クラシルのアプリで試してみました。 テスト結果は実行した端末・OSごとに確認することが出来ます。

iPhoneSE

f:id:takaoh717:20190328163743p:plain:w400

iPhoneX

f:id:takaoh717:20190328163800p:plain:w400

また、UITestを動画タブで実行している際の動画を確認することが出来ますが、こちらはかなりざっくりしたコマ送り動画のような状態になっており、さらに、全てのテストの結果が一つの動画にまとめられているので、テストの数が多い場合は確認が難しそうです。。。

f:id:takaoh717:20190401101620g:plain:w200

Firebaseコンソール以外での実行方法

Bitriseでテストを実行する

今回は確認していませんが、BitriseのWorkflowを使用すると、簡単にテストの実行が出来るようです。 実行結果もちゃんとBitrise上で確認出来るようになっているので、Bitriseを導入しているプロダクトの場合はこっちを使うのが良さそうです。

Device testing for iOS - Bitrise DevCenter

f:id:takaoh717:20190327192859p:plain
Bitrise画面

gcloud コマンドラインでテストを実行する

こちらも今回は試していませんが、CLI上でテストを実行することも出来るみたいです。 実行結果はFirebaseのコンソール上で確認出来ます。Firebaseのコンソール画面で確認したい場合はCLIを使って定期実行するのが良いかもしれません。

まとめ

Firebaseのサービスはアプリに設定ファイルやSDKを追加しないといけないものが多いですが、Test labに関してはプロジェクトの追加を行うだけで他に特別なことをしなくて良いため、比較的導入がしやすいと思います。 また、端末の種類が充実していたり、画面の向きやロケールなどの設定を変更したりなども容易に出来るのが良かったです。 Bitriseを使えばCI環境でもすぐに導入できると思うので、テスト実行を行うための環境をまだ作っていないプロジェクトなどでは役立つと思います、ぜひお試しください。

開発ブログの大成功をお祝いして、みんなが大好きなアレを食べてきました

こんにちは!

dely, Inc.でプロダクトマネージャー兼開発部ジェネラルマネージャーをしている奥原 (@okutaku0507) といいます。気がついたら、もう3月になってしまいました。皆さんは花粉と対峙されていますか。

この記事はレシピ動画サービスであるクラシルを開発しているdely開発部が2019年1Qに開発ブログに取り組み、その一環として2018年のAdvent Calendar (dely Advent Calendar 2018) を実施した背景とその成果について書いて行きたいと思います。

目次

 

1. 開発ブログに取り組んだ背景

まず最初に、dely開発部はとても少数精鋭です。実際に人数を聞くといつも皆さんが驚かれるほどです (気になった方はこちらから僕に聞きにきてください) 。実際に会社に所属して開発ブログを運営されている方なら体感されていると思うのですが、開発ブログを運営していくことは驚くほど工数がかかります。自分たちの取り組んでいることを、社外にわかってもらえるように発信することは思っている以上に大変です。では、少数精鋭でリソースが限られている中、なぜ、僕たちがAdvent Calendarに取り組み、開発ブログを盛り上げていこうと思ったのか。その背景についてお伝えしたいと思います。

今ではどこの会社も同じ現状かと思いますが、delyにおいても「クラシルの事業拡大と新規事業立ち上げのため、圧倒的に人材が足りない」という課題を抱えています。そして、採用人事に携われている方々は痛感していると思いますが、エンジニアとデザイナーの労働市場は本当に採用が困難になっています。これらの課題を解決するために、僕らがどうすればいいのか考えました。

僕の考えでは、採用はマーケティングに似ていると思っています。

f:id:okutaku:20180405140824j:plain

だれでも使えるマーケティングファネルの考え方 (https://www.tam-tam.co.jp/tipsnote/others/post14695.html)

つまり、採用される方のUXを考えた時に、会社を認知する、興味を持つ、転職 (新卒で入社) の際に比較・検討する、入社するというようなファネルに分解できると思います。このファネルにおいて、delyの現状と照らし合わせた際にまず浮かび上がった課題が以下の通りです。

クラシルというプロダクトや社長の堀江さんは知っているけど、それを開発しているdelyの開発部のことは全く知らない。

クラシルというレシピ動画サービスは2017年に全国規模のTVCMを打ったのと、社長である堀江がTVに出たり、PRで露出する機会が多く、両者は広く知れ渡っていました。そのため、候補者や実際に入社された方にヒアリングすると上記の課題が出てきました。そもそも、クラシルを開発している僕らが認知すらされていない状態となっていました。

ファネルの上部である「認知」を獲得することを目的として、上記課題の解決策として開発部ブログを盛り上げるという方法を考えました。

以上の拝見から、2019年1Qの開発部の目標として開発ブログの活性化を入れることになりました。

 

2. KPIと実績

そもそも、dely開発ブログである本ブログは2016年から存在はしていました。しかしながら、工数もかかるためほとんど運用されていなかったのが実情です。

実際に記事を書いていくにも、目標を立てておいた方がそれを目指せると考え、2019年1Qが終わるまでに、通常の目標として42,000PV、ストレッチ目標として100,000PVをおきました。通常の投稿で、ちょっとバズったとしてもPV数は数千程度だったので、部内では目標に対する厳しさすら立ち込めていました。まさかの結果となるとはつゆ知らず...

達成できるのかという一抹の不安がこみ上げる中始まった開発ブログですが、2019年1Q中のPVランキングを紹介します。 

 

第一位は...

tech.dely.jp

弊社SREの井上さんが書いた、AWSの料金が意図せず増えてしまうのを仕組みで解決するという記事でした。AWSで意図せず予算を溶かしたというこの上なく辛い経験をした人に刺さったのか、多くのSREの人に読んでいただけたようです。

 

第二位は...

tech.dely.jp

僕が書いた、クラシルで実践しているリーンなプロダクト開発を事細かに紹介した記事です。僕のクリスマス全てを捧げたかいがありました。delyの開発部では、リーンなプロダクト開発を通して、リリースしてみないとわからないという不確実性を減らす仕組みを作り、ユーザーに価値あるプロダクトを提供することに力を注いでいます。

 

第三位は...

tech.dely.jp

弊社SEOスペシャリストである、internet_ghostさん (@ghost_inter_net) が書いたSEOの記事です。アプリの会社と思われがちですが、最近ではwebチームの発足と共にweb版クラシルもガンガン開発されており、前年比2000%の成長を遂げています。webチームにも注目が集まっています。

 

さて、開発ブログがおいていた目標に対する実績ですが...

f:id:okutaku:20190304104109p:plain

僕らの予想を大きく上回り、早々にストレッチ目標であった10万PVを超えました。開発部として、僕らがやっていたことが皆さんに注目していただけて本当に嬉しい限りです。ノウハウも多く書いたので、読んでいただけた皆さんの役に立てれば幸いです。

 

また、これは本質的ではないかもですが、はてなブックマーク数ランキングでは10位を獲得することができました。少数精鋭で行なった分、この順位は僕らの自信に繋がっています。

qiita.com

3. 開発ブログの成果

さて、目標であったPVを達成してもそれが何も繋がっていなかったら意味がありません。僕らが実感してる効果を列挙します。

  • オーガニックでの応募が増えた
  • スカウトの返信率が向上した
  • お会いする人にブログ読んだことがあると言われるようになった
  • 面接にきていただける方が事前に読んでくれるようになった
  • ブログの盛況で開発部全体が盛り上がった

dely開発部の認知が拡大することで、wantedlyなどでのオーガニックでの応募が増えたように感じますし、面接に来ていただいた方に読んだことがあると言われる機会が増えました。また、副次的な効果としてスカウトの返信率が向上したように思います。考察として、認知を獲得することは採用全てのベースになると思いました。そして、開発ブログが盛り上がると開発部全体も盛り上がって良いなと感じました。

 

4. 終わりに

開発ブログの大成功をお祝いして、みんなが大好きなアレを開発部の皆で食べました。みんなの頑張りを労い、一体感が増してよかったです。これからも引き続き、開発ブログでの発信をしていきたいと思います。お寿司目的で頑張ったのではないことだけは大事なので、明言しておきたいです

f:id:okutaku:20190302185201j:plain

そして、皆さまが気になっている実際の採用ですが、開発ブログが全てではないですが、優秀なメンバーの採用が徐々に決まってきています。一緒になってクラシルをよりよくできるメンバーが増えて、とても嬉しいです。ですが、まだまだ積極的に採用を行なっています。

こちらが現在、募集しているポジションになります。プロダクト開発に携わるポジションは全て募集しているので、是非とも話だけでも聞きにきてください。

www.wantedly.com

また、面接となるとハードルが高い方用にカジュアルに内部のことが聞ける「カジュアル1on1」という取り組みも行なっています。絶対に面接はしないので、是非とも中にいるメンバーに話を聞きにきてください。

bethesun.connpass.com

さらに、行くのもためらわれた方用に気軽に僕とチャットできる選択もオープンにしています。起きている時間全て対応しますので、気軽にチャットしてきてください。

note.mu