見出し画像

enechainの技術スタックをご紹介します

※最新の技術スタックに関する記事はこちら

こんにちは。エンジニアの青戸です。

enechainでは、「誰もが参加でき、あらゆるエネルギーの価値を交換できるフェアなマーケット」を創るため、2021年よりオンライントレーディングプラットフォームの eSquareデータプラットフォームの eCompass の提供を開始しました。サービスの概要については下記をご覧ください。

この記事では、これらのサービスを立ち上げるにあたり、どのような基準で技術選定を行ったのか、結果的にどのようなアーキテクチャや技術スタックを採用したのかをご紹介します。

【2022/06/03 追記】
新プロダクトの技術スタックを追記しました!


技術選定およびアーキテクチャ設計の方針

サービスの開発に着手した当時はエンジニアは2名体制でした。限られたリソースの中で早期にサービスを立ち上げるため、利用する技術の種類を絞って必要な学習コストを下げたり、マネージドサービスや XaaS を利用して初期の開発コストや運用コストを抑えることに主眼を置きました。アーキテクチャ設計においては、初期の開発速度を第一に考えながらも、サービスの大規模化や対象領域の拡大を念頭に置き、ある程度の拡張性も持たせられるような構成を考えました。

下記のような大方針を元に、技術選定やアーキテクチャ設計を行いました。

サーバレスファースト

サーバーの維持運用コストの削減や、スケーラビリティや可用性の向上を目的として、アーキテクチャ設計においては、サーバーレスなサービスやマネージドサービスを優先的に選択するようにしました。

モジュラーモノリス

リポジトリ構成の戦略にはモノリス、マルチレポ、モノレポなど様々なものが存在しますが、初期の戦略としてはモジュラーモノリスを採用しました。モジュラーモノリスは単一のリポジトリで構成され、アプリケーションも単一のプロセスとして動作しますが、ドメイン等の境界でコードを分割することによってコードベースのモジュール性を高める戦略です。

初期の開発速度のみを重視するならモノリスが最適でしたが、将来のサービスの大規模化やマルチプロダクト化に堪えられるようにマイクロサービス化を想定していましたので、マイクロサービスへの段階的な移行が容易なモジュラーモノリスを選択しました。

イベントドリブン

サービス間の通信方式としては REST や RPC などの同期通信とメッセージングによる非同期通信がありますが、サービスの自立性を確保することを優先し、原則としてメッセージングによる通信を選択することとしました。

実践ドメイン駆動設計で紹介されているドメインイベントの考え方を参考に、他サービスとの通信はドメインイベントをメッセージとしてやり取りすることによって実現しています。

スキーマ駆動

バックエンドとフロントエンドの開発を並列化したり、型定義の恩恵をフロントエンド側でも享受できるように、API開発はスキーマ駆動で行うこととしました。スキーマの定義には OpenAPI Specification を採用しました。

技術スタック

結果的に下図のような技術スタックを採用しました。

以下で各種技術の選定理由についてくわしく説明していきます。
※ データ基盤については別記事でご紹介する予定です。

開発言語

開発言語はバックエンドとフロントエンド共に Typescript で統一しました。バックエンドとフロントエンドの開発言語を統一することによって、学習コストやスイッチングコストを下げ、開発リソースを柔軟にやりくりできるようにすることが狙いです。

JavaScript ではなく TypeScript を採用したのは、将来的な大規模開発を見越すと型によるサポートは必須であると考えたことと、ゼロからのプロダクト開発でしたので導入するには最適のタイミングであると考えたためです。

バックエンド

Node.js / Express.js

eSquare / eCompass は B2B 向けのサービスということもあり、リリース後しばらくは実行環境がボトルネックになるほどの高負荷なリクエストは発生しないと予想されたため、性能面で有利な go や Rust よりも、フロントエンドと開発言語を共通化することによって開発効率を高められる Node.js を選択しました。フレームワークには Express.js を採用しています。

PostgreSQL / Type ORM

データベースは PostgreSQL を採用しており、マネージドな RDB サービスである Cloud SQL で稼働させています。Mongodb や Firestore などの NoSQL も選択肢として考えられましたが、エネルギー取引という複雑性の高いドメインを扱ううえでは、スケーラビリティよりもデータの整合性を保つのが容易で複雑なクエリを書きやすい RDB の方がメリットが大きいと考えました。

ORM は TypeScript との親和性が高いことを理由に TypeORM を採用しました。

Open API Specification / Open API generator

前述の通り、APIの開発はスキーマ駆動で進めました。スキーマ定義は OpenAPI-Specification を利用し、フロントエンド向けのコードは Open API generator にて生成しています。API のクライアントコードを書く必要がなくなったことと、オブジェクトの型定義を TypeScript の型付けによってフロントエンド側に強制できたことによって、開発効率の向上に寄与しました。

