この記事でにはいわゆる結論や主張みたいなものはありません。
個人開発の備忘録と振り返り、それに伴う学びの整理を目的とした記事になります。
概要
個人で開発・運用しているWebアプリをリプレイスしました。
タイトルにある通り、元々PHPで開発していたWebアプリを、Kotlin(フレームワークはSpringBoot)で作り直したのですが、実際には開発言語だけではなく、インフラ環境をさくらのVPSからAWSに変えたり、DBをMySQLからPostgreSQLに変えたり、設計思想をMVCからクリーンアーキテクチャに切り替えたりと、対象のシステムに関するあらゆる技術や設計方針を刷新しました。最初の開発からリプレイスに至るまでの技術選定の経緯や紆余曲折を振り返りつつ、その中での学びを整理します。
個人開発と書いてはいますが、自分が作りたいものを作ったわけではなく、知人から依頼されて作ったシステムになります。開発と運用を個人で担当したという意味での個人開発です。
対象Webアプリの概要
- スポーツイベント(主にバスケ)と、イベント参加者の予約・管理を行うためのシステム(Webアプリ)
- 管理者(イベント主催者)はカレンダーから対象日を選んでイベント情報を登録する
- 一般ユーザーはカレンダーから参加したいイベントを選んで、同伴者などの必要事項を追加して参加申し込みする
- イベントには参加上限人数が定められており、参加人数に応じてキャンセル待ちなどのステータスを管理する
- Web上だけではなく、LINE公式アカウントと連携しており、LINEログインやリッチメニューからの申し込みも可能
- 管理者はイベントの管理、参加者の管理、売上管理、メッセージ配信などが行える
アプリ開発の経緯
- バスケ好きの友人が定期的に地元の体育館や施設を借りてバスケのイベントを開催している
- 参加者の応募には、X(当時はTwitter)、Facebook、インスタ、LINE、ホームページ、など、様々なSNS媒体を使っていた
- 各イベントには参加人数の上限があるため、応募人数を把握する必要があるが、応募する媒体が多数あるため、集約するのが大変
- Excelを使って手動で集約・集計をしていたが、イベント数が増えるにつれて手に負えなくなってきたため、アプリで情報を集約する必要性を感じるようになった
- その友人からアプリ開発の依頼を受け、個人でWebアプリとして開発することになった
移行前(Ver1)のWebアプリの技術スタック
- 開発言語
- PHP(フレームワークなし)
- フロント技術
- Vue.js(CDNで一部に導入)
- Bootstrap
- DB
- MySQL
- Webサーバー
- Apache
- 外部API
- SendGrid
- LINE
- 運用サーバー
- CentOS(さくらのVPS)
- デプロイ方法
- サーバーにsshで接続し、最新ソースコードをpull
- 設計方針
- 主にMVCアーキテクチャ
- テスト
- PHPUnit, DBUnitを使って一部は自動化
- 画面操作系、LINEリッチメニューは手動テスト
技術選定の経緯
技術選定について、開発当初はあまり深く考えておらず、その当時PHPを触る機会が多くて慣れていたので、その流れでPHPを選んだ。フレームワークを導入するかどうかは正直迷ったが、結局フレームワークは使わずピュアPHPで開発を進めた。
開発規模が大きくなるならフレームワークを導入するメリットは大きいが、開発を始める段階ではシンプルな小規模システムになる想定だったので、サーバーの環境構築を楽にしたかったこともあり、フレームワークを使用しなかった。
また、PHPでフレームワークを使うならLaravelを使いたいと思っていたが、当時はLaravelをがっつり触った開発経験がなかったので、学習コストを抑えたかったこともフレームワークを使わなかった理由の1つ。
サーバー選定の経緯
Heroku
最初のリリースでは、サーバー環境にHerokuを使っていた。その当時はHerokuには無料枠があり、あまりコストをかけたくなかったこともあって、Herokuを選んだ。
しばらくはHeroku上で運用していたが、無料枠だと初回アクセス時にインスタンス起動の時間がかかるなどの制約があり、それが運用のネックになっていた。Herokuの有料プランを使用するという手もあったが、どうせならサーバー構築も自分で自由に構築できる方が後々のためになりそうだと考え、別のサービスを検討することにした。
さくらのVPS
サーバーを構築するにあたってAWS EC2にするか、さくらインターネットのVPSにするかで迷ったが、その当時は仕事でもさくらのVPSを度々使用していたことや、コストを抑えたいなどの理由からさくらインターネットを選択。
CentOS上にApache, MySQL, PHPなどを導入。ドメインも取得してDNSもさくらのサービスから管理していた。コードを修正した際にはSSHでサーバーにログインし、gitで最新のソースコードをpullする手法でデプロイしていた。
運用後の課題
開発当初、必要最低限の機能だけを備えたWebアプリにする想定で、それほど多くの機能を搭載する予定はなかったが、開発しているうちに色々な機能が欲しくなり、どんどん機能が増えていった。運用が始めるとさらに多くの要望が出て、それに伴い機能がさらに増えていった。
そういった機能の追加や修正を繰り返していく中で、技術的負債が徐々に増えていき、ある時から機能追加や修正が難しい状況になっていた。
一応MVCアーキテクチャを意識してある程度メンテナンスしやすいコードを意識して作ったつもりではあったが、それでも、機能が増えるにつれてつぎはぎのコードは増えていき、可読性は落ちて影響範囲の特定が難しくなり、機能追加や修正時のリスクが高くなっていった。
一応、イベントの運営自体は回っているようだったが、私としてはもっと運営の負担を減らしつつ、売り上げの貢献にもつながってほしいという気持ちもあったので、システムをもっと便利にしていきたいと思っていた。
まだまだ長期運用されることを考えると、どこかのタイミングでシステムを1から設計し直して新しく作り直した方が良いだろう、考えるようになった。
リプレイス時の技術スタック
結局、リプレイス版は最初のリリースから5年以上経ってしまった。
リプレイス版(Ver2)では最終的に以下の技術スタックになった。
- 開発言語
- Kotlin
- Webフレームワーク
- SpringBoot
- フロントのライブラリ
- htmx
- Alpine.js
- DB
- PostgreSQL
- Webサーバー
- Nginx
- 運用サーバー
- EC2(Amaxon Lunux 2023)
- ビルド・デプロイ方法
- CodeBuild + CodeDeploy
- 設計方針
- クリーンアーキテクチャ
- テスト
- JUnitによるドメインモデルの単体テスト
- Selenide + Gaugeによるブラウザ操作の自動テスト
- WiremockによるAPI通信の自動テスト
開発言語の技術選定
本格的にリプレイスを考え始めた時期は、ReactやLaravelを使う機会が多かったので、その技術構成で作り直すことを考えていた(というか作り始めていた)。
しかし、その後に経験した案件の影響を受けて、技術選定を大きく考え直すことになった。
Kotlin, TDD, DDDとの出会い
その案件では、Kotlin + SpringBootを使ったWebアプリ開発をしており、開発手法としてペア・プログラミングやテスト駆動開発(TDD)を採用していた。また、設計思想としてドメイン駆動設計(DDD)やクリーンアーキテクチャを取り入れていた。
その案件の開発スタイルや設計思想、技術選定に大きな影響を受けた結果、React + Laravelで作り始めていたコードは全て破棄し、Kotlin + SpringBootで、TDDやクリーンアーキテクチャを取り入れながら新たに1から作り直すことにした。
フロントの技術選定
リプレイスを考えた当時は、React + LaravelによるSPA構成にしようとしていたが、最終的にはSpringBootだけを使い、SPA構成にはしなかった(MPA構成にした)。
現代のWebアプリはSPAがスタンダートになっている印象はあるけれど、SPA構成にするとインフラ構成も複雑になる上、E2Eテストも肥大化するなど、開発工数や管理コストが大きく増えてしまう。それに見合うだけのリッチなUIが求められているわけでもなかったので、MPA構成にした。
一方、SPAではなくとも、部分的に動的なUIを実現したい箇所はどうしても出てきてしまう。そういう場面では、CDNで部分的にVue.jsを導入し、APIでJSONのデータを取得して動的なUIを構築するようにしていた。
htmxとの出会い
部分的なVue.jsの導入でも大きな課題はなかったが、開発の途中でハイパーメディアシステム──htmxとRESTによるシンプルで軽やかなウェブ開発という本を読み、htmxという技術に影響を受けた。
htmxはざっくり説明するとHTMLに属性を追加するだけでAjaxによるHTTP通信や柔軟なDOM操作ができるJavaScriptライブラリ。さらにAlpine.jsというhtmxと相性の良いライブラリを組み合わせることで、SPAに近いUI/UXを実現できることがわかった。
htmxに出会ってからは、Vue.jsで実装していた部分をhtmxとAlpine.jsに置き換えた。
結果、フロントのソースコード内のJavaScriptを最小限にしながら、SPAのようなUIを実現できるようになった。
Every Layoutとの出会い
Kotlinの開発案件に関わっているときに、Every LayoutというCSSの設計論に関する本を読み、影響を受けた。
元のシステムではCSSに関しては設計論についてはあまり深く考えておらず、Bootstrapを使いながら、最低限のレスポンシブが実現できるようにしていた。リプレイス時にはCSSのライブラリは導入せず、Every Layoutで紹介されているWebコンポーネントをいくつか導入し、レイアウトプリミティブの設計思想でレスポンシブデザインを実現するようにした。
DB設計の紆余曲折
システムを刷新する際にテーブル構造を変えてしまうとデータ移行が面倒になることが目に見えていたが、長期的な運用を考えるとテーブル設計も見直した方が良さそうと判断し、テーブル設計も刷新した。
また、元々のシステムでは環境構築の容易さからMySQLを使っていたが、機能面や個人的な好みでPostgreSQLの方が良いと思っていたので、DBも置き換えることにした。
データ移行については、移行用のCSV取り込み機能を実装して移行した。
インフラ構築
元々のシステムではサーバーにさくらのVPSを使っていたが、新しいシステムはAWS EC2に構築。ビルド・デプロイは自動化したかったので、Code BuildとCode Deployでビルド・デプロイの仕組みを構築。DBの接続情報やAPIに関する情報はパラメータストアに格納し、ログはCloud Watchで見れるようにした。
学びの整理
ここからは個人開発での学びを整理。
プロダクトマネジメントの大事さ
開発を続ける中で、プロダクトマネジメントがとても大事だと気づいた。
一番最初にこのアプリを開発し始めたころは、システムは機能が多ければ多いほど価値が高いという間違った思い込みをしていた。
最小限で作る想定のもとフレームワークを使わない選択をしたにも関わらず、いざ作り始めてみると、「こんな機能があったら便利そう」というアイデアがたくさんわいてきて、それらをなりふり構わず実装していた。しかし、実際には機能を増やしたとしてもユーザーが使う機能は限られていたし、機能を増やせば増やすほどコード量が増えて技術的負債も増えていった。
先の影響を受けた案件の中で優秀なプロダクトマネージャーの方と接する機会があり、その方と仕事をして以降、個人で開発する際にも(もちろん仕事でも)プロダクトマネジメントを強く意識するようになった。
アプリは、機能が多いほど価値が高いとは限らない。むしろ、使われない機能が多くて技術的負債も多いシステムより、本当に使われる機能だけが搭載されているシンプルなシステムの方が良い。そのようなシンプルさを実現するためには、プロダクトマネジメントが欠かせない。
ソフトウェアはシンプルであることが大事。
静的型付け言語の魅力
元々Javaの知識は多少持っていて、Javaのような静的型付け言語の方はコンパイル時にエラーを検出できる分、安全に開発しやすいということは知識としては知っていた。しかし、実際のWeb開発現場ではPHPに携わる機会が多く、静的型付け言語の良さについて実感を持って理解できていなかったように思う。
IntelliJ IDEAを使ったKotlinでの開発は私にってはとても快適で、静的型付け言語での良さを本当の意味で実感できたように思う。静的型付け言語と動的型付け言語はどちらが優れているというものではないけれど、開発の規模が大きくなればなるほど、開発のしやすさは静的型付け言語に軍配が上がるように思う。
テスト自動化とTDD、テスト設計
テストを自動化しておくことで、プロダクトの品質を保証出来たり、後々のリファクタが楽になることは理解していたつもりで、Ver1のシステムでもPHPUnitやDBUnitを使ったテストは実装していた。しかし、運用を続けていると、機能を追加してもテストコードのメンテナンスがついていかない状態になり、最終的にはテストコードが使われないものとなってしまった。この頃はTDDの本質やテスト設計の重要性を理解しておらず、とりあえずたくさんのテストを作れば良いという考えを持っていた。
TDDを実践している案件に関わったことで、テストコードを先に書くことのメリット(テスト漏れを防ぎやすくなったり、テストによって設計が洗練される)を体感することができた。また、単体テストやE2Eテストなどの自動テストの種類と、どの粒度のテストをどの程度細かく実装するか、などのテスト設計の思想や、テストのバランス感覚を学ぶことができた。
テスト自体が負債になってしまわないように、テストもちゃんと設計を意識することがすごく重要。
ドメイン駆動設計とクリーンアーキテクチャ
バックエンドの設計ではクリーンアーキテクチャとドメイン駆動設計を意識した。
DBのデータをマッピングするモデルや、画面表示や入力項目をマッピングするためのモデルをドメインモデルと切り離して、依存関係がシンプルになるように意識した。また、ドメインモデルを独立させて、ビジネスロジックなどはできるだけドメインモデルで完結できるような実装にして、単体テストを充実させた。
このような設計思想を取り入れることで、後からリファクタリングするのがとても楽になった。実はDBをMySQLからPostgreSQLに変えたり、テーブル設計を大幅に見直したのは実装が中盤になって、既にアプリがある程度動いている状態になった後だったのだが、クリーンアーキテクチャを意識した設計をしていたおかげで、影響範囲を最小限にDB設計を大きく変えることができた。
DBを変更した際に最も影響範囲が大きかったのは、E2Eテストでテストデータを作成するためのSQL。
DB固有の構文を使っていることも多かったので、ここはそれなりに修正が入ってしまった。テストデータについては、CSVからテストデータを作成する仕組みなども作っておいた方が良かったと思った。
htmxによるフロント実装
フロントはSPAにせず、htmx(とApline.js)を使っで実装した。
慣れるまで多少苦労した部分はあったが、最終的にはhtmxの機能を駆使して、JavaScriptのコードがほとんど無いにもかかわらず、SPAと言われても気づかないのでは?というくらいのUI/UXにできたと思っている。
SPAにはSPAのメリットが色々とあるけれど、システムの規模や求められているUIによっては、htmxが活躍できる場面は多そうだなと感じていて、htmxの可能性を感じた。
Every LayoutのCSS設計
CSSに関しては他のプログラミング言語と比べても苦手意識があったのだが、Every Layoutという本の考え方を理解することで、少し苦手意識が減ったように思う。
そもそも1度読んだだけでは理解できない部分も多かったので、読み返しながら不明な点をAIなどに補足してもらい、CSS全般の知識を補いながら読んでいった。そのようにCSSの知識を整理しながら読むことで、CSSの設計の種類や、設計思想の違いなどを整理することができた。
BootstrapとTailwindの思想的な違い。
Every LayoutとTailwindの思想的な違い。
ITCSSやBEMなどのCSS設計思想。
CSSの基礎知識とともに、様々なCSSの設計思想と、それらの違いを理解できたことで、CSSの実装がとてもやりやすくなったと感じている。
AWSの知見
運用サーバーはSAKURA VPSのままでもきっと運用できただろうし、その方がコストは抑えられた気がする。ただ、インフラの知識を多く学ぶならAWSの方が良いし、うまく構築すれば運用が楽になるのもAWSだと思ったので、学習も兼ねてAWSで構築することにした。
デプロイの自動化や、ログ監視、セキュリティ対策など、安全性を高めつつ運用が楽になるであろうインフラ環境を、コストの許す範囲で試行錯誤しながら構築。
今まで何となくでしか知らなかったサービスも、自分で一から構築することで理解を深められた部分が多いので、AWSで構築してみて良かったなと思う。
理想を言えば、DBはRDSを使いたかったし、アプリはECS上にデプロイしたかったし、ネットワークをより堅牢にしてセキュリティを強固にしたかったけれど、コストを考えるとどうしても難しかったので、色々と妥協した場面も多い。
IoCツールでのインフラ構成のコード化など、やりたいことは色々あるので、コストとのバランスを考えながら色々と試していきたい。
個人開発は楽しい
対象のWebアプリは人から依頼されて開発したものではあるけれど、明確な納期などもなく1人で気ままに開発できたので、のびのび開発することができてとても楽しかった。
会社の仕事での開発の場合、時間の都合上リファクタリングや設計の見直しは先送りになるケースが多い。また、チームで開発していると、既に動いているコードを気楽に修正することも難しかったりもする。1人で期限を気にしない開発の場合、大胆な設計変更やリファクタリングも気ままに実現できるので、自分の中ではかなり理想的な設計を保った実装ができたように思う。
気ままに開発できたとはいえ、実際のユーザーもいるし、バグがあるとイベントの運営に支障が出ることもあって、UI/UXやテストはそれなりに力を入れて実装したつもり。
後から振り返ると、ユーザーのためにある程度の品質のものを作らないといけないというプレッシャーと、依頼者が友人で明確な納期がないという気楽さのバランスが非常に良かったのかもしれないと思った。