Rustでクリーンアーキテクチャを組む時DIするサンプルコード


はじめに

Rustでクリーンアーキテクチャを組もうとしていて、うまくレイヤーを分離できませんでした。
思想というよりは単にtraitやジェネリクスの正しい書き方を知らなかっただけでかなりの時間を要してしまったので、自分のメモを兼ねて書いてみようと思います。

構成としては
DB -> Repository -> Serviceというようなレイヤーがあった時に、いかに分離(=DI)するかを想定しています。

関連型 + 関連関数での実装


#[derive(Debug)]
struct User {
    age: u8
}

struct PgDatabase;

// 関連型を用いたトレイト
trait Repository {
// 関連型
    type CreateRequest;
    type CreateResponse;

// 関連関数
    fn create(request:Self::CreateRequest) -> Self::CreateResponse;
}

impl Repository for PgDatabase {
    // add code here
    type CreateRequest = u8;
    type CreateResponse = User;
    fn create(age: u8) -> User {
        User { age }
    }
}

struct Service<U:Repository>(U);

impl <U:Repository> Service<U>{
    pub fn new_with_age(&self, age: U::CreateRequest) -> U::CreateResponse {
        U::create(age)
    }
}

fn main() {
    let db: PgDatabase = PgDatabase;
    let service = Service::<PgDatabase>(db);
    let user = service.new_with_age(20);
    dbg!(user);
}

ジェネリクスとメソッドでの実装


#[derive(Debug, Clone, Copy)]
struct User {
    id: u16,
    age: u8
}

struct PgDatabase{
    // DB機能のモックです
    next_user_id: u16,
    users: Vec<User>
}

// generic trait
trait Repository<T,U,V> {
    fn create(&mut self, new_params:T) -> U;
    fn find_by_id(&self, id: V) -> Option<&U>;
}

impl Repository<u8, User, u16> for PgDatabase {
    // add code here
    fn create(&mut self, age: u8) -> User {
        let u = User{ age, id: self.next_user_id };
        self.next_user_id = self.next_user_id + 1;
        self.users.push(u);
        u
    }

    fn find_by_id(&self, id: u16) -> Option<&User> {
        self.users.iter().find(|user| { user.id == id })
    }
}

struct Service<U:Repository<u8, User, u16>>{
    repository: U
}

impl <U:Repository<u8, User, u16>> Service<U>{
    pub fn new_with_age(&mut self, age: u8) -> User {
        self.repository.create(age)
    }
}

fn main() {
    let db: PgDatabase = PgDatabase { next_user_id: 0, users: Vec::new()};
    let mut service = Service::<PgDatabase>{repository: db};
    let user = service.new_with_age(20);
    dbg!(user);
}

........その他はパターンを見つけ次第追記予定

終わりに

今の所2つのサンプルしかありませんが、他の実装パターンも追記予定です。
良くない書き方があるかと思いますので、お気づきの際にはご指摘いただければ幸いです。