フロントエンドとの通信方式として GraphQL が採用されることも多くなってきましたが、クエリの柔軟さよりも実装のシンプルさを優先して REST API を選択しました。

Auth0

コアドメインでない領域はできるだけ既存のソリューションを利用するという方針に基づき、認証認可の基盤は IDaaS の Auth0 を利用して構築しました。ID管理や認証だけでなく、API へのアクセス制御にも Auth0 が提供している RBAC の仕組みを利用することによって、実装のコストを大幅に削減できました。

Auth0 から提供されている機能を活用し、外部サービスとのシングルサインオンや多要素認証なども自前実装なしで実現しています。

フロントエンド

Vue.js / React

フロントエンドのフレームワークには Vue.js を採用しました。React も選択肢として上がりましたが、中規模なアプリケーションであれば Vue.js のほうがより素早く開発ができることと、私含めて初期のエンジニアは Vue.js の開発経験があったため、立ち上がりのスピードを優先して Vue.js を採用しました。

開発言語はバックエンドと統一し TypeScript を採用しています。Vue.js はTypeScript との相性、特に Vuex との相性の悪さについて言及されることが多いです。実際に開発の初期段階はデファクトスタンダードな書き方が存在せずいろいろと試行錯誤を強いられましたが、vuex-smart-moduleなどの型推論をサポートするライブラリを組み合わせることによって快適に開発をすすめることができました。

現在、eSquare / eCompass のほか、新規プロダクトの開発にも着手しています。こちらはより大規模な開発を想定し、 React を採用しています。

Bulma / Buefy

CSS フレームワークは Bulma を採用しました。デザイナー不在の中での開発でしたので、選定の方針としては一般的によく使われるコンポーネントが一通り揃っていて、スタイルのカスタマイズが容易であることや、SPA フレームワークとの組み合わせで不具合が起こらないように JavaScript を使っていないことなどを念頭に置きました。ツールチップなどの動的なコンポーネントは、Bulma をベースとしたUIコンポーネントライブラリであるBuefyを利用して実現しています。

Storybook / Chromatic

Storybook を利用しコンポーネント駆動によるボトムアップなUI開発を行っています。原則として、すべてのUIコンポーネントに対して Story を書き、コンポーネント単体で様々な状態に対する挙動を確認できるようにしています。Storybook は共通的なコンポーネントのカタログとしても活用されています。

また、コンポーネントのビジュアルリグレッションテストの基盤として Chromatic を採用し、CI で自動的に実行しています。プルリクエストからコンポーネントの見た目の差分を簡単に確認できるためレビューの効率化を図れていますし、想定外のコンポーネントへの影響を発見できるため、これまでに幾度となく助けられました。

インフラ

GCP

クラウドプロバイダーは依然として AWS のシェアが支配的ですが、サーバレスなアーキテクチャでサービスを作りやすいことと、コスト的に有利であるという理由で GCP を採用しました。

ウェブアプリケーションの実行環境としては App Engine を採用しました。初期リリース時点ではアプリケーションは単一のサービスとして動作させる前提でしたので、実行環境のセットアップやスケーリング等をすべてよしなにやってくれるのは非常に助かりました。Kubernetes や Cloud Run 等は開発のオーバーヘッドに対して得られるメリットが少なかったため、利用は見送りました。

その他、データストアやメッセージング、タスクキュー等、インフラを構成する要素には基本的にはGCPのマネージドサービスを利用する方針としました。

Terraform

IaC ツールには Terraform を採用し、GCP や Auth0 のリソースをコードで管理しています。State や Module の管理に Terraform Cloud を利用することによって、管理コストを抑えられるようにしました。

plan や apply は CI / CD パイプラインに組み込んで自動化する方法も考えられましたが、初期開発時にはある程度の試行錯誤が予想されたため柔軟性を優先してローカルで実施する運用としています。

CI/CD

GitHub Actions

CI はセットアップの手軽さや GitHub との連携が優れいている点にメリットがあるため GitHub Actions を採用しました。バックエンドではユニットテストや linter 等を、フロントエンドではそれらに加えて前述の Chromatic を自動実行しています。

Cloud Build

リポジトリ戦略としては軽量な Github flow を採用し、ブランチが master ブランチにマージされると直ちにプロダクション環境にデプロイされる仕組みを構築しました。GitHub Actions で CI から CD まで一気通貫でやってしまう方法も考えられましたが、デプロイプロセスを GCP 内で完結させることによって、サービスアカウントのクレデンシャル管理が不要になったり、 CI と分離されていることがセキュリティ的にも望ましかったため、Cloud Build を採用しました。

運用監視

Cloud Logging

アプリケーションの実行環境として App Engine を採用したことから、Google 社からロギングライブラリとの統合が提供されており簡単にログを蓄積できる Cloud Logging を使うこととしました。

アプリケーションログはシンクで Big Query に転送し、アクセスログの解析や SLI の測定等にも利用しています。

Opsgenie / Sentry

