
はじめに
Rust と フレームワーク axum を使って、API サーバーを実装してみました。
対象読者
- Rust で API サーバーを実装したい人
- Rust で DDD を実装したい人
説明しないこと
- Rust の基本的な文法
- DDD の基本的な考え方
- 使っているクレートの使い方
依存の方向

いざ、実装
仕様を決める
今回は、大学がサークルを管理するシステムを作ることにしました。
- メンバーを追加できる
- 4 年生は、追加できない
- メンバーを削除できる
- オーナーは削除できない
- 4 年生は、卒業する
- サークルは最低 3 人以上でないと、活動できない
- サークルは、最大人数が決まっている
- サークルには、代表者が必要
- 20 歳以上の人は、飲み会に参加できる
- 3 年生のみ、サークルの代表者になれる
ドメインレイヤー
サークル集約
まずは、ドメインレイヤーから作成します。
サークル集約は、集約ルートになる Circle
と、集約内のメンバーを表す Member
の 2 つのエンティティから構成されます。
id には Value Object
を使っています。今回、Entity
と Value Object
の違いなどは、説明しませんので、興味がある方は、調べてみてください。
:::details CircleId
:::
次に、集約に知識をあたえるために、メソッドを実装します。
Rust では impl
で struct
にメソッドを実装します。
インターフェース
ドメインの振る舞いを外部に公開するためのインターフェースを作成します。
サークル集約を操作するためのインターフェースを作成します。
trait
は、他の言語で言うところのinterface
のようなものです。
インフラストラクチャレイヤー
インフラストラクチャレイヤーは、永続化を担います。
永続化先は問いません。Firestore
や Postgres
など、なんでも良いです。
今回は、メモリに保存することにしました。
インフラストラクチャレイヤーでは、ドメインレイヤーのインターフェースを実装します。
データベースから取得した値を、XxxData
という名前で表しています。今回は、CircleData
と MemberData
です。
このデータベースの型をドメインレイヤーの型に変換するために、TryFrom
トレイトを実装し、内部で、reconstruct
メソッドを使って、ドメインレイヤーのEntity
の型に変換しています。
今回は DB が オンメモリなので、あまり意味を感じにくいですが、アプリケーション層との依存をなくすのに有効です。
DB が突然、Firebase
に変わっても、データベースの型を変えるだけで、アプリケーション層には影響を与えません。
そのためにも、アプリケーション層がインフラストラクチャレイヤーに依存しないように抽象に依存させることが重要です。(後述します)
DB の実装は DDD にそれほど関係ないので、興味のある方は見てください。
:::details メモリ上に保存する DB の実装
RawLock を使って、排他制御をしています。
:::
アプリケーションレイヤー
アプリケーションレイヤーでは、ユースケースの実現のために、Entity
や VO
を使って、レポジトリー (インフラ層) に処理を依頼します。
レポジトリーに処理を依頼しますが、インフラ層には依存しません。
いわゆる、依存性逆転の原則を用いて、ユースケースを実現します。
実態ではなく、抽象に依存させるために、トレイトを使います。
今回は、サークルを作成するユースケースを実装します。
ひとつひとつ見ていきます。
-
usecase の io (入出力)
usecase の io を、CreateCircleInput
とCreateCircleOutput
として定義します。 -
CreateCircleUsecase 構造体
CreateCircleUsecase
はジェネリクス構造体です。フィールドには、CircleRepositoryInterface
トレイトを実装したものを受け取ります。 -
impl CreateCircleUsecase
CreateCircleUsecase
の構造体に対して、2つメソッド、new
とexecute
を実装します。-
new メソッド
インスタンスを生成するためのメソッドです。CircleRepositoryInterface
トレイトを実装したものを受け取ります。
他の言語で言うところのコンストラクターです。ここに、依存を注入します。実態ではなく、抽象(トレイト)を設定することで、依存性逆転の原則を実現します。 -
execute メソッド
ユースケースを実行するためのメソッドです。CreateCircleInput
を受け取り、Circle Entity
を作成し、レポジトリーに保存します。
self
とは、CreateCircleUsecase
の自身を指します。今回はフィールドが1つしかないので、self.circle_repository
でフィールドにアクセスでき、そのフィールドが、CircleRepositoryInterface
トレイトを実装していることが保証されているため、create
メソッドを呼び出すことができます。
-
これで、ユースケースはドメインのみに依存し、インフラストラクチャレイヤーに依存しないように実装できました。
プレゼンテーションレイヤー
プレゼンテーションレイヤーでは、エンドポイントの定義、リクエストの受け取り、レスポンス、アプリケーション層に渡す値のマッピングを行います。
ひとつひとつ見ていきます。
-
io (入出力)
CreateCircleRequestBody
は、リクエストのボディを受け取るための構造体です。
CreateCircleRequestBody
にfrom
を実装して、CreateCircleInput
を構築できるようにしています。レスポンスも同じような形で実装しています。
-
AppState
AppState
は、アプリケーションの状態を表す構造体です。axum
が提供しています。このアプリケーションでは、レポジトリーを保持していて、依存関係を解決する DI コンテナのような役割をしています。
今回は依存が一つしかないので、ありがたみを感じられませんが、例えば、テスト時に、CircleRepository
をCircleRepositoryMock
のようなモックに差し替えることができます。 -
handle_create_circle
handle_create_circle
関数は、リクエストを受け取り、CreateCircleUsecase
を実行します。
第一引数のState(state)
ではAppState
の値を取り出せます。
取り出した、値を、CreateCircleUsecase
に注入して、execute
メソッドを呼び出します。前述したusecase
を実行している部分です。io はそれぞれ任意の形式に変換しています。
まとめ
Rust axum DDD を使って、API サーバーを実装してみました。Rust はまだまだ、業務での採用が少ないですが、バックエンドをはじめに盛り上がってくると思うので、ぜひ、使ってみてください。また、新しい、ユースケースや、集約を追加していただいたりして、学習の手助けになればと思います。
今回のソースコードは、こちら にあります。 main ブランチは更新しているので、記事執筆時点のソースコードは ver 0.1.0 を参照してください。