STYLY APIの開発体制について
この記事は【STYLY主催】XR Advent Calendar 2022の12月12日分の内容です。
私自身、色々な記事を書いていますが、実はPsychic VR Labでサーバーサイドの責任者をやっています。ここでは、今まであまり多く語ってこなかった、STYLYのサーバーサイド開発の話をしようと思います。
STYLYのサーバーサイドを開発する専業の正社員は私だけです。
STYLYのサーバーサイドの開発はPOが1人、50%稼働の業務委託エンジニアさんが2,3人います。実際にコードを書くのは、2~2.5人/月ぐらいで開発を行っています。
そんな中でSTYLYのAPIのリリースは11月末時点で2022年中に418回行っています。一番多かったのが8月で1ヶ月あたり60回でした。
たぶん、この規模にしては非常に高頻度なリリースを行っている組織だと思います。この開発がどのように行われているのか?過去のアーキテクチャの変遷とともに解説していきたいと思います。
旧アーキテクチャの課題
STYLYではStudioという3D空間を簡単に制作できるWebツールを提供しています。このStudioですが、変則的なつくりをしており、3Dの操作部分はUnity、シーンの公開を制御(Publish)する部分はReactという構成になっています。
そのような技術スタックも相まって、2019年の末頃は以下のような構成でした。
まずApacheが動いており、そこにSlimを使ったPHPのAPIを提供しています。そのApacheに相乗りする形で、静的なWebサーバーが稼働しており、UnityからのWebGLの出力結果と、webpackでbuildされたReactの出力結果をサーブしていました。
この構成はものすごく辛かったです。まず、ビルドプロセスが重いです。基本的なApacheとPHPであれば簡単なものですが、それに加えて、ReactとWebpackを動かす必要があったので、ビルド時にNode.jsも動いていました。また、UnityのWebGL出力というのは非常に容量が大きく、100MB越えすることも珍しくありません。そのため、私が現代的なアーキテクチャにするために一度Dockerイメージ化をしましたが、イメージサイズが2GBを超えることがあり、デプロイ自体が2時間程度かかる超重量級でした。
また、テストはSpreadSheetで管理されており、一通りのチェックリストはありました。しかし、一通りのテストを行うにも2時間程度必要となるとても手間のかかるものでした。
新開発体制
新アーキテクチャ
旧アーキテクチャのデプロイにおいて問題となったのは、
- Unityの出力結果の容量が大きい
- React(Node.js+Webpack)のビルドプロセスが重い
という2点でした。そのため、これらのサーバーを分割しました。私がS3+CloudFrontをセットアップし、UnityからのWebGL出力結果をS3の方へ配置して動作するように、Unityエンジニアの方に調整をお願いしました。また、フロントエンド側もnetlifyの方へ移行するようにお願いしました。
これでビルドプロセスの複雑性をなくし、Dockerイメージの容量を削減しました。これで純粋なREST APIとして開発出来るようになりました。
テストアーキテクチャへの技術投資
私がPsychic VR Labへ入社したとき、サーバーサイドには少しだけ自動テストがありました。しかし、継続的にCIで動いているわけではなく、壊れていても放置されていました。したがって、最初は、CI環境の構築が急務でした。そのため、ある時期は「網羅性の高いREST APIの自動テストを冪等に安定的に高速に動かす」ということに心血を注いでいました。例えば、「データベースのリセットはどうすべきか」であったり、「S3など外部のサービスを利用するものはどうすべきか」「そもそもE2Eテストとは何か」「DooD、DinDとしてテスト環境と作るべきか」などかなり試行錯誤しました。このあたりの「REST APIのテストの冪等性の担保」のノウハウをまとめたものが、2021年に執筆した「REST APIのための自動テストの実践 アジリティのためのテスト・アーキテクチャ」という本だったりします。たぶんテスト自体のアーキテクチャは5回ぐらい基盤構成を変えました。もちろんこの時期から、機能を開発するごとに自動テストを作ることを必須としました。
現在、このテストのアーキテクチャに関しては、テスト環境、開発環境を一括のDockerComposeで管理してしまい、CI上でも同様に動かすことで、デバッグしやすい、冪等性の担保されたテスト環境を構築するのが自分の中ではベストプラクティスとなりました。現在、テスト環境では17台ものコンテナが同時に稼働していますが、CI上のテスト環境構築とテストの実施がGithubActions上で20分以内で完了できるようになっています。
たぶん普通のAPI開発の現場だとありえない話として、「aptのリポジトリが遅く、不安定である」みたいな話もあります。例えば、Ubuntuでaptでソフトウェアをインストールしていて、「aptのリポジトリサーバーが不安定でソフトのインストールに失敗した」という経験は少ないと思います。しかし、STYLYのサーバーサイド開発では、1リリースにつき、平均で3回ほど全体のテストが実行されます。そのため、年間にリリースが400回あるということは、テストが1,200回実施され、そのたびに環境構築が行われaptが実行されます。そうすると、「aptのリポジトリが不安定な日に当たる」ことがあり、このような現象が起こるとリリースできなくなります。したがって、「不安定なaptリポジトリを避けつつ、フェイルオーバーしながら高速にソフトウェアをインストールする」という、それ専用のノウハウの構築が必要になったりします。
このあたりのリアーキテクチャやデプロイ環境の整備で、リリースの所要時間が120分から10分になりました。旧来は、Unityの出力や、Reactの画面が存在していたため、手動テストに頼らざるを得ない部分がありましたが、アーキテクチャを分割したことにより純粋なREST APIになりました。その結果、テスト容易性が上がったので、全てのテストを自動テスト化することで、所要時間が120分から20分へ削減され、軽量で高速な開発が出来るようになりました。
レビュー省力化
テストやリリースが改善し、業務委託さんに仕事を依頼してくと開発が加速していきます。しかし、内容をレビューするのは基本的に私しかいないので、レビューがボトルネックになって、リリースが円滑に回らなくなってきます。そうすると、レビューを省力化する必要があります。
そのため、レビュー省力化のためサーバーサイド開発では3つソフトウェアを利用しています。
1つはSonarQubeです。これは導入が簡単な割に効果の高いものです。詳細な解析にはテスト結果の連携が必要ですが、基本的には、静的解析なのでGitリポジトリを連携するだけでOKです。
これは適当な個人リポジトリの解析結果ですが、このような形でコードがどの辺が良くないのか(コードの臭い)を解析して示してくれます。
また、その理由や、直し方まで解説してくれるので、割と納得感も強いです。
2つ目はPHP-CS-Fixerです。
端的にはLinterです。PHPの標準的なコーディング規約であるPSR-12に対応しているので、これだけで割と必要十分です。名前の通りfixerなので、cs-fixerで検知できたコーディング規約は、自動でfixできるので、直す側も労力が少なくソースコードのスタイルが統一できます。
3つ目は、PhpMetricsです。
PhpMetricsもPHPに対する静的解析ツールです。先述したSonarQubeとは何が違うか?というと、SonarQubeは「局所的なコードの臭い」や「局所的なバッドプラクティス」に特化していると認識しています。一方でPhpMetricsはもう少し大局観のある解析が可能で、例えば、公式の解析結果を見れば分かるように、クラスの凝集度であるLCOMの解析や、クラスの循環複雑度を可視化することが可能です。
このようにツールを自動で動かすことで、「コードの臭い」や「フォーマット規約違反」などの指摘は極力自動で行っています。これには何個か理由があって、やはり人間疲れて、指摘事項が抜け漏れてしまうので、それを防止するためです。また、コードレビューで指摘をしても、理由が思い出せない。ということもあります。レビューしてて「なんとなく違和感があるコード」というものは、リーダブルコードなどの書籍で理由が説明されていたりするのですが、私自身、全てのコードの臭いの種別とその理由を覚えているわけではありません。しかし、レビュワーから「違和感がある。」だけで修正求められても、コードを書いた人は納得感というものは薄いと思います。そのため、なぜそれが良くないコードなのか?を説明するSonarQubeは割とありがたい存在だったりします。逆にSonarQubeが検知しなかった場合は、まぁこういう流派もあるかなぁと思ってスルーしたりもします。
一方で、レビュー時にSonarQubeで何かコードの臭いを検知した場合は、相応の理由が無い限り直してもらっています。残念ながら私はコードの臭いに関してプロではないですし、コードを書いた本人や私の感性を信じるより、プロが作っているSonarQubeの指摘の方がおそらく精度や合理性は高そうなので、そちらを信じる方針にしています。もちろん、フレームワーク等の関係で、どうしても避けられない場合はスルーすることあります。
したがって、SonarQubeやPHP-CS-Fixerで自動化を進めながら、「クラス設計」や、「仕様通りに出来ているか」というところにフォーカスを当ててコードレビューを実施しています。
テスト駆動の伝道
そのようにレビューを省力化し、開発が進んでいくと、別の部分に問題が発生します。前節で書いたように、些末なソースコードに関する修正・レビューはほぼ自動で行われるようになりました。しかし、ある日、チケット化された内容を業務委託さんに実装をお願いしたところ、私が想定したモジュール構成と、実際に作ってもらったモジュール構成にズレが発生しました。
経験のある方だと分かるかもしれないですが、このモジュール分割のズレが発生すると、直すのも大変ですが、指摘も大変だったりします。実は、前節でPhpMetricsを導入したのは、この理由もありました。私自身、クリーンアーキテクチャは好きではありますが、かといってモジュール設計に絶対的な正解は無いと思っています。したがって、設計には理屈の通っている複数の解が存在し、そこから1つ選ぶ過程は、"好みの問題"に集約してしまう部分が、少なからずあると思っています。PhpMetricsにはLCOMなど、クラス同士の関連度を計算する尺度が計算できるため、ある程度、この部分のスクリーニングが出来ると思いましたが、あまりうまく機能しませんでした。
そのため、私は「なぜ人によってモジュール分割が異なってしまうのか?」ということを考えました。私自身は、プロダクト開発においてはテストファーストなテスト駆動開発を行っています。私には、 「UMLで設計したクラス設計とテスト駆動開発で導いたクラス設計は異なる」という仮説があります。UMLというのは一例に過ぎないのですが、いわゆる机上のコードを書かずに決めるクラス設計と、テスト駆動により、「テストを書く。実装する。リファクタする。」というマントラを繰り返す手法だと、そのクラスの分割の方針が違うように感じています。そのため、業務委託の方にもテスト駆動を学んでもらうハンズオンを開催しました。
これにより委託さんと私の間でモジュール分割の方針がズレないように心がけました。定量的に効果はあったのか?と言われると、なかなか難しい面はあります。テスト駆動前とテスト駆動後のメインブランチへのソースコード量を可視化してみるとわずかに傾きが上がったような気がします。もちろん業務量であったり、プログラムの難易度であったり、交絡要素があるので、本当に効果があったか?とまでは言いにくいです。
しかし、私がどういう思想でどういうプロセスでクラスの責務の分割が進めているのか?を理解していただけたようなので、こちら側のレビューの意図というものを伝えやすくなったように思います。
チーム開発体制の整備
リリース・テスト・レビュー・開発とボトルネックを1つ1つ解消していくと、残っていくのは、仕様面でのボトルネックになってきます。旧来の方式であると、POと私がマンツーマンで機能や仕様を取り決め、私がその内容を業務委託さんに切り出す。ということが多かったです。しかし、一度私が受け取って、業務委託さんに伝言ゲームをする形であると、仕様の中身が曖昧であったり、機能を作る背景については、どうしても詳細の解像度が落ちてしまい、それを説明するコストが無視できない状態になりました。
そのため、業務委託さん、PO含めて1週間で行うことを決めるスプリント計画を行い、バックログにフィーチャーをリストアップし、優先度付けを行うように会議体を組み込みました。フィーチャーをステップごとにタスクとして切り出すには、スキルセットとの勘案が必要だったり、タスクの言語化と、その背景説明に私のコストが割かれていたので、そのコストが軽減することが出来るようになりました。
制約理論とDon’t just do agile. Be agile.の理解
私は2018年頃に「テストを安定で高速に動かし続ければ、高頻度のリリースが可能ではないか?」ということをは仮説として持っていました。そんな中、2019年にPsychic VR Labに転職したため、「自分のテストに関する仮説を確かめたい」という気持ちが強かったです。したがって、入社して手を入れることを考えていたのがCI/CD周りでした。それは「リリース周りが高回転で動かないと、全体の改善が進まない」という直感に任せて改善していました。最近まで知りませんでしたが、リリース回数を指標にするのはDevOps的には良い改善指標のようです。リリース周りの速度が改善していくと、リリースを高速に回すためには、私のコードレビュー速度を改善しなければならない。という問題が発生し、自動レビューツールの導入であったり、テスト駆動の伝道が必要になってきました。そして、最終的には仕様をPOからチーム内で伝達することにレイテンシがかかるようになったため、スプリント計画を始めるようになりました。
私はテスト駆動の愛好家でありますが、テスト駆動を作ったKent Beckは、XP(Extreme Programming)の提唱者でもあります。このXPは制約理論(TOC:Theory of Constraints)というものを参考にしています。この制約理論は、簡単にいうと「ボトルネックを改善せよ」という理論です。そう考えると、私のやってきたことは、「自分達の開発がどのように顧客に価値を届けるのか」を「ステップごとに分け」、「ボトルネックになっているポイントを改善する」という非常にシンプルなものになるんだなぁと改めて思い返していました。
サーバーサイドの開発は、最初はリリースやテストなど1人でも出来る「技術的な困難」が主たる原因でした。しかし、時が進むにつれ、「テスト駆動の伝道」であったり、「スプリント計画」であったり、「コミュニケーションの困難」にボトルネックが移動したな。という実感がありました。これは、 アジャイルソフトウェア宣言にある、「プロセスやツールよりも個人と対話を」という言葉の意味なのではないか。アジャイルをする。のではなく、アジャイルになる。(Don’t just do agile. Be agile.)という意味なのではないか。 と思うようになりました。技術的問題を解決していくにつれ、究極的には人と人とのコミュニケーションの問題に帰着する。という割と使い古されて何度も繰り返されてきた内容ではあると思うのですが、自分の実感を持って改めて学んだように思いました。そして、あまり議論されませんが、 「アジャイルソフトウェア開発宣言の背後にある12の原則」というものもあります。これには、「技術的卓越性と優れた設計に対する 不断の注意が機敏さを高めます。」という項目があります。 私は、「技術的な問題で滑ってるのにコミュニケーションで解決しようとしてるスクラム」を経験したことがあるので、そういう意味でも、注目すべきする部分は、「リリースを低速化させているボトルネックは何か?」であって、「コミュニケーションで解決するか技術で解決するか」は、当たり前ですが問題の性質を見て、アプローチを変える必要があります。スクラムだから開発プロセスは万事OKではないです。リリースやテストなどのコストが高い。もしくは不安定である。という負債を抱えたままだと、解決は難しいです。これには、勇気が必要です。自分たちのプロダクトやデプロイに問題があることを認識し、技術力が足りないことを自覚し、技術的困難と自身の成長と戦わなければなりません。しかし、そこに直視する勇気が無ければ、結局、私たちはソフトウェアを作っているので、技術的困難は無いことにして、コミュニケーションの問題に帰着させると、開発が空回りしつづけるのではないか。と思います。
年間に400回リリースしたので満足か?と言われると、全然そんなことは無いな。と思っています。例えば、DevOpsという言葉が生まれたと言われているのは、2009年のFlickrの講演です。その内容は、「10+ Deploys Per Day: Dev and Ops Cooperation at Flickr」です。当時のFlickrは1日に10回越えのリリースをしていたわけです。 STYLYのAPIは年間400回です。自分の人生で1年間にリリースした量(PRをレビューした量)としては異常ですが、営業日で平均化すると1日1.5回に過ぎないわけです。そう考えると、これは会社の規模もあるかもしれませんが、まだ私たちは13年前のFlickrすらクリアできていないわけです。このようなハードルは当たり前のように越えねばならないと思っています。
サーバーサイド正社員エンジニア絶賛募集中!!
自分がやっているのが正しい開発か。正しいアジャイルか。ということは分かりませんが、少なくとも快適に開発できるように頑張っています。なので、STYLYのサーバーサイドの超高速開発を行いたい方は、ぜひ応募よろしくお願いします!!
その他、Unityエンジニア、フロントエンドエンジニア、新卒採用も頑張ります!!