uramnoil / serverist

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Serverist

Kotlin + DDD × クリーンアーキテクチャで作るマイクラ用のサーバーリスト

ディレクトリ構成

/application

アプリケーション層

クライアントサイドとサーバーサイドのインターフェースが定義されている。

/domain

ドメイン層

サーバーサイドのみ利用

/backend

サーバーサイド

Ktor + GraphQL + Exposedで構成。

/clientCommon

クライアントサイドのアプリケーション層の実装や、Jetpack Composeのコンポーネント(UI)が含まれる。

View層はInteractor等の実装に依存してはイケナイため、おそらくUIは別のサブプロジェクトで記述した方が管理がしやすい。

/webCompose

Kotlin/JSとJetpack Compose for Webで作るSPAプロジェクト。 compose.materialがfor Webに対応するまでは、/clientCommonとは別にこのプロジェクト内で全てのコンポーネントを作成する必要がある。

/webReact

Kotlin/JSとReactで作るSPAプロジェクト

知見

Clean Architecture

現状フロントエンド・バックエンドの両方でクリーンアーキテクチャの採用は難しいという結論に至る。

サーバーサイドとクライアントサイドでUseCaseの共通化は難しい

  1. サーバーサイドでは全てのユーザーに対してUseCaseが使われるため、IDによってUseCaseがユーザーを識別する必要がある。 クライアントサイドではCookieなどを使って誰であるかを表現するため、UseCaseでIDを使うべきではない。 つまり、UseCaseの引数の統一が困難である。

  2. また、サーバーサイドとクライアントサイドで必要となるUseCaseやUseCaseのパッケージの場所(CQRSを適用している場合)が異なる。

Note
本プロジェクトでは、:applicationサブプロジェクトでサーバーサイドとクライアントサイドのアプリケーション層のコードを管理している。そこではUseCase本体の共通化はしていないものの、Entityは共通化しているためある程度の冗長性の削減は達成できている。それらのEntityはGraphQLを介して共有されるため、スキーマの管理さえ徹底していれば型の安全は保証される。

UseCaseの引数にはDTOを使うべき

サーバーサイド「The Clean Architecture」は難しい

サーバーサイドでの厳密なクリーンアーキテクチャは、フレームワークが対応していない限りは実装が困難である。

本プロジェクトで使用しているKtorを含め、ほとんどのWebフレームワークは一つの関数でリクエストを処理している。そのため、関数が終了された時点でレスポンスを返し終えている必要があり、Controller→PresenterとFlowが定義されているThe Clean Architectureのサーバーサイドでの適用は困難を極める。

ここからは、本プロジェクトが真のサーバーサイドクリーンアーキテクチャに近づくために実装した手法を紹介する。

基本的に上記のフレームワークの性質から逃れられないので、suspendCoroutine()を使ってControllerでPresenterの完了を待機することで、ControllerとPresenterに分離したままControllerが持つハンドラが終了するまでにレスポンスを返却することを実現した。Controllerの責務が若干肥大化するが、UseCaseInputPortがControllerに直接値を返す、いわゆる「なんちゃってクリーンアーキテクチャ」は回避することができたと思われる。

サーバーサイドクリーンアーキテクチャを実現するためにさまざまな試行錯誤を重ねてきたが、実際はより使いやすいアーキテクチャの適用、またはクリーンアーキテクチャと相性の良いWebフレームワークを用意するのが賢い選択になる。アーキテクチャが意味もなく開発を阻害しては本末転倒であることを改めて実感することができた。

MVCフレームワークでは実装に難がある為、サーバーサイドではより簡易なオニオンアーキテクチャを採用する。(commit: 79a29718d1ed879e4aab949ee98f55c9ce1a0873)

関数ベースのUIライブラリとは相性が悪い(気がする)

Presenterの運用が比較的難しい印象を受ける。ViewはPresenterに内包されている訳ではなく、ReactやJetpack Compose上ではあくまで対等の依存関係となり、PresenterからViewへの操作で従来のやり方とは若干異なる。

ViewModelを介さない独立したPresenterクラスの作成はViewの操作に手間がかかるため、OutputPortを無名オブジェクトかFun Interfaceを使って外部の変数をキャプチャすると記述が楽になる。

ViewModelを介した操作も、状態の変更のみではなく直接Viewを操作する場合で課題が残るので、今後解消していきたい。

フロントエンドでも、状態管理ライブラリ等に剃ったアーキテクチャを採用するべき。

コンテキスト間の関係性の表現

IDDDでは識別子を共有してもいいらしい。一文のみ

About


Languages

Language:Kotlin 98.7%Language:HCL 1.0%Language:HTML 0.2%Language:Dockerfile 0.1%