読者です 読者をやめる 読者になる 読者になる

オールアバウトTech Blog

株式会社オールアバウトのエンジニアブログです。

バーガーショップで例えるオールアバウトでのLaravelアーキテクチャ

Laravel PHP サービス開発

オールアバウトで開発チームに所属している@pakkunです。

12月も近くなり、大きく時期から外れてしまいますが、弊社では8月から9月にかけてサマーインターンを行いました。 その際に弊社で導入しているLaravelというPHPフレームワークの付き合い方を資料とライブコーディングでインターン生に説明しました。

抜粋になりますが、弊社でのLaravelとの付き合い方をブログでも公開します。

とは言え、Laravelを知らない人もいるかと思いますので、まず初めに軽く説明します。

3行でLaravelを知る

MVCベースと記載しましたが、開発者のTaylor Otwellさんは「MVC Is Killing You」と著書で言っており、MVCに縛られると辛くなるので、あまり深くとらわれないようにしましょう。

次にどんな機能があるのか見てみましょう。

Laravelの機能

上記にあげたのは一例です。フルスタックフレームワークであるだけに、他にもイベント管理や認証管理など素晴らしい機能がたくさんあります。

Laravelの基本的なアーキテクチャ

Laravelで必要最低限の機能でWebアプリケーションを実装した場合、下記のようなアーキテクチャになります。

f:id:allabout-techblog:20161115200420p:plain

Laravelのアーキテクチャ自体は、ORMがメインになるので、ドメイン駆動設計(DDD)のレイヤー化アーキテクチャにあるドメイン層をベースにして考えると理解しやすいと思います。
Model部分が、Active Recordパターンで実装されており、Repository、Factory、Model(Entity)が組み合わさった状態になっているので、注意しましょう。

ORMを使用するだけで、レイヤー化アーキテクチャドメイン層の思想に触れられるのは本当に素晴らしいですね。 これをきっかけに、ドメイン駆動設計(DDD)のレイヤー化アーキテクチャを勉強してみても良いと思います。

オールアバウトでのLaravelのアーキテクチャ

Laravelについて簡単に説明しましたので、ここからオールアバウトでのLaravelのアーキテクチャを説明していきます。 先に記載したLaravelの基本的なアーキテクチャを下記のように変更しています。

f:id:allabout-techblog:20161107221435p:plain

Serviceというレイヤーが追加されただけなので、基本的なLaravelのアーキテクチャとあまり変わりませんが、重要な役割を担います。
オールアバウトでは、コントローラーやモデルで扱うには不自然な処理であるEager Loading、DBトランザクション、Cacheの処理をServiceで処理するようにしています。

このアーキテクチャをベースにオールアバウトでは開発を行っています。 レイヤー化アーキテクチャを学んでいると、「ドメインサービスでは?」と思う人もいるかと思いますが、ここで定義しているサービスは「アプリケーションサービス」になりますので注意しましょう。

また規模にもよりますが、Serviceでキャッシュを多用する大規模な案件の場合、オブジェクトのライフサイクルが管理しづらくなるので、サービスを「クエリ用サービス」と「コマンド用サービス」に分けるとシンプルになります。 この思想は、「Tell, Don't Ask」、「コマンドとクエリの分離原則」を知っていれば理解できると思います。

Serviceを追加した弊社のアーキテクチャを詳しく説明しますが、普通にアーキテクチャの役割を説明してもわかりづらいので、ファーストフードのハンバーガーショップを例えにして説明していきます。 また、説明するのはデータの取得についてです。データの更新については、後述の「補足」で少しだけ触れます。

まず、Laravelのアーキテクチャを一度忘れてください。 ハンバーガーショップでいつも行っているハンバーガーの買い方を思い出しましょう。

ハンバーガーショップで行っているハンバーガーの買い方

  • お店に入る。
  • カウンタースタッフに向かう。
  • カウンタースタッフに注文をする。
  • カウンタースタッフが棚からハンバーガーを持ってくる。
  • カウンタースタッフがハンバーガーをトレイに乗せて渡してくれる。
  • ハンバーガーをゲット。

上記のような流れになると思います。 もし、「違う」という人がいたら、ファーストフードのハンバーガーショップに行って体験してきましょう。

イメージできた人は先へ進みましょう。 ハンバーガーの買い方を弊社のアーキテクチャで表現してみます。

f:id:allabout-techblog:20161107221441p:plain

ハンバーガーの中に人が含まれてしまっていますが、私たちが実際にハンバーガーを買うときにも、作っているスタッフと会うことはないので、隠ぺいされていると考えましょう。

次に弊社のアーキテクチャとハンバーガーショップの役割を比較してみましょう。

オールアバウトのLaravel ハンバーガーショップ
Route 入店 / ドライブスルー
Request 注文
Controller カウンタースタッフ
Service
Repository 置くスタッフ
Factory 作るスタッフ
Model ハンバーガー
View トレイ・紙袋

だいぶイメージがしやすくなったのではないでしょうか。

イメージができたところで、ハンバーガーショップを例にして、弊社のアーキテクチャを解説していきます。

ハンバーガーショップで例える弊社のアーキテクチャの役割

Route

Routeは、専用のControllerへ誘導するのが仕事です。それ以上のことはしません。

ハンバーガーショップでは、「入口」にあたります。 ハンバーガーショップでこの役割を説明してみましょう。

  • お店の入り口は、店内のカウンタースタッフへの誘導するのが仕事です。
  • ドライブスルーの入り口は、ドライブスルーのカウンタースタッフへ誘導するのが仕事です。

ハンバーガーショップで人と車が一緒に並ぶことはないのと同じようにプログラムの設計にも気を付けたいところですね。

Request

Requestは、Controllerが理解しやすいリクエストを作るのが仕事です。 Laravelでは、バリデーションを行う重要なクラスです。
Requestクラスはカスタムで定義できますので、リクエストにビジネスロジックが入ってしまった場合、カスタムで定義したRequestクラスでデータを正規化することができます。

ハンバーガーショップで例えるなら、Requestクラスは注文を管理する「メニュー」になります。 ハンバーガーショップでは、日本語のメニューでも英語のメニューでも写真によってカウンタースタッフが注文の理解をしやすくなっています。

これを踏まえた上で、ハンバーガーショップで説明してみましょう。

メニューは、カウンタースタッフが理解しやすい注文を作るのが仕事です。

重要な点としては、RequestやControllerでビジネスロジックを書くようなら正規化が足りないということです。
処理が肥大化してしまうなら、可能な限り、別途画面を用意してデータを正規化するように調整しましょう。

Controller

Controllerは、Requestを受け取り、Serviceから完成されたModelを取得して、Viewと合わせるのが仕事です。
Controllerに仕事をさせないようにViewレンダリング時やRequestで可能な限りデータは正規化しましょう。

ハンバーガーショップならカウンタースタッフがこの役割にあたります。

カウンタースタッフは、注文を受け取り、ストックしている棚から完成されたハンバーガーを取得して、トレイと合わせるのが仕事です。
カウンタースタッフに英語や日本語を覚えさせなくてもよいようにメニューを可能な限りわかりやすくしましょう。

重要な点として、Controllerは完成されたModelを扱うということです。 今回の場合はデータの取得ですので、Modelオブジェクトの値をControllerで更新すると以降の処理で副作用が起き、後悔しか生まれません。コマンドとクエリの責務分離は大事です。やめましょう。

ハンバーガーショップで例えるなら、カウンタースタッフが完成されたハンバーガーの袋を開けてサンドイッチに変更し始めたら、違和感がありますよね?それと同じです。

Service

