1D 歯科医療プラットフォームの技術スタック及び選定理由
こんにちわ。ワンディー株式会社のSoftware Engineerをしております上村です。
歯科医療プラットフォーム1Dは動画で歯科医療技術が学べるサブスクリプションサービスです。
Laravelで構築されたレガシーコードをGoにリプレイスしてきて、ようやく開発チームとしての技術基盤も固まってきたのと、採用活動の一環として弊社の技術スタックとその選定背景をご紹介したいと思います。
主な技術スタック
現在の1Dの主な技術スタックは以下の通りです。
- バックエンド
- Go
- AWS
- GraphQL
- gqlgen
- .ent
- Laravel
- フロントエンド
- React / TypeScript
- Next.js
- Apollo Client
- Vercel
改修前のLaravelの実装がまだ一部残っているため、バックエンドはGoとLaravelのハイブリッド状態ですが、今後はGoに統一していく予定です。
技術負債の解消
私の入社以前は1DはLaravelで作られていましたが、技術負債が蓄積されており、サービスのページスピードも表示されるまでに10秒以上かかるページもあるなど、ユーザー体験が悪化していました。
フロントエンドもLaravelのBladeテンプレートでの開発が行われており、UIとバックエンドの依存度が高い状態でメンテナンス性も低く、新機能の追加や修正が困難な状態でした。
このままではユーザー体験、開発体験ともに悪化する一方であり、これらを解決すべく技術スタックの見直しを行うことになりました。
技術スタックの選定理由
Go
Goはシンプルな言語仕様であり、コンパイル言語であるため、実行速度が速いことが特徴です。また、Goは並行処理をサポートしており、高負荷にも耐えられるため、1Dのような動画配信サービスに適していると考えました。
シンプルで読みやすいコードが書けるため、メンテナンス性が高く、読み物として読みやすいコードが書けるため、開発効率が向上します。
読みやすさはドメイン知識をまだ知らない新しいエンジニアが入ってきたときにも、コードを理解しやすくなるため、全体としてバリューの発揮がしやすい環境を作ることができます。
また、私自身やチームメンバーがGo言語で開発したかったというのも選定理由の一つです。開発体験としてモチベーションが上がる言語を選定することで、開発効率や将来にわたる採用活動にもプラスになると考えました。
「やってみたい」という好奇心と合理性を共存させた意思決定をしていきたいと考えています。
クリーンアーキテクチャ
1Dではクリーンアーキテクチャを採用しています。クリーンアーキテクチャは、ビジネスロジックをフレームワークから切り離すことで、ビジネスロジックの再利用性を高め、テストしやすくするためのアーキテクチャです。
ただし、gqlgen+.entの統合により生成されるデフォルトのリゾルバーは、レスポンス型にent.Model型を利用することでクエリの利便性を実現しているため、クリーンアーキテクチャの原則に反している部分もあります。
このあたりを丁度よい塩梅で共存させるために、Usecase層では以下のようなent.Modelをラップした独自のEntityに依存させるようにしentに直接依存させないようにしています。
package models
import (
"oned/ent"
validation "github.com/go-ozzo/ozzo-validation"
)
type BiographyEntity struct {
model *ent.Biography
v *BiographyValidation
}
func NewBiographyEntity(
name string,
) (*BiographyEntity, error) {
v := &BiographyValidation{}
err := validation.Validate(name,
v.NameRule()...,
)
if err != nil {
return nil, err
}
return &BiographyEntity{
model: &ent.Biography{
Name: name,
},
}, nil
}
func ReconstructBiographyEntity(biography *ent.Biography) *BiographyEntity {
return &BiographyEntity{
model: biography,
}
}
func (e *BiographyEntity) Model() *ent.Biography {
return e.model
}
func (e *BiographyEntity) ID() uint64 {
if e.model == nil {
return 0
}
return e.model.ID
}
func (e *BiographyEntity) Name() string {
if e.model == nil {
return ""
}
return e.model.Name
}
このように、Usecase層でビジネスロジックを実装する際に、ent.Modelをラップした独自のEntityを操作し、Resolverのレイヤーでent.Modelを公開することで、クリーンアーキテクチャの原則を守りつつ、gqlgen+.entの利便性を活かすことができるようにしています。
このあたりはまだまだ課題が有ると考えており引き続きブラッシュアップしていきたいと考えています。
インフラストラクチャ
1DではバックエンドのインフラストラクチャにAWSを採用しています。AWSはクラウドプラットフォームであり、サーバーの管理や運用を簡単に行えるため、小規模な開発チームでも運用コストを抑えながらインフラストラクチャを構築できます。
1Dは動画配信サービスであるため、動画ファイルの保存や配信には大容量のストレージが必要です。また、動画配信サービスはトラフィックが集中することがあるため、スケーラビリティが求められます。AWSは大容量のストレージやスケーラビリティを提供しており、1Dに適したクラウドプラットフォームと考えました。
バックエンドはECS(Fargate)にホスティングされています。構成としてはよくある最低限のプライベートサブネット ECS クラスタ構成です。また、データベースはRDS(Aurora MySQL)を使用しています。IaC(Infrastructure as Code)としてTerraformを使用しています。
前任から引き継いだインフラストラクチャは、EC2にホスティングされており手動での運用が多く、スケーラビリティや可用性、セキュリティの面で課題がありました。まだ100%やりきれていませんがインフラストラクチャのコード化を進め、運用コストの削減や運用の効率化を図っています。
フロントエンド(Next.js)はVercelにホスティングされています。VercelはNext.jsの開発元であり、Next.jsのデプロイを簡単に行える事と、弊社はapp routerを採用しておりキャッシュ機能やプリレンダリング機能、画像の最適化などVercelの提供する機能を活用することでユーザー体験の向上を図っています。
GraphQL
LaravelからGoにリプレイスする際に、REST APIからGraphQLに移行することを選択しました。
その大きな理由として、このリプレイス作業を始めは基本的に2人で行っていたため、より少ない開発コストで開発を進めるために、GraphQLを採用しました。
REST APIではページごとに沢山のエンドポイントを用意する必要があり開発コストがかかりますが、特に取得系についてはGraphQLはクライアントが必要なデータを自由に取得できます。
またスキーマ定義をフロントエンドとバックエンドで共有することで開発効率が向上します。
GraphQLは型安全なクエリを提供するため、開発時のエラーを事前に検知できるため開発品質が向上します。
gqlgen
gqlgenはGo言語でGraphQLサーバーを簡単に構築できるライブラリです。gqlgenを使用することで、型安全なGraphQLサーバーを簡単に構築できるため、少人数での開発でも開発効率が向上します。
.ent
.entはGo言語でORMを簡単に構築できるライブラリです。.entはgqlgenとのインテグレーションが提供されており、GraphQLサーバーとデータベースの連携が簡単に行えるため、開発効率が向上します。
またGraphQL Field Collection機能を活用することで、基本的にEager Loadingが自動的に実行されるため、N+1問題を効果的に解消できます。その結果、開発者が手間をかけることなく、アプリケーションのパフォーマンスを向上させることが可能です。
複雑な集計クエリを書く場合は不便なこともあるようですが、1Dではそのような要件は少ないため今のところは問題なく使えています。
React / TypeScript
ReactはUIライブラリであり、コンポーネント指向の開発が行えるため、UIの再利用性が高く、開発効率が向上します。また、TypeScriptは静的型付け言語であり、開発時のエラーを事前に検知できるため、開発品質が向上します。
Next.js
Next.jsはReactのフレームワークであり、SSR(サーバーサイドレンダリング)やSSG(静的サイトジェネレーション)をサポートしており、ページスピードの向上やSEO対策が行いやすいため採用しました。
また、画像最適化機能やキャッシュ機能、プリレンダリング機能も提供しており、これらの機能に乗っかることで低い開発コストでユーザー体験の向上を図ることができると考えました。
動画学習プラットフォームという特性上、ユーザーが動画を見みたり、目的の情報にアクセスする際にページスピードが速いことが求められるため、まだまだ改善の余地はあるので今後引き続きチューニングに取り組んでいきたいと思います。
Apollo Client
Apollo ClientはGraphQLクライアントライブラリであり、GraphQLサーバーとの通信を簡単に行えるため採用しました。
また、Next.jsのキャッシング機能と合わせて、クライアントコンポーネントにおけるApollo clientのキャッシング機能を活用することで、ユーザー体験の向上を図っています。
おわりに
1Dの技術スタックとその選定理由についてほんの一部ではございますがご紹介しました。
すでに走っているプロダクトの技術スタックを変更することは、開発効率や開発品質、ユーザー体験の向上に繋がる一方で、リスクも伴い非常に難しい事だと思います。
しかし、それに見合うだけの価値があると考えますし、エンジニアとして学びの機会にもなると同時に、チームとしての成長にも繋がると考えています。
合理性のもと適切にリスクを取っていけるチームでありたいと思います。
最後までお読みいただきありがとうございました。
Link
1Dではメンバーを大募集中です。1Dプラットフォームの開発に興味を持った方がいればぜひご応募お待ちしています。詳しくは以下のページをご覧ください。
Discussion