LaravelでのAPI開発を爆速にするためにやっていること

松本宏太氏(以下、松本):お越しいただきありがとうございます。『LaravelでのAPI開発を爆速にするためにやっていること』というタイトルで発表させていただきます。よろしくお願いします。

もしかしたら知ってる人がいるかもしれませんが、kotamatというアカウントで、ナメた顔(のアイコン)でやってます。あと、あんまりテンション高くないので、イアンさんみたいな感じのことを求められるとしょんぼりします。

スカウターという会社でCTOをやっています。スポンサーさせていただいているような会社です。サーバ(サイド)とかインフラ周りをやりつつ、フロント(エンド)もやっています。あと、自転車が好きです。

Laravelとは完全に関係ないんですが、NuxtMeetupというイベントを主催していて、ちょうど来週の月曜に(第7回を)やります。

あとはLaraVue勉強会というLaravelとVueの勉強会とか、さっきIRTで、LaravelとVueの相談会みたいなものをやっています。

(スカウターが)何をやっている会社なのか、いまいち掴めないところがあると思います。転職にまつわるサービスを展開していて、SCOUTERというサービスからSARDINE、back checkといろいろやってきております。

SCOUTERって何? というと、転職エージェントっていっぱいありますけど、彼らは免許を持ってやってるんですね。そこらへんをハックして、免許なしで誰でもできるようにしたサービスです。

SARDINEは、実際のエージェント様向けのプラットフォームです。

最近、back checkという、どちらかというと転職者向けの新サービスを開発しています。

履歴書とか経歴書ではわからないところについて、前職の上司や一緒に働いていた人から推薦状をもらって、リファレンスチェックをして選考後のミスマッチなどをなくすという取り組みです。

お察しの通り、全サービスをLaravelとVueの構成でやっています。

今日は、そもそもなんで爆速開発をしなきゃいけないの? というところをご説明させていただいたあと、実際にやってきたことをご紹介できればと思っています。

爆速開発が必要な理由

なんで(爆速開発を)やらなきゃいけないのか。うちは求人系のサービスをやっているのですごく体感しているんですが、(エンジニアは)めちゃくちゃ売り手市場です。

みなさん、転職しようと思ったらサクっと転職できると思います。初心者の方でも、Twitterでサクっとつぶやけば、みんなダーって来るようなところがあって、本当に売り手市場です。なので、あまり人を増やせないというところがけっこうあるなと。

あと、プロダクト開発って、バランスがすごく大事だと思っていて。

チーム開発って、PMやPO、デザイナー、エンジニアという、いろいろな専門職が集うものだと思っているんですけど、特定の職種だけを増やしても、全体のアウトプットは上がらないんですね。

そもそも、知的労働系だと、人を増やせばいいってわけじゃないというところがすごくあると思います。コミュニケーションをしっかり取らないとやっぱりうまく進まないので、コミュニケーションコストが増大したり。

他にも、うちだと、入社後にオンボーディングというかたちでこういうサービスをやってますよということを説明してるんですが、そういうコストもどうしてもかかってきます。あと、専門性にムラがあったりもする。

人を増やせばいいわけじゃないところに対しての解決策って、個々のパフォーマンスをいかに上げるかが、各社課題になってきます。ここをいかにやっていくかが、差別化の要因になると思っています。

何をやってきたのか?

じゃあ何をやってきたの? というところで、今回は、DXの向上と変更箇所の最小化についてお話ししようと思います。

DXは、Developer Experienceの略です。UXのUがDになった感じです。要は、開発者にとっての体験を向上させようという取り組みですね。

今やっている取り組みは、フロントとAPIを別リポジトリにしたり、APIスペックの出力を自動化したり(というものです)。今回は、ステージング環境の整備も挙げておきます。

まず、フロントとAPIを別リポジトリにすることについて。うち、開発者ブログというものをやっていて、僕も参加しているんですが、(そこで)2017年11月に、同一リポジトリで管理するときの構成について書いていました。

