リリース直前にLumenからLaravelに移行した話

田口航氏(以下、田口):資料は99パーセントオッケーが出ると思うので、後日公開します。

(会場笑)

「リリース直前にLumenからLaravelに移行した話」と題しまして、株式会社GameWithの田口航と申します。よろしくお願いします。

今回Laravelを使ってリリースしたMangaWithのアーキテクチャ設計だったり、なぜ開発初期はLumenを選択したか、なぜ(Laravelに)移行することになったか、最後に移行してみて実際どうなったかという話をしたいと思います。

最初に自己紹介をしますと、田口航と言います。FF14とかビールとかが好きで、最近FF14に麻雀が実装されたのでゲームの中でゲームをやってるっていう、よくわかんない状態になってます。

弊社は、株式会社GameWithといいます。知ってる方、どれくらいいらっしゃいますか?

(会場挙手)

ありがとうございます。

2013年設立で、ゲームメディアサービスのGameWithを運営しています。ゲーム攻略だったり、最近は動画配信もやってます。

新規事業として、僕が携わったマンガサービスのMangaWithと、最近ブロックチェーンゲームを開発中です。このMangaWithは最初Lumenで実装していたんですが、途中でLaravelに変わったということで、今日はその話をしていきたいと思います。

スケジュールをざっと紹介すると、去年の6月ごろに開発をスタートして、だいたい半年後の12月にリリースしました。最初はLumenを選んだんですけど、リリース1ヶ月前の11月にLumenではどうしても立ち向かえないところが出てきて、Laravelに移行しました。

Lumen、知ってるよって方?

(会場挙手)

けっこういらっしゃいますね。簡単に説明すると、軽量ということです。

公式によると、1秒間にさばけるリクエスト数が1,900と、SilexやSlimを抜いて一番多かったり、Laravelの標準のEloquentやValidation、Routing、Middlewareとかも標準で持ってるし、Laravelへの移行も簡単だそうです。

もう少し開発寄りの話でLaravelと違うところは、configディレクトリがないです。最初びっくりしたんですけど、基本的にenvを使ってやる設定になっています。一応、configディレクトリを勝手に作って、その中にdatabase.phpなどを書いて運用することも可能です。

FacadeやEloquentを使えるとは言っているんですけど、初期状態では使えないです。これはコメントアウトされているだけなので、素直に外せば使えます。

resources系の下にほとんどなにもなくて、webpack.mixとか入ってなかったり。API開発メインにしろってことかな、と思っています。

Lumen5.2からは、セッションがサポート外になっています。API開発メインだからかなと思ってはいるんですけど、これも一応独自で定義してあげれば使えます。(これらが)実際にLumenを使ってみて感じたところです。

MangaWithのアーキテクチャ設計について

MangaWithのアーキテクチャ設計について。開発当初の6月に、ビジネスロジックはできるだけフレームワークに依存しないように作っていこうと決めました。わかりやすくいうと、Eloquentモデルを使わずにQueryBuilderを利用したり、というところで開発をしていこうと進めました。

いろんなアーキテクチャを調べて、オニオンアーキテクチャが良さそうということにたどり着きました。

これも最初知らなかったんですけど、(スライドを指して)すごく簡単な概念図がこちらです。玉ねぎを半分に切った断面図みたいだからオニオンアーキテクチャなのかなと勝手に思ってます。

一番外側にインフラ層があって、Controller、Service、Repository、Entityと、外側から内側に依存関係を表すという図になっています。(スライドを指して)平らにしたものが右の図ですね。MangaWithのビジネスロジックは、このオニオンアーキテクチャをもとに開発されています。

コードとともに説明をすると、Controllerは最初にInfrastructureであるappdb connectionでインフラを定義して、それをServiceに渡して、Serviceがgetをするというふうになっています。

Serviceが受け取ったインフラをRepositoryに渡して、基本的にビジネスロジックはServiceに集約。取ったデータをゴリゴリ加工するのは、この中で閉じ込めます。

受け取ったconnnectionをRepositoryはそのまま所持して、ここでEntityを定義して、ここにdbから実際に取得するところをここに書いて、Entityを返すというかたちになっています。

こういったかたちでビジネスロジックは閉じ込め、フレームワークには徹底的に依存しない。閉じ込めて作るということを決めました。

フレームワーク選定と実装における事件

アーキテクチャ設計が決まったので、フレームワークはどうしようかというところです。最初はやっぱり、LaravelとLumenのどっちを使おうかという話になりました。

僕たちのアーキテクチャ設計ビジネスロジックは、できるだけフレームワークに依存しないことをテーマにしていたので、どうせ基本的にデータ受け取って返すだけだろう、インアウトぐらいしか使わないだろうということで、軽いしLumenでいいやとなりました。

