TL;DR
- 8年近く稼働していたPHPのシステムをGoでリプレースしようと試みた経緯と経過報告。
- Goを書きたいエンジニアは絶賛募集中だよ
自己紹介
- twitter: Go里🚴♂️
- 2015/10からHowtelevisionにジョイン
- Go/PHP/JS/EngineeringManager
開発合宿に自転車で行く図
副業
- 2018/11からhey社でも副業を始めGoでのWebアプリケーション開発を手伝い始めた
- [FYI]ハウテレビジョン開発者ブログ
ハウテレビジョン
- 2010年創業
- 2019年、マザーズ上場
- 新卒向けの「外資就活ドットコム」と、中途向けの「Liiga」というキャリア支援サービスをやってる会社
- 社員40名、総従業員数は100名超
- インターン生がめちゃくちゃ働いてる変な会社
技術顧問: Naoya (2015年〜)
外資就活ドットコム
- 2010年にローンチされた就活サービス
- 東大・京大・早慶など、いわゆる上位校の就活生のシェアがめちゃくちゃ高い
- 「就活してる東大生の80%以上が登録してるサービス」
- 「ユーザーファースト」を掲げてサービスづくり・運営を行っており、広告に頼らずほぼ口コミとSEOだけで会員を増やし続けてる
やっと本題
Goを導入するに至った経緯
技術的な歴史/創業前後(〜2010年)
- 社長(非エンジニア)が書いたWordPress製のブログ
- 就活業界のブラックボックスをオープンにした記事の数々がバズりまくる
- もともとはお役立ち動画で起業したハウテレビジョンだったがすぐにピボットし外資就活を事業化することに
技術的な歴史(2010年)
- 事業化にともない会員登録/ログイン機能などを実装
- WordPressにCakePHP1.x系を増築することに
技術的な歴史(2011年)
- CakePHPの2.0がリリースされ、新機能は2.0系で書いていくことに
- WordPressに加え、CakePHPの1系と2系が混在しているカオスな状態
技術的な歴史(2014年)
- 学生向け(toC)のWebアプリの他に、企業向け(toB)や社内向けの管理画面を、別のWebアプリケーションとして開発
- 1つのDBにつながるCakePHPのアプリケーションが3つに増える()
- 複数のWebアプリケーション間で共通化したい処理を提供する内部向けWebAPIを開発
- 1つのDBにつながるCakePHPのアプリケーションが4つに増える()
技術的な歴史(2015年)
- iOSアプリをSwiftで実装しリリース
- AndroidアプリをJavaで実装しリリース
- スマホアプリのbackendとなる外部向けWebAPIを開発
- 1つのDBにつながるCakePHPのアプリケーションが5つに増える()
- さくらのVPSからAWSへの完全移行計画が成る
技術的な歴史(2016年)
- Cake1系の処理をCake2系に完全移植
- アプリケーション分割問題解消のための議論が始まる
- CakePHP3系でのBigRewriteプロジェクトが始まる
- 当時退職ラッシュもあり、4ヶ月ほどであえなく頓挫
THE・カオス
2017年当時、解決したかった課題①
アプリケーション分割問題
- 他のCakeアプリのModelをコピペして来てカスタマイズ
- 似たようなビジネスロジックがあちこちのリポジトリに書かれている
- 仕様変更・追加のたびに修正漏れが発生しやすい
2017年当時、解決したかった課題②
PHP型ふわふわ問題
- PHPは型のような何かなので、常に厳密な比較をしないと安心できない
- 使っていないコードの判別、削除が難しい
- ゴミコードが増えていくことで、さらにゴミの削除に立ち向かえなくなる
2017年当時、解決したかった課題③
テストコードほとんど無い問題
- ほとんどない
- あってもメンテされず腐っていることが多い
2017年当時、解決したかった課題④
DBがカオス問題
- 使ってないと思われるテーブル、カラムがたくさんある
- コメントが書かれておらず役割や値の意味がわからないこともよくある
- ていうか外部キーが全く貼ってないのでデータ汚染が起きてる部分も
2017年当時、解決したかった課題⑤
モチベーション問題
- 古いバージョンのPHPからの脱却も簡単にはいかず辟易…。
- 「え、サポート切れが近い…?ダイジョウブ、ダイジョウブ(ダメ)」
- 今後優秀なエンジニアをたくさん採用していく上で、PHP(5.x系)書きたい人がいるだろうか…?
機運
2017年夏、スマホアプリ大規模リニューアルの号令
- アプリユーザーのアクセスが増加しWeb版を上回る
- アプリユーザーの方がアクティブ率が4倍も高いことが判明
- iOS/Androidエンジニアが長らく不在でUIの改善が止まっており、ユーザーの不満も爆発
最優先の事業課題は、最高のスマホアプリを作ること!!
ネイティブ側
- iOS/Androidの専門家を継続的にチームに囲うことが難しい
- Webエンジニアでも気軽にネイティブの実装ができるようにしたい
- Webのフロントエンドもカオスなので整備していきたい
- Reactかな?
ネイティブ側は ReactNative で行くぞ
...バックエンド、どうする?
大方針
- backendの処理を1つのAPIに集約し、DBに繋がるアプリケーションを減らす
- 静的型付けのコンパイル言語で長期的にメンテしやすくする
- 将来性、採用優位性の高い技術選択をする
技術選定の経緯
- Java, Scala, Kotlin, PHP7, Goなどで簡単なWebAPIの実装してみる
- 書き味やライブラリの充実度、世間的なノウハウなどを考慮した結果…
Goで行くぞ
どうやっていったか?
Goで行くとは言ったものの…
- 社内にGoを業務で書いたことある人がいない
- 当時エンジニアが減っていたこともあり、普段の業務で手一杯…
そうだ、開発合宿に行こう
2泊3日の開発合宿でやったこと
- フレームワーク、ライブラリの選定
- gin, gorm, dep...
- 中長期に耐えられそうなアーキテクチャ(v0.1)をとりあえず決める
- ためしに何本かエンドポイントを実装してみる
- フロントエンドと並行して作業を進めるためにAPI Docやmockを提供する仕組みづくり
- API Blueprint
難しかったこと
- HTTPのハンドリングをするgin、ORMのgormなど、ライブラリを使った上でどういうパッケージ構成にするかは自分たちで決めていく必要があった
- MVC? クリーンアーキテクチャ?
- CakePHP出身だと「設定より規約」に慣れすぎており、自分でアーキテクチャのルールを決めていくのが大変
- フルスタックなGo製のWAFもあったけど、RESTのWebAPI作りたいだけなのでtoo muchに感じた
- 2人くらいで議論してアーキテクチャを決めていく。不安感でいっぱい。
開発合宿で作ったアーキテクチャ
それでもやるっきゃない
後のタイムライン
- 2017/11: 開発合宿
- 2017/12: 業務委託の採用頑張る
- 2018/01: バックエンドエンジニア1名ジョイン
- 2018/02: バックエンド&インフラエンジニア1名ジョイン
- 2018/03: バックエンド&インフラエンジニア1名ジョイン
- 2018/04: 4月末に新アプリリリース(予定)
開発が本格化すると…
アーキテクチャについて
- 新しく入ったメンバーが実際に開発を始めPullRequestを上げてみると、まだまだ議論が足りてない設計上の課題がポロポロ出てきた。
- トランザクションはどこのパッケージがやる?
- DBコネクションをmainから引き回すのはしんどい
- domain層はDBの具体的な情報を知らないピュアなビジネスロジックにすべき
- UTとかFixtureとかどうする?
進捗が上がらず追い詰められる
- アーキテクチャが固まっておらず、実装時の迷いが多いので開発スピードが出ない
- リリースまであと2ヶ月で、未実装のエンドポイントが30~40本
- 原因不明の腹痛にさいなまれる
そうだ、モブプロしよう
モブプロやってみた
- 5人くらいで集まり、ほぼ1日中1つの画面を見ながら実装を進める
- 1つのエンドポイントの実装を進めつつ、設計の議論をしてルール作りをしていった
結果
- 2週間モブプロして1つのエンドポイントしか出来ていなかったが、後の開発効率が爆速になった
- モブプロの中で徹底的に議論したことで、アーキテクチャのルールについて皆が十二分に理解できている状態になった
- それにより、実装もレビューもめちゃくちゃスムーズに進むようになった
- 何とか大幅な納期の遅れなくリリースを迎えることができた
最終的にはこんな感じ
main.go
- MySQLやRedisなどのコネクションを取得し、DIをコールする
- ルーティング
- どのRequest(URL)をどのControllerのどのActionに繋ぐかを定義
DI層
- アーキテクチャ全体のごちゃごちゃした部分を押し込める場所
- MySQL、RedisなどのコネクションなどをもとにInfraのインスタンス(実体)を取得
- InfraのインスタンスをUsecase、Controllerに注入したものをmain.goに返す
Controller層
- HTTPのRequestとResponseをハンドリングが責務
- クエリパラメータやリクエストボディの取得
- JSONで返すか、Imageで返すか
- どのステータスコードを返すか
Usecase層
- 主にビジネスロジックを書く場所
- Controllerから受け取ったデータを元に様々な処理を行う
- 入力値のバリデーション
- Domain(を実装したInfra)に処理を命令しデータの取得、操作、通知の作成など
- 返却値の加工、整形
- Contollerに適切なエラーを返す
- HTTPリクエスト以外のトリガーで起動されることも想定している
- HTTP APIとバッチ処理とでビジネスロジックを共有できる
Domain層
- サービスの根幹となるリソースの構造や振る舞いをInterfaceとして定義する場所
- ex) 「掲示板のコメント」は、「ID」「本文」「投稿者のニックネーム」「イイね数」などのデータを持っている
- ex) 「掲示板のコメント」に対しては、「一覧取得」「詳細取得」「投稿」「イイねする」などの操作ができる
Infra層
- Domain層で定義されたInterfaceを具象化している場所
- ex) 「掲示板のコメント」に対する「一覧取得」という操作が、具体的にどういうテーブルにどういうSQLを投げるか?が記述されている
その後の歩み
技術的な歴史(2018年)
- 七転八倒しながらアプリのリニューアルをリリース(iOS版のみ)
- backendは完全にGoのみ
- Webの方で大きく手を加える機能を徐々にReact/Goで書き換えていく
技術的な歴史(2019年)
- Web版のGo/React化をさらに進める
- Android版アプリもReactNativeでリニューアル完了
- PHPのAPIも取り潰せるところが増えてきた
PHPからGoに移行して良かったこと
- 大胆なリファクタがしやすくなった
- 最悪ビルド通れば一安心
- GoLand(JetBrains製のIDE)などで開発すると静的型付けの恩恵をめちゃくちゃ享受できる
- コードをどこまででも追える
- テストコードを書きやすい設計にしたことでカバレッジをある程度キープできている
- DBの具体的な情報をInfra層に閉じ込めたので、DBのリファクタがしやすい
- エンジニア採用においてもGoが強力なフックになっていると実感する
イマイチなところ
- レイヤごとに責務がキッチリ分かれているため、データのバケツリレーを頻繁にやる必要がある
- Goの問題というわけじゃない
最近どんな感じか?
- あくまでビジネスインパクトの大きな開発が発生したタイミングでGo/Reactの新技術に置き換えていくスタイル
- 仕様そのままで技術的に置き換えるだけ、ということはしない
- Web側、特に企業向けや社内向けの管理画面などはPHPのまま
- アーキテクチャに関して、細かいところでルールが統一できてないところもまだある
- Controller層とUsecase層の境目がボヤけてるところがある、…など
- DBに繋ぐUTでフィクスチャを共有してるのがツラい、など、開発上の課題も色々ある
Go小ネタ集
ライブリロード
- Goのコードの変更を即座に検知しReBuildしてくれるライブラリを開発環境で仕込んでる
- github.com/codegangsta/gin
- 大体2~3秒で自動ビルドが完了するので、コンパイル言語を使っているが故のストレスはほとんど無い
独自のErrorパッケージ "karma"
- Goではあらゆる層でerrorを返すが、そのerrorの種類によって処理を分岐させることがあり、すべての層から参照できるerrorパッケージが欲しくなった
- 独自にerror型を定義することになるが、Go標準のerrorsパッケージと識別するために karma と名付けた
- カルマ = 業 = go
go routine
- Goの代名詞の並列化を使う機会があったのでご紹介
- とある機能で、ユーザー一覧を返す時にそのユーザーに紐づくTwitterアカウントの情報も返すという要件
- ユーザーの数だけTwitterAPIを叩くことになるので並列化している
- PHPだったらかなり苦労していたであろう要件だった
mock
- UTを書く中でDomain層のメソッドをmockにしたくなり、mockgenというtoolを使い始めた
- interfaceが記述されている.goファイルから、自動でそのinterfaceの実装を満たす*_mock.goを作ってくれる
- *_mock.goファイルもgit管理していたが、interfaceに変更を加えるたびにmockファイルも修正しないといけないのが面倒になったので、最近git管理をやめてCIでテスト実行時にmockファイルを自動生成するように変更した
richgo
- テストの実行結果がいまいち見づらいので導入
-
go test
の代わりにrichgo test
とするだけで色付きでテスト結果が見れる
まだまだ技術的にやりたいことがいっぱい
- 既存のバッチ処理をGoに移行する
- 既存のJobQueueシステムをGoに移行する
- 学生向け(toC)だけでなく、企業向け(toB)のアプリケーションもGoに移行する
- APIのE2EテストをCIに導入したい