大規模サービスを運営している会社が新規プロダクトを開発した時の物語~サーバーサイド編~
目次
- プロローグ
- 基盤スクラッチ開発の選択
- 基盤開発スタート
- 基盤開発終盤
- 機能開発スタート
- 開発完了〜リリース、そして振り返り〜
- あとがき
プロローグ
日本一のオンラインデーティングサービスを運営する弊社。
2019年4月、桜の開花とともに新規サービスの概ねの仕様が固まり、いざ開発が始まった。
APIの開発はJimという私の同僚が1人で行う予定であった。
ある日、のんきに帰宅しようとしていると、Jimから呼び出された。
私(山下)
Jim (James Kirk)
私
「やあ、Jimどうしたんだい?」
Jim
「暇かい?暇だよね?山下さん。」
「実は、新規サービスの仕様が概ね決まったんだけど、既存サービスとはだいぶ仕様が異なっているんだ。」
「もともと既存サービスを元にして、少しシステムを拡張すれば良さそうだったんだけどそうもいかなそうなんだよね…」
「とりあえず手伝ってくれよ。暇そうだし。」
私
「OK!(暇じゃないけど…w)」
こうして2人で仕様を見つめながら開発に取り掛かったのであった。
基盤スクラッチ開発の選択
Jim
「 んーやっぱり既存サービスをベースにするのは少し無理があるな…
(スクラッチで開発したいな..)」
私
「たしかに難しそうだね。仕様が決まって当初の想定とはだいぶ異なっているのであれば、もういちど開発方針を練り直した方が良さそうだね。リポジトリを分けて基盤開発は新しくやった方がいいかもしれないし!」
Jim
「そうそう。
(よくわかっているじゃないか。さっさと上を説得してきてくれ。)」
私
「とりあえずそれぞれのメリット・デメリットをまとめてみよう。」
既存サービスの基盤を使用する場合
【メリット】
- 既存のリソースを活用できる(新規に開発する部分が少ない?)
【デメリット】
- 既存のコードに引きずられて開発が逆に遅くなる可能性がある
- コミットが混ざるので既存サービスの運用が複雑になる(修正部分の確認など)
新規に基盤開発を行う場合
【メリット】
- ビルドが早くなる
- 技術的負債を引き継がなくてよくなる
- キャッチアップコストがなくなる
- 既存サービスで得られた知見を活かして、最適な設計ができる
- 楽しい
- 既存影響を考えなくて良い
【デメリット】
- 既存のリソースを有効活用できないかも
- 工数が増える
簡単に整理をしてみると一目瞭然である。(スクラッチ開発の方がメリットが多い。というか気持ちが完全にそっちに行っているw)
懸念点はやはり工数の問題。
これはすぐに基盤開発部分の正確な工数差分をだして、
その後の機能開発の工数およびリリースしてからの運用コストを減らせるということを明らかにした。
みんなすぐに納得して、新サービス用のリポジトリが爆誕したのであった。
私
「じゃあ、ちょっと私は別の仕事をしてくるから、基盤開発を進めててね!」
Jim
「OK!
(よしよし、これで好き勝手できるぜ!)」
基盤開発スタート
私
「やあJim、調子はどうだい?」
Jim
「いい感じだよ!」
「(そろそろ本腰入れて手伝ってくれないかな?コードを書いてほしいいんだよ?)」
「ちょっと進捗を共有するね!」
弊社のAPIはプログラム言語に「Go」を採用している。
Jim
「まずフレームワークは利用しないことにしたよ。」
「既存サービスではGinを利用しているけど、routerとmiddlewareくらいしか恩恵を受けれていないよね。」
「chiっていう軽量なルーティングライブラリがあって、middlewareもちゃんとサポートされているからこれを使うよ。」
「アーキテクチャはこんな感じ!」
「ドメイン層をベースとして、ビジネスロジックを独立させる。」
「Interfaceを定義して依存性を注入することで、疎結合なシステムにしていくよ!」
私
「おおー。これはテストも書きやすそうだし、シンプルでとても良いね!」
Jim
「ふふふ。では、山下さん。そろそろ手を動かしてもらうよ!」
基本的にライブラリの選定は、複数の候補をあげて下記のような表を作って判断していくことにした。
サンプルの通り、「chi」を採用したのも他ライブラリとの比較表を作って、Jimがプレゼンをしてたので、自信を持って選択をすることができたのである。
他、熾烈なメリデメ競争を勝ち上がり選定されたものも紹介
CLIライブラリ:cobra
圧倒的な人気。dockerでも採用されている安定感。用途はバッチタスクなど。
ORM:Xorm
GORMと激しい一騎打ちを繰り広げたが、既存サービスで利用している「Xormで特に困っていないよね」という一言が決め手。安全策をとったのである。
Elasticsearch Client:elastic
Memory Cache:eurekache
Redis Client:redis
このあたりも既存サービスで利用中のもので特に困ったことはなかったので、そのまま利用することとした。(全然熾烈じゃない..w)
ただそれこそ、インフラ層のコードはそのまま既存サービスの流用ができる構成にしたので特に問題もなかった。
補足情報
エディタ:GoLand, VSCode
ちなみに私はVSCodeです。
新サービスの立ち上げ開発を機にVimから移行しましたが、
プラグインも充実していて、これが無料ということに驚愕しました。
基盤開発終盤
私
「んー。エラー機構をちゃんと整備しないとな…」
Jim
「それならいいのがあるよ!xerrors!」
「xerrorsは一部のメソッドがgo1.13から正式に組み込まれることが決まっていて、後から追従しやすいように今からこれを使っておいた方がいいよ!」
私
「OK!」
〜ごにょごにょ〜
私
「 Jim先生。独自のエラーパッケージを作成して、errorをラップできるようにしたよ!xerrorsのおかげで、ちゃんとエラー発生箇所を追えるし、エラーログもいろんなところで出すんじゃなくて、レスポンスを出力する直前の一箇所だけで無駄がないようなすっきりした感じにできたよ!」
「さらにユーザーへ見せるためのエラーメッセージもちゃんと管理できるようにして、エラーLevelやステータスコードも一緒に管理できるようにしてしまった(やりすぎたかもw)。ついでにエラー定義をコードみなくてもわかりやすいように自動でドキュメント生成もしてくれるよ!(多分誰も見ていない..)」
Jim
「OKOK」
「開発環境も整えておいたよ!」
・パッケージ管理:modules
ちょうどglideやdepからmodulesへの移行令がでてきたくらいだった
・ローカル開発環境構築:docker
全社的に利用しているので、誰でもすぐにdocker-composeで新サービスの開発に入れる。使わない理由がない。awsの各種サービスもローカル用のimageがあるのですべてdockerで管理している。
「必要なインフラ層の実装もだいぶ終わったよ!」
私
「さすが!」
「もし、翻訳対応が必要とかなったときのために、翻訳用のpackageを用意しといたよ!」
「文字列はそのpackageの関数をかますようにしてね!
(実際に必要になったら text を利用する想定)」
Jim
「基盤開発は一通りできたね。次は機能開発をしていくよ!」
機能開発スタート
ここで我々は非常に重要なことに気づく。
リリース日である。
リリース日は早々に決まっていて、なんと7月末。
テストの期間も含めるとほとんど時間がない。
ここで我々はさらに2名開発者を増やす決断をした。
基盤部分をスクラッチでシンプルに作成しなおしたおかげで、機能開発は爆速で進んでいったのである。
Jim
「ドキュメンテーションはswaggerとか使って自動生成したかったけど、ちょうどPOSTMANを全社的にドキュメンテーションツールとして使おうとしていたので、これにしっかり落とし込んで行こう!」
一同
「おー!」
このあたりで下記を追加
Code自動生成:go-bindata, mockgen
go-bindataも既存サービスで使っていて、静的ファイルの読み込みは特にこれで困らない。mockgenはinterfaceの追加が活発になってきてmockの更新が追いつかなくなりお試しでいれてみた。
CI:circle ci
テストの自動実行。カバレッジとかまではとれていないが、ビルドのチェックも怠りません(しょぼい..)
インフラ構築:AWS
既存サービスが完全にコード化されていたので、そこからの流用で爆速で用意が完了。S3やSQS・SESもフル活用です。
開発完了〜リリース、そして振り返り〜
私
「ふう。なんとかリリースにこぎつけたね。」
Jim
「うんうん。本当によかった。キリもいいので悪かったところを反省してみよう!」
反省点
・ダミーデータの作成
機能開発を複数人で並行して行なったので、
ダミーデータに統一感がなくて、機能ごと依存関係のあるようなテストが非常にしづらい。
せめてダミーデータを機能のファネルごとに用意できるとか精巧に作り込むことができれば、もっと開発効率があがったと思う。
・データベース設計&スキーマ管理
開発中に仕様が変わるというのがざらにあったのでDBの設計で少し残念なところがある。
仕様が変わるのは仕方がないので、スキーマ管理をもっと柔軟にできていれば良かったなあと思う。
なんか良いツールないかしら?
・wiring(DIツールの導入?)
開発途中でwireとかDIツールが結構流行ってきていて導入を検討したが、
結果まだまだ我々くらいの規模であれば導入するほどでもないかなという印象。
main.goでのDIは圧巻である。(反省点ではない)
・コードジェネレーション
mockのジェネレートとかは後から入れてみたりはしてみましたが、
もっと最初の設計から最適化させてできたところはなかったかなあとちゃんと考えてみたい気分ではある(特にない気もする)
・const管理
基本的にconstは各ドメインに定義しています。
それはそれで良い気がするんですが、複数のドメインで共通して利用したいconstとかがでてきて結構カオスになった。
いっときconst用の専用packageを作成しようという案もでたりしましたが、特にこれが正解だというものにたどり着けていません。
・環境変数の管理
Configurationのライブラリとして、 viper を使おうとしたが、高機能すぎてドキュメントを読み切る前に愚直にos packageで読み込むやつがでてきたので、そのまま素の実装を貫いています。あとからでも導入できるよね!と開き直っていますが辛いところではあります。
私
「たくさん反省点でたね。笑」
Jim
「そうだね。でもリリースまもないし、やることはいっぱいあるので立ち止まっていられないよ!」
「ただ一つ言えることは、スクラッチで開発して良かったね。」
「自分たちで考えて自信をもって開発をしてきたから、責任感もちゃんともてるし、常にもっとより良いものをということを意識できたんだなと思うよ!」
私
「良いこと言うね!この経験ができたことは本当に良かった!せっかくだしちゃんとブログにまとめてアウトプットするね!」
そして月日は流れ、Advent Calenderに乗っかってようやくブログを書いたのでした~ fin
あとがき
読みやすいようにとJimとの対話式のストーリー調にしてみましたが、読み返してみるとかなりはちゃめちゃな雰囲気ですね。
すみません(笑)
プロダクトの立ち上げという経験を通して改めて思ったのは、やはりサービス開発は非常にやりがいがあることだし、なにより楽しいことですね。
自分たちのつくったサービスが、1人でも多くの方に良い影響を与えられるようになったらと切に願い、これからも精進していきます。
最後までお読みいただきありがとうございました。