Let's write β

プログラミング中にできたことか、思ったこととか

RustでLayered Architectureをやってみた

ちょっとしたネットワークリクエストを伴うツールを書く事があり、せっかくなのでRustでレイヤードアーキテクチャでやってみることにしました。

github.com

フォルダを分けてモジュールレベルで分離するのではなく、Cargoのworkspaceをつかって各レイヤーをcrateレベルで分離する事で各層の分離をより強化するというアプローチをとってみました。

Domain層の責務は、ドメイン層のレベルで利用するモデルのStructと、リポジトリのTrait、そしてData層での実装を意図したRepositoryのProviderのTraitです。

モデルについてもTraitで提供するというのも考えましたが、完全に独立したStructで定義し、この値を構成する責任はData層に委任するという形にしてみました。 このあたりは、Traitで提供するアプローチについてもやってみたいとおもいます。

Data層では、以下のようなRepositoryを実際に実装した値を提供する責務のProviderを実装する事によってApplication層で使えるように提供します。 AndroidアプリケーションではDaggerのようなDIコンテナのフレームワークがあるのですが、 RustではDIフレームワークはほとんどつかわれておらず、自分で実装するというのが一般的なようです。

一つのクレートの中でやる場合には、Genericsをつかって実現する事が一般的なようですが、今回はcrateレベルで分離をしてみているので Box<dyn Trait>パターンで隠蔽する事になり、このあたりは記述量がかなり増えて大変な印象がありました。

pub trait SampleModelRepositoryProvider {
    fn provide_sample_model_repository(&self) -> Box<dyn repository::SampleModelRepository>;
}

これを実装したRepositoryを提供するためにData層で実装して、Data層からはこのDataModuleだけを外部に公開するようにします。

pub struct DataModule {}

impl SampleModelRepositoryProvider for DataModule {
    fn provide_sample_model_repository(&self) -> Box<dyn SampleModelRepository> { 
        return Box::new(SampleModelRepositoryImpl::new());
    }
}

このモジュールをApplication層から

let data_module = data::DataModule::new();

let repo = data_module.provide_sample_model_repository();
let model = repo.get_by_email_pass("my_email".to_owned(), "my_pass".to_owned()).awai;
println!("{:?}", model);

こんな感じで利用する事で、Domain層で提供されているインタフェースの実装の詳細を知らずに利用する事ができました。