Serviceは、Controllerが欲しがっている完成されたModelを提供するのが仕事です。 効率をあげるためにServiceは、Modelをキャッシュする場合があります。 必要であれば、ルートModelに紐づくオブジェクトをEager Loadingしておきます

ハンバーガーショップなら、ストック(キャッシュ)している棚が同じ役割をしています。 ハンバーガーショップで説明してみます。

棚は、カウンタースタッフが欲しがっている完成されたハンバーガーを提供するのが仕事です。 効率をあげるために棚は、ハンバーガーをストックしている場合があります。 必要であれば、ハンバーガーセットに紐づく商品をまとめて袋に入れておきます。

ここでも重要なのは、Serviceは完成されたモデルを扱うということです。 Controllerと同様にデータの取得ですので、Modelオブジェクトの値をServiceで更新すると以降の処理で副作用が起き、後悔しか生まれません。コマンドとクエリの責務分離は大事です。やめましょう。

棚に置いておいたら、完成されたチーズバーガーが突然ハンバーガーになることは現実世界で起きないのと同じです。
もし変化してしまったら誰がその責任を負うのでしょうか?それと同じです。

Repository

Repositoryは、Service(または、Controller)と永続化されたデータをやり取りするのが仕事です。 簡単に言えば、Factoryとのやり取りを抽象化することが役割となります。 Factoryの担当が、MySQL、Postgres、Mongo、APIに変化しても意識させません。
Laravelでいうと、Eloquentのcreateメソッドやgetメソッド、firstメソッドなどが該当します。

ハンバーガーショップでは、この役割を担うことが少ないと思いますが、例えるなら下記になります。

作られたハンバーガーを棚におく人は、棚(または、カウンタースタッフ)と冷蔵庫の材料から作られたハンバーガーをやり取りするのが仕事です。 簡単に言えば、ハンバーガーを作るスタッフとのやり取りを抽象化することが役割となります。 ハンバーガーを作るスタッフの担当が、アルバイト、超一流のシェフ、別の店舗からハンバーガーを持ってくることに変化しても意識させません。

ORMを使用していればあまり意識しないですが、大事な概念ですね。

Factoryの役割

Factoryは、Modelが欲しいと言われたら、DBから情報を集めて、Modelオブジェクトを作るだけが仕事です。 Factoryは、Repositoryのおかげで、別の代替手段に切り替えやすい存在となっています。

ハンバーガーショップであれば、ハンバーガーを作るスタッフが該当します。

ハンバーガーを作るスタッフは、ハンバーガーが欲しいと言われたら、冷蔵庫から材料を集めて、ハンバーガーを作るだけが仕事です。 ハンバーガーを作るスタッフは、ハンバーガーを棚におく人のおかげで、別の代替手段に切り替えやすい存在となっています。

Factoryを切り替えられることによって、多様性が得られていますね。

Model(Entity)

情報のかたまり、あるいはオブジェクトです。 私たちがWebサイトにアクセスしたときには、いつもModelという情報を取得しています。

Modelの役割をハンバーガーショップでごり押しで説明してみます。

おいしさのかたまり、あるいはハンバーガーです。 私たちがハンバーガ-ショップに行った時には、いつもハンバーガーという商品を食べています。

ハンバーガーであれば、カロリーという情報を持ち、味、ソース、パンという構造も持っています。 この場合、情報はValue Object、構造はRelationと言えるでしょう。

もし欲しいもの(Model)が手に入らなかったら、がっかりしませんか?Modelはアクションに対しての本質と言えますね。

View

Viewは、Modelを展開するだけが役割です。 また、Viewを使わずにJson形式で出力する場合もあります。

Viewの役割をハンバーガーショップで説明してみます。

トレイは、ハンバーガーを盛り付けるだけが役割です。 また、トレイを使わずに紙袋で渡す場合もあります。

ここで大事なのは、トレイの上でハンバーガーを展開することはあっても、ハンバーガーを材料にして加工することはないので注意しましょう。 調理道具のないトレイで火を起こして料理をすると大火傷しますよ?

プログラムも同じように、Viewの上ではModelを加工せずに展開するだけにしましょう。

ハンバーガーショップを例にしてのデータの取得の説明はこれで終わりです。

データの取得についてのみの説明だったため、データの更新時について、少しだけ補足します。

補足

弊社では、データの更新もServiceを利用していますので、基本的には大きく変わりませんがデータの更新の場合、注意が必要になります。 それは、データの更新の場合、Modelオブジェクトの値を更新することが前提なので、副作用が起きるということです。 そのため、以降の処理で大きく影響を受ける可能性がありますので、状態を変化させたModelの再利用はやめましょう。 状態が変化したModelは、一度破棄をして、再度データベースへ問い合わせるのがベストです。

RESTの概念で言えば、StoreやUpdateした後に、Showにリダイレクトするのと同じです。
MySQLであれば、InsertやUpdateした後に、データが取得できないのと同じです。
Reactで言えば、Stateの状態を更新した場合、Viewを再度レンダリングするのと同じです。 テキストファイルで言えば、更新して閉じた後に、再度開いて見直しするのと同じです。

普段から意識せずとも理解していることが多いのではないでしょうか。これらを意識して開発をするときれいに実装できると思います。

簡単にまとめるのであれば、下記のように覚えておくと良いでしょう。

  • データを更新する処理では、状態を変更したModelの再利用は絶対ダメ。
  • データを取得する処理では、Modelの状態変更は絶対ダメ。

まとめ

長くなりましたが、以上がオールアバウトでのLaravelとの付き合い方になります。

ここで記載した内容は、Laravelだけでなく、一般的なWebフレームワークに置き換えることもできます。 Webフレームワークを使用しての実装や設計に悩んでいる方の参考になれば幸いです。

最後になりますが、私はハンバーガーショップでは働いたことがありません。ご注意ください。

オールアバウトでは、一緒にハンバーガーを作ってくれるサービスを開発をしていくエンジニアを募集しています。 ご応募お待ちしております。

テックブログの書き方・続け方

DevOps techblog wercker

@takkyです。

オールアバウトTech Blogは本日11/18で開設半年になりました!!
今回は半年運営してきたTech Blogの書き方・続け方について紹介します。

Tech Blogの体制

オールアバウトTech Blogは、開発部内でTech Blog編集部を立ち上げて運営をしています。
現在メンバーは3人で全員エンジニアです。
他社様の運営の話を聞いてみると人事や広報の方がメンバーとして入っていることもあるようなのですが
オールアバウトのTech Blogは運営メンバーが全員エンジニアです。
Tech Blogの編集メンバーは主業務の他に持ち回りで記事の校正やネタ出し、執筆依頼など運営作業を行っています。
編集部を立ち上げた理由としては以下になります。

  • Tech Blogは会社の名前を出していることもあり記事の質を担保したかった。
  • 業務で使用している技術でどんなことがブログに掲載できるのか、どんなネタを書けばいいのか。という点はエンジニアでないと難しい。
  • ブログを作ったので自由に記事を書いて公開して下さい。という形式だと、なかなかネタが出ず長続きがしない。

編集会議

2週間に1回、30分程度編集会議を行っています。編集会議では

  1. 前回投稿した記事のKPI確認
  2. ブログのネタ出し
  3. 持ち込みネタのチェック

を行っています。
KPIに関してはGoogle Analyticsを使用してPV数を取得しています。
ブログのネタ出しに関しては、外部公開が出来そうなネタや流行りそうなネタを整理して、
実際に業務で使用した人や得意な人にお願いして執筆してもらっています。
ネタ出しを編集部が行うことで会社として取り組んでいる技術を打ち出すことができます。
また、あまり自信がない人でも編集部からプッシュすることで自信をもって書いてもらうことができています。
基本的には編集部から依頼して執筆をしている形になるのですが、
公募制でこういう記事を書きたいという人からのネタに関してもチェックをしています。