Lumenに決めて、6月から実装していきました。7、8、9、10月は基本的に順調に開発が進んで、12月のリリースに向けてがんばっていたんですが、11月に事件が起きます。

実はここまで、検品環境などで、簡単なものは作っていたんですけど、ステージングというほぼ本番同等のものを作っていなかったんですね。1ヶ月前だし、ステージングを作っていよいよテストしていくことになったところで、事件が起きます。Lumenでマスタースレーブ構成が実現できないという壁にぶち当たったんですね。

どういうことかというと、MangaWithには独自のクラスが多々あります。

例えば日付操作のCarbon、それからDB Connectionや、LogやSftpなどを独自で作ってます。ただ、「独自」って言ってるんですけど、継承して空で定義にして使ってるっていう、現状なんの意味もない独自クラスを多々作っています。

本当にこれをそのまま、本番で使っている感じです。

DB ConnectionをどうLumenで独自で使っているかというと、(スライドを指して)これはAppServiceProviderなんですけど、extendのところで上書きというか、自分たちの作ったConnectionを使うようにしています。こうした独自のDB Connectionを使っていたせいで、マスタースレーブ構成を取るときに、マスタースレーブも自分たちでやらなきゃいけなくなりました。

LaravelやLumenって基本的に、database.phpにread/writeをhostで定義してあげれば、裏側で自動的にマスタースレーブ構成を取ってくれるので、自分たちで書かなくてもいいんです。

Lumenでこれをやってしまうと、これはLumenのapplication.phpというところに書かれているんですが、デフォルトでdatabase service providerというものがここで生まれてしまいます。

この結果何が起きるかというと、マスタースレーブ構成を取ると自分たちでDB Connectionを上書きしているんですけど、それが再上書きされるというより、上書きしてるのに使ってない、という感じになる。

database service providerの中でデフォルトのDB Connectionが定義されているせいで、そっちが強制的に使われるので、マスタースレーブ構成取れないじゃんということに、11月に本番相当の環境を作っていて気づきました。

回避方法はいろいろあって、まず、そもそも独自DB Connectionをやめればいいという意見。それから、独自マスタースレーブ構成を自分たちで作ってみればいいんじゃないかという意見。

そして、LaravelならLaravelのdatabase service providersを外から注入できるから、そっちに移行すればいいんじゃないか、という3つの意見が出てきて、エンジニアでどうしようかと話しあいました。

Laravelへの移行を決定

結果、今回は、Laravelに移行(するという方法)を取りました。独自DB Connectionはビジネスロジックに直結しているので、今さら変える? ということと、独自マスタースレーブを自分たちで作る(のはハードルが高い)ということと、Lumenを使ってはみたけど「Laravelに移行したいよね」って空気が流れていたので(笑)。

(会場笑)

移行を決めました。実際の移行のサイクルは、簡単に3つです。

今回やったのは、まず、create-laravel-projectみたいな感じで、空のLaravelのプロジェクトを作りました。

今回、ビジネスロジックがフレームワークにほとんど依存していないので、ビジネスロジックを丸ごとコピーして、Laravelプロジェクトにペーストして、Controllerとつなぎ合わせました。

ミドルウェアの実行とControllerのviewのタイミングがLumenとLaravelで逆というところと、ルーティングの書き方がちょっと違うので、そこらへんをゴリゴリ修正していました。実際かかった時間は、3、4時間くらいでした。

実際移行してみて(思ったこと)です。

だいたい200、300ファイルくらい出てきて、レビューするのも多っ! と思ったんですけど、テストの書き方が、ちょっと関数の並びが変わったり、ルーティングも少し違うだけなので基本的に全置換だったんですが、量が多くて辛かった。

ただ、よかった点が、Unitテストが98パーセントくらい書いてました。ここ、すごくしんどいんですが、逆にすごく役立った。テストも一応そのまま、基本的にはコピーして動いているので、LaravelでもちゃんとControllerもUnitテスト全部動いててよかったというところは、わりとテストの良さを感じました。辛かったけど。

移行のその他もろもろ理由があります。

Lumen5.2はセッションが使えないんですね。自分たちで定義すれば別にできるので、独自に書いてはいるんですけど。

フレームワークがちゃんと用意されている機能、サポート外しているところを無理やり復活させているので、ちょっと怖くて、変えたかったというところがあった。

あと、テストは返却されるHTMLの中身が見れない。たぶんそこらへんもAPI開発用なのかなと思うんですが、そこも変えたいという話はみんなにありました。

実際に移行してから、速度としては、実際の開発中にLumenのリクエストの速度とかを測ってなかったのでわからないですが、Unitテストは遅くなりました。