まだNuxtが1.0に上がってないくらいのときです。(その記事)では同一リポジトリで管理したほうがいいよって言っていたんですが。

ツラミが発生しました。

うちはCIをTravisで回してるんですが、ちょっとした変更、例えば文言の修正やCSSのちょっとした修正でも、フロントとサーバー、それぞれのビルドが走ってしまう。

これがどんどん、時間が増えれば増えるほど時間がかかる。ある程度の時間が経つと、メンバーがTwitterを始める(笑)。復帰してくれない。辛い。

そういうことがあったので、(フロントとAPIを)分割しました。

もともとあったAPIのサーバーこにlaravel-mixとかごちゃっと入れてたやつをフロントに設けて、ビルドをPHPの構成にしました。ビルドプロセスを分割したんですね。

ビルドに対してcomposer installとかyarn installとかやって、スクリプトに関してもCodeSnifferとか、あとはjest入れたり、デプロイするときはcodedeployとかやるというような感じでやってたんですけど。

結果、ものすごくシンプルで、1個あたり4分かかっていたところがフロントで3分、サーバーで1分という、4-3=1みたいな感じでいい感じに分かれました。

分割する上で行った工夫

これだったら普通ですが、いくつか工夫があります。

別リポジトリにする際の注意点として、LaravelでVueを使うときに、bladeにフロントがくっついている状態で書く人がけっこういて、それがデファクトに書かれていたりするんですが、そうすると依存性がすごく高くなってしまうんですね。

分割するときはいいんですが、将来性を考えたとき、SPAとAPIの構成にしないと、どっちも気にしなきゃいけなくなって、両方できる強いエンジニアしか触れなくなっちゃうというところがあります。

じゃあSPAとAPIにするとなると、いろいろ注意点があります。さっき言ったblade下のところは剥がす必要があるし、ルーティングも、Laravelから剥がしたほうが結局はいいところもあります。

あと、認証形式、というか状態管理なんですけど、そこの部分を、セッションからトークンベースの認証形式にしないとうまくいかなかったり。

あと、laravel-mixをなるべく使わないようにするとか、デプロイフローがどうしても変わるパターンがあると思うので、インフラもちょっと気をつけなきゃいけないというところが注意点です。

ブログでも紹介していますが、Laravelのすごくいいところは、レスポンスに関してちょっと変えるだけでうまくいくというところだと思っています。Vueの関数でbladeの名前を与えて、articleとかをあげるところに対してレスポンスのjsonを返してあげるだけで、レスポンスが返るところは基本的にできます。

上の部分のユーザーのauth情報を持ってきて、articleをヘッジするというような処理に関しても、例えばJwt Authというパッケージを使うと、sessionのときに使った関数でも、同じEloquentが返ります。

処理上はあまり変わらないので、ほぼほぼ軽微な変更だけで済んでしまうのが、Laravelの素晴らしいところだと思っています。

Laravel mixを使わない

さっき触れた、laravel-mixを使わないという話について。

リポジトリ分割後は、mixを使うメリットがなかなか発生しなくなります。

versionという、Laravel上で展開するときにハッシュを変えてキャッシュをクリアしてくれるものがあるんですが、それがもちろん使えない。

(それから、)mixの依存パッケージのバージョンに左右されます。以前、webpackの4に上げようと思ったのに、mixがぜんぜん上がらないから(webpackも)上げられなかったということがありました。

あとは、捨てづらい構成になってしまうところです。Laravelからほかのフレームワークに変えようというところも難しくなります。

インフラの再考について。

インスタンス1個に対して、サーバーとフロントでそれぞれデプロイ先が変わることを考えると、再構成は検討が必要です。このへんは複雑なので、もし聞きたければアスク・ザ・スピーカーで聞いてください。

APIスペックの自動生成について