記事の書き方

オールアバウトTech Blogでは、各自好きなMarkDown Editorを使用してMarkDownで執筆を行ってもらっています。
記事を書く際には、outline draft publishの3ステップで行っています。

outlineステップ

outlineステップではどのような記事を書いていくか見出しを作ってもらっています。
タイトルを頂いただけでは執筆者と編集者の間で認識のズレが生じてしまいます。
outlineで見出しを作ってもらうことで認識ズレがないようにしました。

draftステップ

draftステップでは公開前の記事本文のチェックをしています。
werckerというCIツールでcommit毎に自動でtextlintを実行し、細かい"てにをは"や表記ゆれのチェックをしなくても大丈夫なように行っています。
werckerはコンテナベースなので、textlintを使えるように準備したDockerImageをGoogle Container RegistryにPushして、 それを使うようにしています。
wercker.yamlにdraftディレクトリに入っている.mdファイルのみtextlintをかけるようにしているため、 draft記事がtextlintかけられる事になります。 図に示すと以下のようになります。
f:id:allabout-techblog:20161116122607p:plain

書いた文章がルールに従っていないと以下の用に警告がでます、

f:id:allabout-techblog:20161109121253p:plain

ルールにはWEB+DB PRESSで使われている用語に合わせるルールと、JTF日本語標準スタイルガイドを一部カスタマイズして使用しています。

github.com github.com

werckerに関しての詳しい説明は過去の記事をごらんください。

allabout-tech.hatenablog.com

また、ブログガイドラインを作成しガイドラインを守るように執筆してもらうことで執筆者・編集者の負担を減らしています。
ブログ開設直後はブログガイドラインが無いためどう書けばいいかわからず執筆者の負担となっていました。
編集者も主業務があるためなかなかチェックが出来ず双方の負担となっていました。
outlineで見出しを書くこととブログガイドラインを作成することで両者ともに負担が少なく執筆・編集作業ができています。

publishステップ

publishステップでは、執筆した内容をはてなブログにコピーして公開作業を行っています。

はてなブログの選定理由

テックブログを開設しようとすると、自分たちで作成するのかブログサービスを使うのかの選択が必要になります。
以前もオールアバウトテックブログは開設(※4年前)しており、自分たちで作成していたのですが、 運用負荷が高く閉鎖してしまいました。
そのような反省点もあり、今回は外部のブログサービスを使うこととしました。
ブログサービスは種々ありますが、以下の理由からはてなブログを選定しました。

  • MarkDown形式で執筆ができる。QiitaなどでMarkDown形式に慣れており書きやすかった。
  • MarkDownで執筆できるので、Bitbucketでの管理と相性が良かった。
  • 他社様のテックブログや他の技術ブログを見てもはてなブログの採用率が高かった。

今回は情報を発信するという点に注力したかったため、外部のサービスを使用しましたが 新しい技術の導入のサンドボックスとしてテックブログを運営するというのもいい考えだと思います。
外部サービスを使用するか自分たちで作成するかはどこに力を入れていきたいかで変わってくると思います。

記事公開時の編集作業

記事を公開する前に記事公開予定日を選定します。
できるだけ多くの人に読まれるように、大きめなカンファレンスや新製品の発表がある時を避けて記事公開予定日としています。
一部例外があり、カンファレンスの内容と記事の内容がマッチする場合は同じ日付で公開をしています。
公開日予定日が決定し予約公開するぞ前に編集者は大事な作業が残っています。

  1. 記事のタグ付け
    タグ付けに関しては編集者が実際に記事を読んで記事にあった内容のタグを2-3個ピックしてつけています。
  2. サムネイル画像の選択
    SNSで拡散されたときに見栄えがいい画像を記事中の画像からピックしてサムネイル画像としています。

こうして記事が公開された後には、Zapierを使い記事を公開されたことをSlackに自動で流しています。
理由は2つあります。

  1. 正しく公開されたかリマインダー的な扱い。
  2. 外部の人に読んで貰う前に自分たちでも興味を持って読んでもらいたいという思いから。

こうして記事が公開されています。
編集会議のネタ出しから執筆・編集を経て公開まで1記事約1か月かかっています。
(並行して何記事か執筆・編集している状態です)

半年間上手く行った理由

以前(4年前)テックブログを運営していたときは更新が滞りがちでした。
この半年では記事の執筆が滞ることも少なく5月~9月までは月平均2記事出すことができていました。
この理由としては、Qiita TeamやQiita Organizationなどで外部に情報発信をする文化が根づいてきたことや、週に1回ワークショップを行っており、アウトプットの意識が向上してきているからだと思います。
しかしながら、外部勉強会で発表を行うことは敷居が高く感じているエンジニアが多くおり、 そのようなエンジニアでもアウトプットができる場として機能していたのが大きいと思います。
PVやはてなブックマーク数などでユーザの反響がダイレクトにわかるためそれがモチベーションになる人もいますし、
半年に1回ベストブログアワードを選出して表彰するようにしているため、賞を狙うという点で燃えている人もいます。
最終的には編集者が執筆の催促をすることで書かなきゃ…という気持ちにさせているというのも大きいと思います。
このように書くことによって得られるメリットやモチベーションを保ちつつ気持ちよく執筆してもらうというのが大切だと考えています。

今後運営としてやりたいこと

ブログの運営としては手動で行っているところが多くあります。
今後は手動で行っている作業の自動化や効率化もしていきたいなと思います。
例えば記事の内容を解析して機械学習で自動タグ付けを行ったり、
bitbucketでマージされたらはてなブログへ自動pushする仕組みを作るなどです。
更にPV数やはてなブックマーク数を執筆者にKPIとして通知するのも現状手動なので自動化したいと考えています。
上記改善ができましたら、またブログで報告したいと思います。

まだオールアバウトTech Blogは開設半年です。
今後も長く運営を続けていけるように努力して行きたいと思います。
また、役に立つ技術内容を毎月お届け出来るようにしていきたいと思います。
今後もオールアバウトTech Blogの投稿にご期待下さい!

おまけ

おまけとしてこの半年で公開した記事のうち反響が良かった記事を紹介します。

3位

allabout-tech.hatenablog.com @naga1460さんが書いた記事でした。
広告配信システムやレガシーシステムのリプレイスという点で興味を持たれた方が多かったようで、PVも多い記事でした。

2位

allabout-tech.hatenablog.com 筋トレエンジニア芸人の@musclemikiyaさんが書いた記事でした。
技術的な話。というよりは考え方に焦点を置いた記事でした。
エンジニアの考え方の記事は引きが強くチーム開発をしていく上で参考になる記事でした。

1位
allabout-tech.hatenablog.com

nnmrさんが書いたサイト高速化についての記事でした。
本当に基本的なところから改善していったことを丁寧にまとめた記事でした。
はてなブックマーク数も1200超えとかなりブックマークもあり、社外の方からもあの記事見たよ。
という声をいただくことが多かったです。

上記で紹介した記事以外では、初めて行った開発合宿の記事リリースフローの変遷など様々な内容で公開をしてきました。 半年間という短い期間ながら結構評判のいい記事を公開できたと思います。

ドリコムさん、SpeeeさんとクローズドLT会を開催しました!

勉強会 LT DevOps