バックエンドサービスのアラート監視には、必要な機能を一通り備えていて導入が容易という理由で Opsgenie を選択しました。Cloud Loggingに書かれたエラーログを元に Opsgenie の API をコールしアラートが発報されるような仕組みを構築しています。フロントエンドのエラー監視には Sentry を採用しました。

各種アラートは Slack の運用監視用チャンネルに連携し、障害対応に関する情報共有を行えるようにしています。

【06/03 追記】 新プロダクトの技術スタック

新規プロダクトの技術スタック紹介は、担当エンジニアの小沢から紹介します。
新サービスは既存サービスと同様のB2Bサービスとなります。こちらのプロダクトでは下図のような技術スタックを採用しました。

バックエンド

Node.js / NestJS

新プロダクトは、バックエンドのフレームワークにNestJS、スキーマ定義にはGraphQLを採用しました。

TypeScriptとの相性、DI標準対応によるレイヤードアーキテクチャへの対応しやすさ、モジュール単位での管理によるドメイン境界の意識しやすさなどから、弊社のDDDベースの開発に適していると判断したためです。

PostgreSQL / Prisma

RDBは既存プロダクトと同様の理由でPostgreSQLを、ORMはPrismaを採用しました。

PrismaもTypeScriptとの親和性が高く、カラムに対してもコード補完が効くため実装に集中することが出来ます。またテーブルに対するバージョン管理機能といった周辺機能が整っているため開発体験の向上につながっています。

GraphQL / GraphQL Code Generator

スキーマ定義は、新プロダクトのユースケースが複雑である点からフロントエンド側で柔軟にデータフェッチできるようにするためGraphQLを採用しました。

フロントエンドは、GraphQL Code Generatorを使用しAPIクライアントのコードを生成しています。APIクライアントには、ReactのErrorBoundaryやSuspenseとの相性が良いReact Queryを採用しました。

フロントエンド

React / Chakra UI

新プロダクトは、UIライブラリにReact、コンポーネントライブラリにChakra UIを採用しました。

新プロダクトはある程度複雑なUIを構築することがわかっているため、中規模以上のコードベースになることを想定しReactを採用しました。また、ErrorBoundaryとSuspenseによるエラーやローディングといった状態の表現が直感的となるため、開発体験の向上につながっています。
Next.js については、SSR の要件が無かったことやベンダロックインを避けたい気持ちもあって採用を見送っています。

Chakra UIは、デフォルトスタイルを持つコンポーネントやユーティリティーが充実しており、TypeScriptとの親和性も高いため開発に集中することが出来ています。
テーマ設定の機能がある点やFigmaのオートレイアウトから直感的に実装出来る点も手触りがよく気にいっています。

インフラ

Kubernetes

既存プロダクトと同様にクラウドプロバイダーはGCPを採用しています。アプリケーションの実行環境としては、Kubernetesを採用しています。

Kubernetesにすることの初期導入のコストはかかりますが、CI/CDのパイプラインが整理され、シンプルになりました。
また、バッチシステムのようなワークフローはArgo Workflowsで実現しています。Argo WorkflowはKubernetesネイティブなワークフローエンジンであり、新たなマネージドサービスをイチから学ぶよりも学習コストが押さえることが出来ました。

今後の展望

本記事の序盤では、サービス立ち上げ期における技術選定の考え方や技術スタックについてご紹介しました。初期開発コストや運用コストの低減を重視した技術選定を行いましたが、事業の成長に伴いサービスもエンジニア組織も規模を急速に拡大しており、少人数では効率的だった開発環境にも様々な課題が顕在化しつつあります。技術選定やアーキテクチャ設計の観点も、大規模化する組織においていかにスケールさせられるかどうかにシフトしつつあります。
この課題に対するアップデートとして、記事の後半では新プロダクトにおける技術選定のお話をしました。現在、SRE チームを中心としてシステム全体の Kubernetes 移行や CI / CD パイプラインの刷新を進めています。開発者がより開発に集中でき、デプロイを簡単かつ安全に実行できるような開発環境を急ピッチで整備しています

アプリケーション開発チームも、既存サービスの改善や新規サービスの開発と並行して、段階的なマイクロサービス化など、サービスの大規模化に備えたアーキテクチャ改善を進めていく予定です。

enechain では、長く続くサービスだと考えているからこそ、最新の技術へのキャッチアップや技術選定を重要視しています。
数十年続くシステムについて真剣に議論出来る仲間を求めています!

一緒に働いていただける仲間を募集しています!

enechainではこれから一緒にエネルギー業界にビッグウェーブを起こしてくれる仲間を募集しています!ミッションや事業内容、エンジニア組織など何でも結構ですので、少しでも興味を持っていただいた方はご連絡ください!

もっとenechainを知りたい方は、こちら
採用に興味のある方は、こちら
Meetyでエンジニアとお話したい方は、こちら


この記事が気に入ったらサポートをしてみませんか?