たぶんLumenが起動をすごく最適化していて、例えばデフォルトでコメントアウトされていたり、Configがなかったりで、Lumenは起動がめちゃくちゃ速いので、そこらへんが遅くなったのは感じました。

今回、LumenからLaravel(への移行)だったので、移行しやすいところではあったんですけど、ビジネスロジックを固めていたから基本的にはスムーズにいけたのかなということをすごく感じました。

あと2つですね。

独自クラス関連もすでに多々作ってるんですが、YAGNIの法則という、将来使われそうだからと先に作っても(結局は)だいたい使われない、ということをめちゃくちゃ感じました。

今はほとんど独自クラスを作っておらず、メリットも感じてません。Carbonも空のままで、なんかメリットがあるのかなと思いながらやってます。独自DB Connectionも、開発チームは最初3人いたんですが途中で1人辞めてしまって、その人が(独自DB Connectionを)推していたので、残った2人は「この独自DB Connection、なんであるんだろう」って感じ。

さっきのオレオレフレームワークの話を聞いて、同じなんですけど、ぜんぜんメリットないよねってことを感じています。未来に活きるのかもしれないですが。

あと、LumenとLaravel、2つのWebサービスを使ってみて思ったことは、Lumenのホームページにも書いてあることなんですが、セッションをそもそも外されたり、APIサーバーや単一の機能ならLumen(を使うべき)じゃないかと感じました。普通に作るならLaravelのほうがいいとは思った。

と思ったんですが、エンジニアの中で振り返りをしていたときに、(今回は)たまたま独自DB Connectionを使っていたせいでマスタースレーブが使えなかったから移行したけど、もし独自DB ConnectionじゃなかったらそのままLumenでやってたかな、という話をしました。

セッションだけはちょっと不安ですが、たぶん使っていたと思います。基本的に、Laravelの機能、Eloquentとかは使わずにビジネスロジックを固めていたりはするので。そんなにユーザーへの出し分けとフレームワークになっているので、そんなに実は……このくらい不便(なのは)、セッション(のところ)くらいかなっていう。

あとテストの機能がちょっと少ないですが、それを取っても、実際使っていたんじゃないかという思いはあります。そこまでは普通に順調に使っていたので、Lumenでやってみてもいいかなと実は思っています。

これからについて

これはちょっと未来の話ですが、ビジネスロジックがほぼフレームワークに依存していないので、Webサービスとしてviewを出すときにLaravelを使って、APIはLumenで(作ることができるかもしれない)。

ビジネスロジックがまた共通かわからないんですけど依存させずに持っていて、出し分けでフレームワークを選べたり。今後できていけるのかなとは、今回2つ使ってみてちょっと感じたりもしました。

緊張しててめっちゃ早口なのですごく早いんですけど(笑)。まとめです。Lumenでも、なんだかんだWebサービスの開発はいけるなと感じました。あと、独自クラスだいたい使われない問題で、YAGNIの法則を身をもって実感してしまった。

最初にアーキテクチャを設計したあと、辛いってずっと言いながらテストをいっぱい書いてたので、書きすぎ問題でテストに十数分かかってるときもあったんですけど、それに助けられたことをこっちはちゃんと実感します。

あと、今回この資料を書きながらエンジニアと話してて思ったことが、フレームワークがユーザーとサーバーの疎通の間に入っているおかげで、インアウトのビジネスロジックに注力できるなと思いました。

フレームワークに依存させずにこの中から作ってるのは、本当に自分たちの好きなふうに、僕たちの力でビジネスロジックが書ききれてよかったと感じています。ご清聴ありがとうございました。

(会場拍手)

トランザクションをどう扱っているか

司会者:ありがとうございました。少々お時間が余っているので、なにかご質問などございましたら手を挙げていただければ、マイクをお渡しにいきます。なんでも大丈夫ですよ。

(会場挙手)

質問者1:ありがとうございました。細かい話ですが、トランザクションをどう扱ってどうテストしてるのかなと。Serviceで制御してその分Serviceではトランザクションが……。どういうふうにテストしてどう実装しているのかが気になりました。

田口:トランザクションは、実装としては、例えば1個のServiceの中でインサートが2つあってそれをトランザクションしてるようなときは、厳密にそれぞれ両方失敗させてロールバックとかまではやってなくて。2つあった場合は、片方だけ失敗するトランザクションはロールバックできてるよねというようなところまでしか、書いてはないです。

質問者1:この場合って、実データベースを使ってテストしているのか、モックを使っているかってどっちですか?

田口:ここはモックです。今は、失敗させて、ちゃんと通るようにっていうところまででやってます。

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

司会者:そのほか、大丈夫ですか? それでは、これにてセッションを終了させていただきます。

田口さん、ありがとうございました。

(会場拍手)