@takkyです。 先月9/29(木)にドリコムさん、Speeeさんと合同でクローズドLT大会(#adslt)を開催しました。
f:id:allabout-techblog:20161024182522j:plain

今回のLT大会は、新卒で入社した3年目までのエンジニアが各年代ごとに1人ずつ計9人発表する形式でした。

※ここでのクローズドは発表者・参加者が3社の方に限るという意味で使っています。
当日の楽しい様子に関してはtwitter#adsltで検索してみると良いです。

発表内容

今回はクローズドなLT大会ということもあり、弊社の発表内容についてのみ紹介します。 (上がっているスライドも発表当日のものとは異なり一部抜粋版となっています)

新卒1年目 @amymd

@amymdさんは新卒1年目(配属5か月)のサーバサイドエンジニアです。
citrusの開発を行っています。
そんな@amymdさんですが、この半年で非エンジニア(ディレクター)に対してエンジニアの仕事を体験してもらい
技術的な仕組みへの理解や開発手順について理解してもらうためにメンターとして活動を行ってきました。
この活動を通して非エンジニアに技術的な理解をしてもらえただけでなく、
プルリクエストベースでやり取りが出来るようになったそうです。
また、

教える側の立場になって自分が理解した「つもり」になっていたところに気づくことができるようになった。

と発表をしていました。
詳しい内容に関しては以下のスライドを参考にしてください。

新卒2年目 @sutchan

@sutchanさんは新卒2年目のエンジニアです。
主にcafesnapのWeb/API開発やアプリ開発 (iOS/Android)を行っています。
今回は"Androidアプリをガイドラインに沿ったデザインに変更してUXを改善した"、という内容で発表を行っていました。
特に今回はナビゲーションドロワーの改善について発表を行ってくれました。

新卒3年目 @takky

新卒3年目はが発表を行いました。
技術基盤グループで半年間行ってきたことについて発表してきました。
DevOpsを進めるために半年間で行ったログ監視基盤・アプリケーションパフォーマンス測定基盤の作成やCIとDockerの導入について発表を行いました。
詳しくは以下のスライドを参考にしてください。

懇親会 & LT

こういうときの懇親会といえばピザ!  ということでピザとアルコールで乾杯をしました。
f:id:allabout-techblog:20161024182557j:plain
入社からの年数や年が近いということもあり、盛り上がっていました。
また、飛び込みLTも開催され、ブログではあまりかけないようなぶっちゃけトークも出ていました。

まとめ

今回発表した新卒1年目の@amymdさんに感想を聞いてみたので掲載します。

今回の発表を通して、自分の考えや気づきを整理することができました。
また、その後の懇親会で発表に対してコメントを聞くことができて、
新たに気づかされることもあり、
このような交流会で発表&参加することができて本当に良かったと思います。
他社の方の発表では、業務で使用している技術や、
普段エンジニアとしてどのように考えて仕事に取り組んでいるのか
というお話を聴くことができ、大変刺激を受けました。
特に「引き出しを増やすことが大事」というお話が印象に残っており、
今後エンジニアとして幅広い領域に興味を持って知識を吸収していきたいと思います。
企画していただいた運営のみなさま、本当にありがとうございました。

とのことで有意義なLT会となったようです。

私も今回参加して勉強になりました。
社外の人と情報交換をしないと自分のエンジニアとしての立ち位置や今の流行りなどがわからないので
今回参加してよかったです。
また、各社の同じ年代の人の発表を聞くことが出来て良い刺激になりました。
会場を提供してくださったドリコムさん、今回参加してくださったSpeeeさんありがとうございました。  

オールアバウトの開発部では積極的に社外のエンジニアと情報交換をしたいと考えています。
その一貫として11/3(木)に開かれるphpカンファレンス 2016にもスポンサーとして参加しています! 

phpcon.php.gr.jp

是非ブースにお越しください! 
また、共同勉強会の開催など興味がある方もお気軽にお声がけください! 

サイトを37倍に高速化した7つの手法

サービス開発 newrelic 高速化 MySQL Laravel PHP

今回はオールアバウトのnnmrが弊社サイトAll About Japanの速度を高速化した経緯についてまとめます。

All About Japanとは

そもそもAll About Japan(以下AAJ)とは何かといいますと、弊社が提供している訪日外国人向けの日本紹介サイトです。
外国人向けサイトで、英語、中国語(繁体字)、中国語(簡体字)、タイ語、韓国語の5か国語に対応しております。
「Anime」「Izakaya」「Ninja」といったような特集や、実際に観光する人向けのモデルルート記事が特色です。

■ 特集
特集記事一覧 (url : http://allabout-japan.com/en/tag/sushi/ )

■ モデルルート記事
モデルルート記事 (url : http://allabout-japan.com/en/article/222/ )

技術的な紹介

LAMP環境です。
(サーバー構成は後に記述します)
また、PHPフレームワークにLaravelを使用しております。

高速化したきっかけ

ある日のこと、

「最近、All About Japanが遅くない?」

という話が上がり、実際に調査を行ったところ、このような結果が出てきました。

重たかった頃のAll About Japanのグラフ

このグラフはNew Relic(後に説明します)によって出力したもので、フロント側のキャッシュを介さずにサーバーへ直接のアクセスがあった際の負荷を表しております。
つまり、キャッシュが切れたタイミングでだいたい4.5sかかっているということです。

もちろん、この数値が低いに越したことはありません。
ちなみに、社内の他webサービスがやれ200ms台で「ちょっと遅いかも」なんて言われているくらいでしたので、4.5sというのはあまりにおぞましい数字です。

このようなことから、平均速度100ms台を目指す長い旅が始まりました。

実践1 : SQL見直し

5か国語対応ということで薄々お気づきかもしれませんが、翻訳が必要な分だけ1つの記事を表示させるためのクエリが以下のように複雑になりがちです。

AAJの記事ページ

  • その記事のIDから翻訳データを取得
  • カテゴリを取得
  • カテゴリから関連記事を取得
  • 関連記事のID毎に翻訳データを取得
  • 関連記事に紐づくタグを関連記事の個数分取得
  • タグ毎に翻訳データを取得

実際にslow queryで確認したところ、10秒以上かかっているクエリを発見しました。
slow queryとは、処理に時間がかかっているクエリをログとして残してくれる機能で、MySQLを使用している場合に設定が可能です。

クエリが重たくなる理由といえば

  1. select * している
  2. indexを貼ってない
  3. そもそもの無駄なクエリの数が多い

等々、いろいろありますが、ここはindexを貼ってないことを疑ってみました。

indexが上手く動いているか否かはexplainで確認できます。
explainを出す方法は割と簡単で、調べたいクエリの頭に"explain"を加えて実行するだけです。
するとこのように結果が出力されます。

explain結果

explainの項目は大体このように確認しております。

  • type → ALLもしくはindexと表記されている場合、レコード数が膨大である場合は見なおした方が良いです。
  • possible_keys → indexで使えるキーが無いとNULLになります。
  • key,key_len,ref → いずれもindexで使えるキーが無いとNULLになります。
  • rows → 少ないほうが好ましいです。大きいほど負荷がかかります。
  • Extra → Using Temporary, Using Filesortと出ている場合はindex追加で見なおした方が良いです。

select_typeに"DEPENDENT SUBQUERY"と書かれていることが多いのはLaravelのアクティブレコード(Eloquent)を使用しているためです。
サブクエリはなるべく避けた方が速度改善に繋がりますが、今回の場合、Eloquentで記述した方がプログラムの見た目は非常に簡潔であるため、
Eloquentの実装を残しつつ、そこで発生したサブクエリはindexで対応するという方針で進めました。

結果

index追加結果

ご覧の通り、4.5s→1.0sまで落ち着きました。

〜ここで学んだこと〜

  • 記事のデータがある程度溜まったらslow queryのログを見よう。
  • slow queryの結果からexplainを使ってindexを追加できる箇所を検討してみよう。

実践2 : SQL見直し(その二)

実践1と同じく、SQLを見直しましたが、実践1と比較して異なることといえば、New Relicを活用したことが挙げられます。
初めの方からちょくちょく登場しているこのNew Relicですが、これは、サーバサイドアプリケーションのパフォーマンス測定のツールです。 一方で、クライアントサイドの速度の計測といえば、GTmetrixが代表的です。https://gtmetrix.com/

New Relic overview

New Relicの基本画面といえばお馴染みのこちら「OverView」というものがあります。
PHP」「MySQL」「Redis」という項目からどの負荷の比率が高いかを確認することができます。

New Relic transactions

負荷の比率をphpのclass、method毎に確認したい場合、「Transactions」が活躍します。
左側の「Transactions」を選択するとこの画面に遷移します。

New Relic transactions

今回の場合、緑色と黄色の項目が時間によって急に負荷が上がっていることが確認できます。

New Relic transactions

更に、左のcontrollerの項目を選択すると、method毎の負荷の内訳が確認できるため
controllerに負荷があるのか、model側に負荷があるのかが確認できます。

今回もmodel側に問題がありそうだったので、実践1と同様に該当するクエリにexplainをかけ、indexが追加出来る場所に追加して対応しました。

結果

index追加結果

ご覧の通り、MySQLのドカドカしている負荷がかなりなだらかになっていることが分かります。
ここで1000ms → 600msまで落ち着きました。

〜ここで学んだこと〜

サーバーサイドの負荷の確認にはNew Relicを活用しよう。

実践3 : サーバー構成見直し

SQLのアプローチによる速度改善のネタが出尽くしつつあるところ、サーバー構成そのものの見直しに踏み込んでみました。

サーバー構成の変遷

AAJはサーバー構成に変遷がありました。
最終的に一番右側の構成となっております。

All About Japanサーバー構成

メディアローンチ時、AAJを構成する全サーバーはオンプレミスでした。 この後、中国からの閲覧に対応するためWebサーバーとキャッシュサーバーのみクラウドへ移行しました。 一部がクラウド、一部がオンプレミスという構成にしていることの問題としては、ネットワークの状況によってサーバー間の通信ができなくなり、サイトが表示されなくなることが挙げられます。
このようなリスクを考慮し、オンプレミスならオンプレミスに、クラウドならクラウドに寄せたほうが良いということになり、全てのサーバーをクラウドに設置しました。

結果

移行前

All About Japanサーバー構成 変更前の負荷

移行後

All About Japanサーバー構成 変更後の負荷

上記のNew Relicの結果から、一番速度が改善されているのがRedis(KVS)であることが分かるかと思います。
旧サーバー構成ではwebサーバー(クラウド)からKVSサーバー(オンプレミス)へのアクセス時に通信コストが発生していました。
これを全てのサーバーをクラウド上に置くことで通信時間が短縮し、速度改善に繋がったと考えられます。

〜ここで学んだこと〜

サーバそのものの構成も速度に影響することを心得るべし。

実践4 : 使用しているフレームワークでできる速度改善

前途の通り、AAJにはフレームワークとしてLaravelを使用しておりますので、Laravelでできる速度改善を調べたところ、
config:cacheとroute:cacheというものがありました。

config:cache

laravelのconfig/app.phpにある内容をキャッシュしてくれるものです。
サーバー上の該当アプリ以下でコマンドを打つか、composer.jsonに記述してcomposer updateを行うことで適用できます。

php artisan config:cache

詳細はこちら : https://laravel.com/docs/5.0/configuration#configuration-caching

route:cache

laravelのapp/routes.phpに記述する、routingに関する設定をキャッシュしてくれるものです。
こちらもconfig:cacheと同様、サーバー上の該当アプリ以下でコマンドを打つか、composer.jsonに記述してcomposer updateを行うことで適用できます。

php artisan route:cache

詳細はこちら : https://laravel.com/docs/5.0/controllers#route-caching

検証は怠らないこと

今回、どれが効果的であったかを明確にするため、(1)config:cacheのみ、(2)route:cacheのみ、(3)config:cache,route:cache両方という組み合わせで検証を行いました。
なんとなく、キャッシュしてくれるならどちらもやっておいた方が良いだろうと考えがちですが、検証の結果では(1)のconfig:cacheのみが一番効果がありました。

結果

config:cache導入前

config:cache導入前

config:cache導入後

config:cache導入後

ページ速度(平均)がおよそ30ms改善しました。

〜ここで学んだこと〜

  • フレームワークや言語等の特有の速度改善方法は調べて実践してみよう。
  • 本番サーバーに導入する前の検証は細かく行うべし。

実践5 : PHPのバージョンアップ

この時点で210ms台に突入し、最初が4.5sだったと考えれば十分に上出来だと思ってしまいがちですが、目標は100ms台です。
SQLの見直しも概ねでき、Laravelのキャッシュも入れたことだし、サーバー構成も見直したとなると、そろそろネタが尽きるところですが、ここでPHPのバージョンアップを検討しました。
元々AAJのPHPのバージョンは5.5.Xでした。※ オールアバウトの標準環境自体はPHP 5.5.Xです。
PHP 7が他のPHPのバージョンより高速であることや、使用しているフレームワークのLaravelがPHP 7に対応していることから、PHP 7へのバージョンアップを試みました。

結果

PHP 7導入前

PHP 7導入前

PHP 7導入後

PHP 7導入後

開発環境での検証と同様、50msの速度改善を図ることができ、 想定していた160ms台に突入しました。

〜ここで学んだこと〜

  • PHPのバージョンアップも高速化に繋がる

実践6 : KVSキャッシュ時間見直し

この、速度改善をあれこれ試みている過程で 「ひょっとしたらKVSキャッシュが利いていないのでは?」 という指摘があり、改めてKVSキャッシュ時間を調べてみました。

AAJのキャッシュ構成

KVSキャッシュの説明の前に、AAJのキャッシュは以下の構成となっております。

All About Japanキャッシュ構成

  • ブラウザキャッシュ
    ※特に設定せずにブラウザ側が生成するキャッシュ
  • varnishキャッシュ
    → webサーバーのアクセスの手前でページデータのキャッシュを返す
  • KVSキャッシュ ページを表示するために必要なデータをキャッシュ
    → DBサーバーのアクセスの手前でDBデータのキャッシュを返す

つまり、ブラウザキャッシュが無かったらvarnishキャッシュ、varnishキャッシュが無かったらwebサーバーにアクセス、webサーバーからKVSキャッシュを参照して、無かったらDBサーバーにアクセスをするようになっております。

varnishの仕組み

varnishのキャッシュ時間は60分に設定していますが、60分でキャッシュが切れる前にアクセスがあると、そこから60分のキャッシュが有効になります。

varnish,KVSキャッシュの仕組み

一方、KVSのキャッシュ時間は10分に設定されておりました。 仮にvarnishのキャッシュが60分で切れたとしても、それまでにKVSのキャッシュが切れてしまっているため、varnishのキャッシュが切れる度に毎回KVSのキャッシュが切れていました。

KVS時間調整

そのため、このようにKVSキャッシュの時間をvarnishキャッシュ時間より長くすることによって、varnishが切れた際にKVSキャッシュが有効になるように設定し直しました。

結果

KVS時間調整前

10:30に本番に適用しましたが、10:30を境にグラフが右下に下がっているのが分かるかと思います。

KVS時間調整後

最終的に120ms台まで落ち着きました。

〜ここで学んだこと〜

キャッシュを組み合わせている場合はキャッシュ時間の設定に気をつけよう

実践7 : New Relicのチェック

この度のAAJでは、サイトをローンチしてから、気がついたら表示速度が4.5秒になっていて慌ててあの手この手で速度改善を試みましたが、 今、思えば日頃から速度をチェックしているという習慣があったら良かったというように思います。

実践7は具体的に何かの施策をしている訳ではありませんが、New Relicをチェックする習慣についてまとめました。

New Relicを確認するポイント

Apdex

Apdexとは、Application Performance Indexの略で、ページ速度に対するユーザーの満足度を数値化したものです。 0から1の範囲で、1に近いほどページを閲覧するユーザーの満足度が高いと言えます。

詳しくはこちら

T値(閾値)の設定方法

Apdexの値は設定されたT値を基準に算出されます。

New Relic Apdex設定

画面左の"SETTINGS" → "Application"から"Apdex T"のフォームに然るべき値を入れて"Save application settings"を押せば設定は完了します。

T値については、あまり高い値を入れてしまうと常に1.0を保ち続けるだけなので、意味がありません。
常にApdex Scoreが0.8台くらいにしたい場合は現在の平均速度の1.15倍くらいに設定しておけばよいかと思います。
同様に、常にApdex Scoreが0.9台が良い場合は平均速度の1.7倍くらいが目安です。

noteの活用

New RelicはLITE PLANだと24時間分のグラフしか保持されません。 過去のグラフを保存する方法について、1つはスクリーンショットで地道に記録するというものがあるかと思いますが、noteという機能を活用するという方法もあります。

使い方についてはこちらに詳しく記載しました。

注意点

New Relic note グラフ

noteの場合、平均速度等、細かい数字は記録されません。

alertの設定

New Relic alert一覧

日頃からチェックとはいったものの、一日中New Relicに張り付いている訳にもいきません。 New Relicにはalert機能があって、例えば先ほどのApdex scoreが一定の値を下回った時にalertをSlackやメールで通知する設定があります。

詳しい設定方法はこちらをご確認ください。

まとめ

これまでの高速化の内容を以下にまとめました。

SQL見直し

  • index追加
  • クエリ見直し

活用したもの

  • slowquery
  • New Relic

サーバー構成見直し

AAJを動かしているサーバーを全てクラウド環境にまとめることで各サーバー間の通信コストを抑える

Laravelによる速度改善

config:cache追加

PHPのバージョンアップ

PHP 5.5.XからPHP 7へバージョンアップ

KVSキャッシュ時間を見直し

varnishのキャッシュ時間が60分に対してKVSキャッシュの時間を80分に修正

さいごに

このようにして、All About Japanは一番重たかった頃の4.5秒から120ミリ秒まで速さが改善しました。数字だけで見ると1/37まで改善したということになります。

All About Japanはサイトローンチして間もなく、データが少ない頃からは想定していなかった負荷が発生したことがきっかけで高速化のあれこれを試みました。 もし、「最近、サイトが重たいな…」と思うことがあって、これが参考になれば幸いです。

サービス成功のためにチーム開発でエンジニアができること

DevOps サービス開発

こんにちは、オールアバウトの筋トレエンジニア芸人の@musclemikiyaです。 普段はCafeSnapというiOS/Andorid向けのネイティブアプリの開発を行っています。 このアプリは大手チェーン以外の個性光るオシャレカフェの情報が満載なアプリですので、ぜひ使ってみてください。

さて、今回はサービスの成功可能性を高めるために、エンジニアがどのようにサービス開発に関わっていくかです。 エンジニアのサービスへの関わり方は様々だと思いますが、今回はよりサービス運営に近い立場のエンジニア(チーム)を想定しています。

※チームの規模やフェーズによって当てはまらないこともあるので、一意見として参考にしていただければと思います。

コードを書く時間の最大化 ≠ サービスの成功

サービス開発をする上でエンジニアの重要な役割は、コードを書いて機能を実装することだと思います。 そう考えると、コードを書く時間を最大化するのが一番の近道のようにも思えます。もちろん、コードを集中して書く時間は必要です。しかし実際はそれだけだとうまく いかないことが多いように思えます。それはなぜでしょう?

CafeSnapではチーム全体でユーザーやプロダクトの課題を考えることを非常に重視しています。 大きなチームでは全員で課題を考えるのが難しいこともあるとは思いますが、それぞれの与えられた領域の課題を考えるというのは非常に重要だと考えています。

エンジニアが課題から考えるメリット

なぜエンジニア含め、チーム全体で課題を考えることが重要なのでしょうか? いくつかメリットを見ていきたいと思います。

メンバー間で認識のすり合わせに時間がかからない

「Aの機能を実装して下さい」と言われるのと、「ユーザーはXという課題を抱えていて、それを解決するためにAという機能を実装します」というのでは、実装に入る時のイメージが変わりますよね。 さらに、課題を考えるプロセスにも参加しているので、実装しながらエンジニア自身で考えて機能を調整できるようになるはずです。

課題に対して適切な技術選択ができる

エンジニアが課題を考えるプロセスに参加することで、課題を考えると同時にその課題に対してはどのような解決手段があるかをイメージできます。

ここでは、あまり実装方法などを考え過ぎない方が良いでしょう。課題検討中に実装方法を考え過ぎると「それは難しそうだからできない」などといったネガティブな議論につながってしまうこともあります。 課題を考える初期段階では技術については置いておき、ある程度議論が進んだ段階で考えるなど切り替えも必要です。

成果物の質が上がる

これは個人差がある部分かと思いますが、UI/UXやコードの品質が高まってくるということがCafeSnapエンジニア内では起きました。課題をきちんと理解することで、ユーザーがどのように使うのかイメージできるようになったからだと考えています。特にエンジニアがUI/UXの調整にも深く関わるネイティブアプリの場合は、この部分でお互いが指摘し合う時間を減らすことが開発スピードを上げる秘訣にもなります。

チーム内で摩擦が起きにくい

開発をする上で色々な手法は存在しますが、チームでの開発がうまくいかなくなる原因の大半が人間関係だと思います。エンジニア以外の人が企画を考え、エンジニアが実装するだけの関係の場合、 必ずお互いに不満が生まれます。課題を全員が共有することで、方向性が揃い議論もスムーズに進むようになります。

ほんの一例を挙げてみましたが、少なくともエンジニアのチームに1人は課題を理解している人がいることで、開発のスピードは大きく上がります。

課題を発見するには

ではエンジニアが課題を見つけるには何が重要なのかを見ていきます。 ここでは3つ例を挙げて説明します。

1. 実際にサービスを使う

f:id:allabout-techblog:20160913175848j:plain

大前提として、自分たちが開発しているサービスを全く使わずに課題を考えることは不可能と言っても良いかもしれません。 漠然と数値を眺めていても課題は見つかりにくいもので、実際に使った時に感じたことを元に仮説を立て、数字と照らしあわせて課題を洗い出します。 CafeSnapチームでも普段のランチではアプリを使って恵比寿周辺のカフェを検索し、カフェ写真を投稿しています。

気をつけなければいけないのは、自分たちがユーザー目線でサービスを使っているつもりが、いつの間にか運営目線になってしまうことです。 課題を見つけるときは、常にユーザー目線なのか運営目線なのかを意識して切り替えることが重要です。

2. 数字を見る癖をつける

f:id:allabout-techblog:20160819152346j:plain

実際にサービスを使って課題の仮説を立てた場合、必ずその仮説がどの数字に当てはまるかをチェックする必要があります。また、毎日決まった数値を見ることによってユーザーの小さい変化にも敏感になることができます。そのためには、数字を見るハードルを低くするための仕組みづくりも重要です。

CafeSnapでは主に2つの方法で数字を常にウォッチしています。

GoogleAnalytics(以下GA) + スプレッドシート + アドオン

アプリのスクリーンビューやアクションを計測したり、ユーザーセグメントごとのコホート分析などを使って継続率を測ったりしています。スプレッドシートのアドオンを使用して、毎日決まった時間にGAの数値をスプレッドシートに反映し、毎朝前日までの変化をウォッチしています。

本番DataBase(MySQL)+ re:dash

re:dashは最近広まっているBIツールの一つですが、GAでは取得できない値を本番のデータベース(MySQL)やアクセスログをBigQueryにエクスポートしたものなどから 取得してグラフ化したりしています。

3. ユーザーインタビューを実施する

ここでは詳しく説明しませんが、Wantedlyの仲さんが下記の記事でも書いている通り、 ユーザーインタビュー(ユーザーヒアリング)は、あくまでも答え合わせだと思うとよいでしょう。
ユーザーファーストの嘘

しかし、実際に自分が作っているサービスのユーザーに直接会うというのは、様々な発見があったりフィードバックがもらえるので、 開発するモチベーションにも大きく影響します。CafeSnapでもエンジニアが積極的に企画のメンバーに同行しています。

まとめ

ここまでエンジニアがサービス成功のためにチーム開発でできることについて見てきました。どれも簡単にできそうなものですが、案外エンジニアとしてコードを書いていると受け身になっていたり、サービスの成功を最優先に考えていなかったりすることがあります。
今回紹介したのはほんの一例ですが、エンジニアとして何ができるかを常に考えながら、良いサービスになるよう日々取り組んでいきたいですね!

開発合宿でプロダクトを完成させるための5つの準備

開発合宿

こんにちは。オールアバウトの@naga1460です。

先日は1泊2日の開発合宿に参加してきました。

allabout-tech.hatenablog.com (合宿全体の様子は↑こちらの記事をご覧ください!)

合宿ではチームに分かれて開発を行ったのですが、私のチームでは最終的に2つのプロダクトを作り上げることができました。

2日間という短い時間でプロダクトをきちんと完成させるために必要な5つの準備を、今回は紹介したいと思います。

時系列に沿って説明していきます。

その1. 事前ブレストでアイディア収集〜合宿1ヶ月前〜

合宿の1ヶ月程前から、参加メンバー各個人が開発したいもの・アイディアを共有シートに書き出し始めました。

この時点ではチーム分けは行っておらず、完全に個人個人で自由なアイディアを書きました。 オールアバウトの既存プロダクトを改善する案から全く新規の案まで、50個以上の案が集まりました。

◎ここで沢山案を出すことで、その後のチーム分け・開発がスムーズに!

その2. キックオフでモチベーションを高める〜合宿2週間前〜

今回、開発合宿全体のキックオフを実施したことにより、参加メンバーのモチベーションが良い感じに高まりました。

ちなみにキックオフは合宿2週間前に行ったのですが、2週間というのは準備期間としても十分で、 かつ、ダレることなくモチベーションを維持できるようなちょうど良い長さでした。

キックオフでは、まずチーム分けの発表を行い、その後は早速各チームでの作業を行いました。

チーム分け発表

このチーム分けは、その1の事前ブレストを元に同じジャンルの希望者が同じチームになるよう、合宿運営側で決めたものです。 私はBOT系の開発希望で出していたので、同じくBOT希望者との2人チームとなりました。

◎ここでの盛り上がりがその後のモチベーションを左右!

チーム作業開始

チーム分け発表の後は、そのまま各チームでの作業を始めました。

私のチームはまずブレストを行い、作りたいBOTの案を出していきました。 事前ブレストの時点である程度案が出ていたので、チームでのブレストはとてもスムーズに進みました。

20分程度の話し合いの結果、最終的に下記の3つの案に固まりました。

  • All About LINE BOT
    • 概要
      • 単語を送信すると、関連するAll Aboutの記事を返信してくれるLINE BOT.
    • 作ろうと思った理由
      • 他社のLINE BOTを見て、All AboutのLINE BOTを作ってみたくなったので。
  • CafeSnap LINE BOT
    • 概要
      • 地名を送信すると、CafeSnapに登録されているカフェを返信してくれるLINE BOT.
    • 作ろうと思った理由
      • LINE BOTを作るのであれば、WebメディアであるAll AboutよりもスマホアプリであるCafeSnapの方が親和性が高そう!と思ったので。
  • 自動勤怠通知Slack BOT
    • 概要
      • 毎朝始業時刻の時点でまだ出社していない人の一覧をSlackの公開チャネルにお知らせしてくれるSlack BOT.
    • 作ろうと思った理由
      • 遅刻時に毎回手動で勤怠メールを送るのが皆辛そうなので、BOTが代わりに送ってあげれば良さそう。。せっかくなのでメールではなくSlackで。

◎チーム分け発表直後にすぐチーム作業を開始することでモチベーションを維持!

その3. 調査期間を設ける〜合宿2週間前〜

※ここからは私のチーム独自の動きとなります。

私のチームでは、キックオフ後〜合宿2週間前の期間には、キックオフで出した案それぞれの実現方法、実現可能性を分担して調査しました。

その結果、実際に開発するプロダクトは下記の2つに決定しました。

  • CafeSnap LINE BOT
    採用理由:
    • LINE BOTにはオールアバウトのどのサービスもまだ踏み込んでいないため、新領域への挑戦ができる。
    • CafeSnapはスマホアプリメインのサービスなので、ユーザーとLINE BOTとの親和性は高そう。
    • CafeSnapの既存のカフェ検索APIが利用可能。
    • 位置情報等と連携した拡張も見込める。
  • 自動勤怠通知Slack BOT
    採用理由:
    • IOTと絡めた開発に挑戦したい。
    • 席にいるかどうかは、イスに圧力センサーを置けば検出可能。
    • 圧力センサーとサーバー間のやりとりにRaspberry Piを使えないかと勉強会に飛び込み参加してみたところ、割と簡単そう。
    • サーバーでRaspberry Piから受信した情報を元に、席に座っていない人を特定し、Slackに通知すれば実現可能。

残りの案は、下記の通り没となりました。

  • All About LINE BOT
    没理由:
    • 既存の記事検索APIが無くAPIも新規開発となるため、2日間の合宿では間に合いそうにない。
    • All About公式のLINEアカウントが既にあるので、新規性が微妙。

採用した2つのプロダクトはどちらも小規模な開発となるため、チームの1人が1つを別々に開発することにしました。

◎調査期間を設けることで、納得のいく取捨選択を!

その4. プロダクトに必須なものを揃える〜合宿1週間前〜

合宿1週間前には、それぞれのプロダクト固有で必要となるものを準備しました。 全て「これがないとプロダクト開発は不可能」というものなので、プロダクト採用が決まってからすぐに準備に取り掛かりました。

  • BOT API Trial Accountの取得(LINE BOT開発)

    • LINE BOT開発に必要な"BOT API Trial Account"を取得し、IP等の設定テスト。
  • SSL対応ドメインの用意(LINE BOT開発)

  • 圧力センサー+Raspberry Pi(自動勤怠通知Slack BOT)

    • イスに圧力センサーを仕込んで在席確認を行うため、圧力センサーの用意とRaspberry Pi連携テスト。
      • ※ここで「圧力センサーは壊れやすい」ということも判明。

◎プロダクト開発に必須なものは早めに揃えておく!

その5. 開発環境を整える(プロダクト共通)〜合宿1週間前〜

こちらもその4と同じく合宿1週間前ですが、開発環境の構築を行いました。 作るものによって用意するものは変わると思いますが、事前に準備することで合宿当日は開発に集中することができます。 私のチームでは次の環境を準備しました。

  • Webサーバーの用意・疎通確認

    • どちらのプロダクトも外部から通信可能なWebサーバーが必要なため、GCE上にチーム共有のインスタンスを立ててWebサーバとした。
  • リポジトリ作成・疎通確認

    • GCP→Bitbucketのpushテストも含む。

これらは普段やらない作業なので、やり方を思い出しながらやると思いの外時間がかかりました。。

◎念入りに準備し、開発の障壁を減らしておく!

そして、開発合宿当日。。

準備万端で開発合宿当日を迎えたため、当日の開発はとてもスムーズに進みました。

  • 1日目
    • メイン機能をほぼ完成させる。
  • 2日目
    • 仕上げの調整に徹する。

このようなスケジュールで、最終的にCafeSnap LINE BOT、自動勤怠通知Slack BOTどちらも完成させることができました。

開発を振り返ってみて

今回の開発を振り返り、良かったこと、悪かったことをまとめました。

良かったこと

キックオフでモチベーションが上がった

キックオフでモチベーションが上がったおかげで、最後まで開発をやりきることができました。

キックオフは実施して本当に良かったです。

事前準備のおかげで開発に集中できた

事前に環境構築や疎通確認等の準備を念入りすぎるほど行っておいたおかげで、 合宿当日はメインの開発にじっくり取り組むことができました。

これなくしては2日間でプロダクト完成まで漕ぎ着けられませんでした。

1人でプロダクトを1から作って完成させた達成感

普段の業務は既存プロダクトの改修案件が多いですし、新規プロダクトの開発だとしても複数人で開発することがほとんどなので、 1人でプロダクトを完成させる経験のできる機会は多くありません。

ですが今回の私のチームでは1人1プロダクトを開発したため、 自分の力で1つのプロダクトを完成させられた、という大きな達成感が得られました。

悪かったこと

チームでサーバーを共有することによる弊害

チームで同じサーバーを使用していると、他メンバーによるライブラリインストール等の影響を受けてしまい、思わぬところで時間を取られてしまうこともあります。

よくよく考えれば、プロダクトによって必要な環境、ライブラリも違うため、サーバーはチームごとではなく、プロダクトごとに構築しておくべきだったと思いました。

サーバーへのライブラリインストールに手こずる

CafeSnap LINE BOTの開発の途中で、画像に文字入れをする際の文字サイズ指定等のために、特定のライブラリをサーバーにインストールする必要があることが発覚しました。

色々試したのですが、上手くインストールできず、、、最終的には時間の関係で諦めました。

無駄な時間を使うことになってしまったため、サーバーにインストールする系のライブラリ辺りは事前に準備しておけば良かったです。

GCP設定周りがインフラ担当者任せ

インフラ担当者の方が慣れている、会社のアカウントで合宿用のサーバーを管理する、 等の理由から、GCPの設定周りはインフラ担当者任せとなってしまっていました。

これにより、サーバートラブル時に毎回インフラ担当者に聞く、 という手間が発生してしまったため、その辺りも含めて自分のチームで準備しておいた方が、 開発がよりスムーズになっていたと思います。

最後に

このようにして、私のチームでは無事2つのプロダクトを完成させることができました。

せっかくのチームなのにバラバラに開発するのはもったいないのでは?という意見もあるかもしれませんが、 仕様を一緒に考えたり、実装で詰まった部分の相談、息抜きの雑談等、チームを組む事によるメリットは大いにあったと思います。

ただ、また開発合宿をするとしたら、次はチームで1つのプロダクトを協力して開発したいです。

※今回LINE BOT開発でリッチメッセージ送信について知見を得られたので、サンプルコードと合わせてQiitaに投稿しました。 是非参考にしてください! qiita.com

SeleniumでE2Eテストを始めよう

selenium テスト

オールアバウトのsankameです。

突然ですが、Webブラウザのテストってやりずらいですよね…

ブラウザ毎に挙動が違ったり、結局、担当者頼みになって、その人が代わったらまた障害が起きて…と。

エンジニアならこういう問題はさっさと自動化してスマートに解決したいものです。

※このアプリのソースコードはページ末尾のリンクからご利用下さい。

…とは言え、「なる早でリリースを」という場面ではテストは後回しにされがちで、さらに第2フェーズが始まると尚更テストに時間を割くのが難しくなります。(※1)

オールアバウトではユニットテストがまだ全アプリに入っていない状況ですが、とにかくハードルを下げつつ半年ほど前からE2Eテスト(※2)を実施しています。(※3)

(※1) オールアバウトには専任のQAチーム (Quality Assurance)がないため、開発エンジニアがそれを担当しています。

(※2) End to End Test (ネットワーク終端ホストによるテスト)。Webブラウザによる利用者視点でのテストと解釈。

(※3) Google社によると、本来は[ユニットテスト数] > [E2Eテスト数]とのこと…参考

ハードルを下げる

テストのハードルが高いのは、プロダクションコードと合わせてテストコードを書くのでコード量が倍になる点だと思います。

その負担を減らすため、まずはJavaScript (以下JS)エラーをチェックすることに特化したテストアプリを作成し、毎日動かすことにしました。GitHub URL

プラットフォーム

下記の理由でE2EテストにはSelenium + Javaを採用しています。

  • ネット各社の導入事例など、利用実績が多い。

  • Seleniumは十数年の歴史がありながら、最近でも比較的活発にメンテされている。参考

  • SeleniumW3Cで標準化が検討されている。参考

システム構成

準備

  • CentosWindowsで予めSeleniumサーバーを起動しておきます。

    (パスやバージョンは適宜読み替えて下さい)

    $ java -jar /usr/local/src/selenium-server-standalone-2.41.0.jar -role hub

    C:\apps\selenium>java -jar selenium-server-standalone-2.42.2.jar -role webdriver -hub http://【CentosのIP】:4444/grid/register

構成図

f:id:allabout-techblog:20160811023523j:plain

※図中のロゴは右のサイトより使用。 Selenium HQ さくらのナレッジ

処理フロー

○数字は上記の図に対応

  • ① アプリがテスト対象のURLを読み込みます。

  • ② アプリからWebDriver APIをコール。

  • ③ 2つのSeleniumサーバーを中継します。

  • Windows側のSeleniumサーバーがブラウザを起動し、テスト対象のURLにアクセス。

  • ⑤ アプリは発生したJSエラーをWebDriverから受け取り、メールで通知します。(JSエラー収集はライブラリを使用)

特記事項

  • 将来的にテスト対象のブラウザを増やしたり、テストの並列化ができるように、③でSeleniumサーバーをhubとnodeの関係にしました。

  • アプリはJenkinsで定期実行していましたが、Wercker+Deployerに移行しつつあるため、現在は暫定でRundeckから行っています。

  • WebDriverが対応しているブラウザのバージョンは決まっているため、敢えてブラウザの自動アップデートは止めています。

エラー修正

上記⑤のメールは開発者のメーリングリストに送信しています。

メールで送信するだけだと、そのエラーに誰が対応していてどんな状態なのか管理しずらいので、Google Apps Scriptを使ってGmailからGoogleスプレッドシートに自動で読み込ませるようにしています。

f:id:allabout-techblog:20160811234215j:plain

このシートをもとに、毎週の定例会議で「修正担当者をアサイン」→「進捗確認」を繰り返しています。

まとめ

次のステップとしては、リンクの飛び先や画面要素のチェックなど、Selenium本来の機能を活かしたテストコードを書いていく予定です。

また、現在は1日1回のテストを定時に実行しているだけなので、リグレッションテストという意味ではまだ不十分です。

将来的にはCIツールと連携して、ステージングにWebアプリをデプロイしたタイミングでE2Eテストを回したいと考えています。

ソースコード

プルリクなど頂ければ幸いです。

github.comGitHub - allabout/js-e2e-test: Selenium WebDriverを用いてJavaScriptの自動テストを行います。