APIスペックの自動生成について。さっき言った、責務範囲を分割できるというところや、デプロイ時のビルドの高速化がSPAの構成の特徴だと思っています。

ただ、それをやると違う課題も発生します。それぞれの通信のインターフェースを定義しないと壊れやすいです。Swaggerでやったり、各社でいろいろやってるとは思いますが、その部分でメンテコストが上がってしまう、ということが特徴としてあります。

メンテがけっこう大変なのに対して、一部のアプリケーションでAPIスペックの自動生成を行っているということがあります。これがFeatureテストを記述するだけで、RFC7230ベースのファイルが生成される。

これが、httpの公式というところがあるので、PHPStormやVSCodeやAtom、このファイル形式に対応しているのが、かなりのIDEやエディタで対応されているので、これで実行できちゃうファイルが生成されます。

なぜできるかというと、Laravelの標準のAPIテストがすごく優秀で、this->getJasonみたいな感じで呼ぶだけでフィーチャーテストが書けたり、POSTもメソッドで書けたり、ペイロードも配列でサクっと書けます。こういうものがあったので、それを拡張して、スペックファイルをそのときに生成するというものを自分で作ったりもしています。

インストール方法は、こんな感じ(composer require --dev kotamat/laravel-apispec-generator)でrequireして、親クラスをそれに継承したものにしてあげます。isExportSpecをtrueにしてあげると、ファイルが出力されるようになっています。中身はそのまま使用しているので、変える必要はないです。

実行すると、storageのapp配下にapiのtestのディレクトリの中にメソッド名のPOST.httpみたいなファイルが生成されます。

PHPStormとかだと、左上に再生ボタンが出てきます。

これを実行すると、下のほうでresponse headerとbodyが返ってきて、Content-LengthとかTypeとかも返ってきます。

この部分を生成されたあとのあれに対してbodyをちょっと変えてあげるだけでこのレスポンスも変わるので、テストがすごく楽になります。

これの結果、普通、テストって単なるエンジニアの不安払拭のためだけだと思うんですけど、業務上必要なフローに昇華されて、プラスアルファの価値が生まれます。

あと、データ構造に合わせて動的にスペックが変更されるので、そこのメンテナンスコストはゼロというところや、さっきのところでTDDライクというか、ちょっとしたbodyの変更とかもサクっと検証できるところがメリットでした。

ステージング環境の整備

あとは、ステージング環境の整備について。うちだと、プレビュー環境とステージング環境というものをこれから用意していこうと思っています。

プレビューは確認環境用です。それぞれ任意のタイミングでデプロイできるというもので、例えば、CSメンバーとか営業メンバーが確認できる環境を、複数用意できるというものがプレビュー環境です。

ステージング環境は最終確認用で、特定のブランチ命名規則にてpushされたものが自動でデプロイされます。これは本番のインフラ構成と同じなので、構成的な問題も検知できます。ジョブキューやCDNなどのレイヤーに関しても、テストができるものを作っています。

プレビューに関しては本当にシンプルで、Slackにコマンドを打ったらcapistranoがデプロイするというものです。対象ブランチを指定してあげたり、あとは特定のディレクトリを指定してあげることによって、ディレクトリごとのサブディレクトリにデプロイされるものを作っています。

ステージンクに関しては、全部一緒の構成のものを用意してあげて、codedeployでデプロイするということをやっています。

ステージングに関しては、特定のブランチ命名規則について話したんですけど、これはgit flowを参考にできると思っています。

