Skip to content

Idのラッパー型の定義とそれに関連するリファクタリング #40

@wasabi-eater

Description

@wasabi-eater

期末テストが終わったらやりましょう。

やりたいこと

  • UserId 型と ArticleId 型の定義
  • 既存の User 型, Article 型のフィールドを Idの型と Dataの型に分ける
  • UserArticle は Idの型と Dataの型をフィールドで持つようにする
  • UserRepository ArticleRepository などのメソッドの型を変える

UserId 型と ArticleId 型の定義

現状、ユーザーのIdと記事のIdが同じく ObjectId になっていて、型名からはどちらの Id か判断できなくなっているので ObjectId をラップする型として UserIdArticleId 型を定義したい。

↓イメージ

#[derive(Clone)]
pub struct UserId {
    inner: ObjectId
}

UserId, ArticleIdSerialize Deserializederive するのでなく、手書きで impl することでシリアライズ結果は変わらないようにする。

↓イメージ

impl Serialize for UserId {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        self.inner.serialize(serializer)
    }
}

既存の User 型, Article 型のフィールドを Idの型と Dataの型に分ける。 UserArticle は Idの型と Dataの型をフィールドで持つようにする

↓既存の User

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct User {
    #[serde(rename = "_id")]
    pub id: ObjectId,
    pub name: UserName, // ユーザー名
    pub display_name: String,
    pub intro: String,
    pub email: String,
    pub show_email: bool,
    pub pw_hash: Vec<u8>, // ハッシュ化されたパスワード
    pub created_at: DateTime<Utc>
}

↓変更後のイメージ

#[derive(Clone, PartialEq, Eq)]
pub struct User {
    id: UserId
    data: UserData,
}
#[optfield(OptionalUserData)]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct UserData {
    pub name: UserName, // ユーザー名
    pub display_name: String,
    pub intro: String,
    pub email: String,
    pub show_email: bool,
    pub pw_hash: Vec<u8>, // ハッシュ化されたパスワード
    pub created_at: DateTime<Utc>
}

新しい UserSerialize , Deserialize も手書き impl して、シリアライズ結果は変わらないようにしても良いと思うし、面倒くさければ普通に derive しても良いと思う。

#[optfield(OptionalUserData)]optfieldクレート のフィールドを全て Option でラップした型を作るマクロ。 OptionalUserData の用途は後述。

UserData という名前は微妙かも知れない。(Data という言葉の意味が広すぎるような気もするので)

AritcleData についての説明は、 UserData と同様なので割愛。

UserRepository ArticleRepository などのメソッドの型を変える

現状の UserRepository.add_user .update_user の型

async fn add_user(&self, name: String, display_name: String, intro: String, email: String, show_email: bool, pw_hash: Vec<u8>) -> Result<User, UserServiceError>;

async fn update_user(&self, id: ObjectId, name: Option<String>, display_name: Option<String>, intro: Option<String>, email: Option<String>, show_email: Option<bool>, pw_hash: Option<Vec<u8>>) -> Result<User, UserServiceError>;

これを、こんな感じに変えたい(イメージ)。

async fn add_user(&self, data: UserData) -> Result<User, UserService>;
async fn update_user(&self, user_id: UserId, data: OptionalUserData) -> Result<User, UserServiceError>;

関数の引数の数が減ってシンプルになるし、抽象度が高くなる。

また現状だと、これらのメソッドは User 型を返しているが、データは手元にあるので User 型を返す必要はないかも。
こんな感じにしても良いかも知れない。

async fn add_user(&self, data: UserData) -> Result<UserId, UserService>;
async fn update_user(&self, user_id: UserId, data: OptionalUserData) -> Result<(), UserServiceError>;

ArticleRepository などのトレイトのメソッドについても、同様なので割愛。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions