RustのWebアプリケーションでDependency inversionさせる

3 min読了の目安(約1800字TECH技術記事

Clean Architectureのようなレイヤードアーキテクチャを実現するためには依存関係を逆転させる必要があります。この記事ではレイヤードアーキテクチャ自体の説明は割愛し、RustのWebアプリケーションでどうのように依存関係を逆転させたかについて書いています。
サンプルコードはニュースを非同期に取得するものです。Use caseとGatewayとPortいういう概念を用いて説明していきます。Use caseにアプリケーションロジックを、Gatewayには外部APIやデータストアなどからの戻り値をDomainに変換するロジックを書きます。Portは実装の詳細から依存される抽象的なレイヤです。以下の図のようにUse caseがPortに依存し、GatewayもPortに依存するようにします。

それぞれのレイヤについて説明していきます。

Use case

news_get_usecaseにはニュースのID一覧を取得し、そのIDをもとに最新のニュースを100件分取得するアプリケーションロジックが書かれています。Use caseはGatewayの具体的な実装を知らず、Portを実装した何かの存在しか知りません。

pub async fn execute(news_port: impl NewsPort) -> Result<NewsList, Error> {
    let news_ids = news_port.find_news_ids().await?;
    news_port.find_news(news_ids.take_latest(100)).await
}

Port

Use caseとGatewayから依存される抽象的なレイヤです。Portの実装はGatewayで行います。

#[async_trait]
pub trait NewsPort {
    async fn find_news(&self, ids: NewsIds) -> Result<NewsList, Error>;
    async fn find_news_ids(&self) -> Result<NewsIds, Error>;
}

Gateway

Portの実装を行うレイヤです。下のサンプルコードでは省略していますが、Gatewayには外部APIやデータストアなどからの戻り値を変換するロジックが書かれます。このレイヤのおかげでドメインロジックやアプリケーションロジックがアプリケーションの外界に依存しなくなります。

#[derive(Debug, Copy, Clone)]
pub struct NewsGateway;

#[async_trait]
impl NewsPort for NewsGateway {
    async fn find_news(&self, ids: NewsIds) -> Result<NewsList, Error> {
        // convert a data format received from external systems to a particular Domain object
    }

    async fn find_news_ids(&self) -> Result<NewsIds, Error> {
        // convert a data format received from external systems to a particular Domain object
    }
}

これらの実装により依存関係を逆転させることができました。

コード全体

最後に

他のAPIやデータストアなどの通信対象の増加に伴って依存対象を増やしたい場合でも、Use caseの関数の引数を増やすだけで依存関係自体は簡潔に保つ方針で実装しました。そのため、かなり簡潔な方法で依存関係を逆転できたと思います。