本番はmasterにマージされたとき、ステージングはrelease/*にマージされたとき、というところで分けられると。

これをするとリリース時の規則が制御できるので、今、何がステージングにあるのかがGitHubを見ればわかる状態になります。

(スライドを指して)これがgit flowです。これに対して、緑がrelease branchesです。release/0.1が上がったらこれがステージングに上がるし、次developしたときに、リリースしたらrelease/0.2が上がるという感じです。

変更範囲の最小化

最後に、変更範囲の最小化についてお話ししたいと思います。弊社のサービスは、求人企業や管理者や転職者やエージェントなど、関係するユーザーが多いし、それぞれにデータを渡してあげる処理がものすごく複雑に絡み合っています。

例えば、転職者がこの求人にアプライしたいということが、エージェントにも通知されるし、採用企業にも通知されるというふうになっています。

こういう特性があるので、それぞれのユーザーで同ドメインの情報を扱う必要があります。同ドメインの処理の編集が頻出するので、責務をかなり凝縮する必要があるというところが課題でした。

以前は、同じ処理が複数のリポジトリで書かれていて、全部編集しないといけなかったんですが、今回の改修で1つにまとめることができました。ユーザーのベースのリポジトリと、ドメインベースのリポジトリを分割しました。

マイクロサービスではないけど、そのリポジトリをインポートするという、ゆるいマイクロサービスみたいなことを、ゆるいDDDでやるという感じです。

(スライドを指して)左側が、ログインするユーザーです。各ユーザーごとに必要な処理のみがここに記載されています。右側に、ドメインに必要なコンポネ集が入っています。求人の詳細ページや求人の検索は、すべて右側に集約されています。

各コンポーネントがリポジトリとしてアプリケーション側に反映されているというところで、今回NPMのほうは省きますが、composerに関しては、org/some_domainみたいなものがあったときに、それのバージョン指定と、あとはcomposerのPackagistですかね。

Packagistのプライベートのリポジトリを用意するのもあれだなと思ったので、GitHubを直接リポジトリで参照できるようにしています。これがrepositoriesというキーに配列を渡してあげることによって、Packagist以外のところからもインポートできるようになっています。

type : gitというものを指定してあげることによって、そのvendor配下で変更したものをpushすればこっち側にも反映されるというふうになっているので、インポートした瞬間に1つの大きなmono repositoryにもできるけど、ソースコードは分割されているという、いいとこ取りの構成が、composerだとできるようになっています。アプリケーション側はこういう感じで指定しています。

コンポーネント側は、適切な名前空間を指定してあげるだけですね。autoloadのところで、psr-4みたいな感じで、OrgのSomeDomainでsrcディレクトリの配下がpsr-4の形式でディレクトリ排出されてるよっていうところだったり。

あとは、Laravel5.5以降ならextraというやつに対応しているので、そちらにlaravelのprovidersを指定してあげると、app.phpにプロバイダを記載しなくてもこれが見られるようになるという感じで、簡単にプロバイダを定義できます。

エンジニアを増やすだけでは良くない

ちょっと急ぎ足でしたが、まとめとしては、エンジニアは単に増やすだけではあまりよくない、もうちょっとインテリジェンスにやるべき、というところです。

効率を上げるには環境を整備して無駄を省いていくというところが、ゆくゆくのプロジェクト管理に対してかなり響いてきます。あとは、アプリケーション側のアーキテクチャーも随時見直して、変更しやすいコードをしっかり作っていくことが大事ということで、まとめとしておきます。

いつものやつですけど、スカウターではエンジニアを絶賛募集中です。LaravelとかVue、あとAWSの構成を一緒にやってくれる方を募集しています。

scouter-laravelや、scouter-vueというURLでアクセスしていただければ閲覧できますので、もしよければご応募ください。

以上です。ありがとうございます。

(会場拍手)

SPAに切り出す場所をどう選んだか?

司会者:時間が余っているので、質問がある方は挙手をお願いいたします。

(会場挙手)

質問者1:ありがとうございます。既存で最初Vue.jsで開発していて、そこからSPAに切り出す場所をどう選んでいたのかお聞きしたいです。

松本:SPAに切り出す場所というのは……。

質問者1:同じアプリケーションで「ここはSPAにして」「ここはそのままVue」「ここは普通のLaravelの機能でそのままいく」みたいに切り分けるとき、どうしていたのかなと。

松本:最初作ったときに、もうbladeにほぼ依存させてない状態で作ってたんです。結果的に、全取っ替えにはなってるんですけど。

さっき言ったように、Laravelはいい感じにレスポンスを定義してあげるだけでJSONを返すようにしてくれたり、authはJwt Authを使うと変更なしでいけるということがあったので、全取っ替えでもそんなに工数がかからなかったという事情があります。

今、PHPでがっつりHTMLを書くことになったら、もしかするとけっこう大変かもしれないです。

質問者1:今RailsとVue.jsがけっこう依存しちゃっていて、SPAにするのはすごく大変かなと思っているんですが。

松本:そうですね(笑)。そこの部分はまた別のハックが必要という気がします。

質問者1:わかりました。ありがとうございます。

司会者:僕から質問してもいいですか? ちなみにWebアプリケーションのエンジニアじゃないので、Laravelは新卒1年目の研修以来触ってないんですが(笑)。

爆速で開発するために、リポジトリをサービスごとに複数に分けてらっしゃいましたが、新しく入ってきたメンバーとかには、1個のリポジトリにまとまっているほうがわかりやすいんじゃないか、と思うんですね。そういう新しく入ったメンバーからの反応って、どういう感じでしたか? 

松本:難しいねって意見は、どうしても発生してしまいますね(笑)。完全に1個にするという方法もあるとは思うんですけど、認証形態、もともとがそれぞれ分かれていた状態から始まっているので。

一番上から全部一緒にしてしまうと、全部SPA化しないといけないというか、すべて同時にしなきゃいけないので、そこらへんがまだ踏み切れてないというところがあります。

司会者:分割していったほうが一つひとつSPA化できるから、そっちのほうが早い改善ができるという?

松本:そうですね。それに依存させるというところは、そんなに難しくはない。ただ、リポジトリそのものを消してしまうと、デプロイフローとかも変わってきちゃうので。

司会者:わかりました。ありがとうございます。

(会場挙手)

RFC7230とは何か

質問者2:どうもありがとうございました。僕も、Laravelで既存のFeatureテストを使ってテストを書いています。ご説明の中にあった、APIスペックでRFC7230に沿って書くというところがわからなかったので、もう1度教えていただけると助かります。

松本:RFC7230はどういう形式かというと、(スライドを指して)こんな感じになってます。メソッド名とURL、URIか。その下にRequestHeaderがあって、bodyがある。

だいたい.httpというファイルだったり.restのファイル名にしてあげると、要はPHPファイルだったらPHPということをエディタが認識してくれますけど、同様に、HTTPファイルだったらこれは実行可能なファイルだということを認識してくれます。

なので、この形式で吐き出してあげれば実行できます。これがIntelliJとかPHPStormだと標準で入ってるんですが、VimとかEmacsとか、あとはVSCodeっていうのはプラグイン入れれば実行できるようになっています。

質問者2:なるほど。この.httpというファイルを……Featureテストを作ってから出力するんでしたっけ?

松本:Featureテストを実行すると勝手に出力されます。

質問者2:なるほど。ちなみにこの.httpファイルは、作られたあとリポジトリにコミットされますか?

松本:storageのapp配下に入っているので、されないです。その都度生成する感じですね。

質問者2:都度生成して、それをPHPStorm上からポチって再生ボタンを押してテストするときに使うってことですね。

松本:そうですね。テストを実行した瞬間の最新のスペックが取得できるので、ここのバージョン管理とかメンテナンスは不要になっています。

質問者2:なるほど、わかりました。ありがとうございます。

司会者:松本さん、ありがとうございました。お時間になりましたので、これにて『LaravelでのAPI開発を爆速にするためにやっていること』を終了とさせていただきます。このあとアスク・ザ・スピーカーをエクストラトラック303にて行いますので、まだご質問がある方はそちらに移動してください。

最後にもう1度、松本さんへの盛大な拍手をお願いいたします。ありがとうございました。

(会場拍手)