RustとDDDでAPIサーバーを構築する

要約
Rust and DDD
意見はこのエリアに表示されます
アイキャッチ画像

はじめに

Rust と フレームワーク axum を使って、API サーバーを実装してみました。

対象読者

  • Rust で API サーバーを実装したい人
  • Rust で DDD を実装したい人

説明しないこと

  • Rust の基本的な文法
  • DDD の基本的な考え方
  • 使っているクレートの使い方

依存の方向

依存の方向

いざ、実装

仕様を決める

今回は、大学がサークルを管理するシステムを作ることにしました。

  • メンバーを追加できる
    • 4 年生は、追加できない
  • メンバーを削除できる
    • オーナーは削除できない
  • 4 年生は、卒業する
  • サークルは最低 3 人以上でないと、活動できない
  • サークルは、最大人数が決まっている
  • サークルには、代表者が必要
  • 20 歳以上の人は、飲み会に参加できる
  • 3 年生のみ、サークルの代表者になれる

ドメインレイヤー

サークル集約

まずは、ドメインレイヤーから作成します。
サークル集約は、集約ルートになる Circle と、集約内のメンバーを表す Member の 2 つのエンティティから構成されます。

id には Value Object を使っています。今回、EntityValue Object の違いなどは、説明しませんので、興味がある方は、調べてみてください。

:::details CircleId

:::

次に、集約に知識をあたえるために、メソッドを実装します。
Rust では implstruct にメソッドを実装します。

インターフェース

ドメインの振る舞いを外部に公開するためのインターフェースを作成します。
サークル集約を操作するためのインターフェースを作成します。

traitは、他の言語で言うところのinterfaceのようなものです。

インフラストラクチャレイヤー

インフラストラクチャレイヤーは、永続化を担います。
永続化先は問いません。FirestorePostgres など、なんでも良いです。
今回は、メモリに保存することにしました。

インフラストラクチャレイヤーでは、ドメインレイヤーのインターフェースを実装します。

データベースから取得した値を、XxxDataという名前で表しています。今回は、CircleDataMemberData です。
このデータベースの型をドメインレイヤーの型に変換するために、TryFrom トレイトを実装し、内部で、reconstruct メソッドを使って、ドメインレイヤーのEntityの型に変換しています。
今回は DB が オンメモリなので、あまり意味を感じにくいですが、アプリケーション層との依存をなくすのに有効です。
DB が突然、Firebase に変わっても、データベースの型を変えるだけで、アプリケーション層には影響を与えません。
そのためにも、アプリケーション層がインフラストラクチャレイヤーに依存しないように抽象に依存させることが重要です。(後述します)

DB の実装は DDD にそれほど関係ないので、興味のある方は見てください。
:::details メモリ上に保存する DB の実装

RawLock を使って、排他制御をしています。
:::

アプリケーションレイヤー

アプリケーションレイヤーでは、ユースケースの実現のために、EntityVO を使って、レポジトリー (インフラ層) に処理を依頼します。
レポジトリーに処理を依頼しますが、インフラ層には依存しません。
いわゆる、依存性逆転の原則を用いて、ユースケースを実現します。
実態ではなく、抽象に依存させるために、トレイトを使います。
今回は、サークルを作成するユースケースを実装します。

ひとつひとつ見ていきます。

  1. usecase の io (入出力)
    usecase の io を、CreateCircleInputCreateCircleOutput として定義します。

  2. CreateCircleUsecase 構造体
    CreateCircleUsecase はジェネリクス構造体です。フィールドには、CircleRepositoryInterface トレイトを実装したものを受け取ります。

  3. impl CreateCircleUsecase
    CreateCircleUsecase の構造体に対して、2つメソッド、newexecute を実装します。

    • new メソッド
      インスタンスを生成するためのメソッドです。CircleRepositoryInterface トレイトを実装したものを受け取ります。
      他の言語で言うところのコンストラクターです。ここに、依存を注入します。実態ではなく、抽象(トレイト)を設定することで、依存性逆転の原則を実現します。

    • execute メソッド
      ユースケースを実行するためのメソッドです。CreateCircleInput を受け取り、Circle Entity を作成し、レポジトリーに保存します。
      self とは、CreateCircleUsecase の自身を指します。今回はフィールドが1つしかないので、self.circle_repository でフィールドにアクセスでき、そのフィールドが、CircleRepositoryInterface トレイトを実装していることが保証されているため、create メソッドを呼び出すことができます。

これで、ユースケースはドメインのみに依存し、インフラストラクチャレイヤーに依存しないように実装できました。

プレゼンテーションレイヤー

プレゼンテーションレイヤーでは、エンドポイントの定義、リクエストの受け取り、レスポンス、アプリケーション層に渡す値のマッピングを行います。

ひとつひとつ見ていきます。

  1. io (入出力)
    CreateCircleRequestBody は、リクエストのボディを受け取るための構造体です。
    CreateCircleRequestBodyfrom を実装して、CreateCircleInput を構築できるようにしています。

    レスポンスも同じような形で実装しています。

  2. AppState
    AppState は、アプリケーションの状態を表す構造体です。axum が提供しています。このアプリケーションでは、レポジトリーを保持していて、依存関係を解決する DI コンテナのような役割をしています。
    今回は依存が一つしかないので、ありがたみを感じられませんが、例えば、テスト時に、CircleRepositoryCircleRepositoryMock のようなモックに差し替えることができます。

  3. handle_create_circle
    handle_create_circle 関数は、リクエストを受け取り、CreateCircleUsecase を実行します。
    第一引数の State(state) では AppState の値を取り出せます。
    取り出した、値を、CreateCircleUsecase に注入して、execute メソッドを呼び出します。前述したusecase を実行している部分です。io はそれぞれ任意の形式に変換しています。

まとめ

Rust axum DDD を使って、API サーバーを実装してみました。Rust はまだまだ、業務での採用が少ないですが、バックエンドをはじめに盛り上がってくると思うので、ぜひ、使ってみてください。また、新しい、ユースケースや、集約を追加していただいたりして、学習の手助けになればと思います。
今回のソースコードは、こちら にあります。 main ブランチは更新しているので、記事執筆時点のソースコードは ver 0.1.0 を参照してください。

Explore More
関連記事はありません。