LoginSignup
6
1

More than 3 years have passed since last update.

RustのValueObjectの生成の方法を色々考えた

Last updated at Posted at 2020-12-13

はじめに

LIFULLその2 Advent Calendar 2020 - Qiitaの12日目の記事になります。
RustでClean ArchitectureでWeb Applicationを作ろうとしましたが、投稿に遅刻しているので(13日に書いてる)妥協してValueObjectを使うにあたって調査したことを記事にしようと思います汗
記事を早く作るため箇条書きを多用してます。

先行実装

実装パターン

  • 以下の部分について考えてみたことを列挙してみます。
  • 例としてはUserを題材にします。

何も考えずとりあえず箱をつくる

pub struct User {
    name: String,
    mail: String,
}

impl User {
    pub fn new(name: &str, mail: &str) -> Self {
        return Self { name: name.to_string(), mail: mail.to_string() };
    }
}
#[test]
fn new_test() {
    let user = User::new("ユーザ名", "hoge@example.com");
    assert!(user.name == "ユーザ名");
    assert!(user.mail == "hoge@example.com");
}

  • classはないのでstructimplをつかっています。
  • また(1)structのほうではStringで受け取って、(2)newでは&strで受け取っています。
    • (1)にしているのは、文字列の操作でStringにのほうがなんやかんや柔軟に使えるから、という理由です。
    • (2)にしているのはnewをする際に毎回"text".to_string()と書くのがダルいからです。

問題点

  • このままだと、validationをどう書けばいいかわからないです。
  • emailが不正な値だったらnewの中でその処理書きたいです。ただし、書いたとしてその中でpanicを起こさせることしかできないです。
  • 正しい値ではなかったら、何かしらの手段でApplicationにエラーを渡したいです。

newの返り値にOption<Self>をつかう

pub struct User {
    name: String,
    mail: String,
}

impl User {
    pub fn new(name: &str, mail: &str) -> Option<Self> {
        // from https://qiita.com/sakuro/items/1eaa307609ceaaf51123
        let re = Regex::new(r#"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$"#)
            .unwrap();
        // validation for params
        if name.len() <= 0 || name.len() >= 30 || !re.is_match(mail)  {
            return None;
        }
        return Some(Self { name: name.to_string(), mail: mail.to_string() });
    }
}
#[test]
fn new_test() {
    let op_user = User::new("ユーザ名", "hoge@example.com");
    assert!(op_user != None);
    let user = op_user.unwrap();
    assert!(user.name == "ユーザ名");
    assert!(user.mail == "hoge@example.com");
}

気になる点

  • 毎回生成が正しいか、生成が成功したか判定をしないといけないため、ValueObjectを大量に生成する場合、のいいアイディアがないです。
    • trait ValueObjectを作っていい感じにまとめて生成とか考えようとしましたが、知識不足も相まってパンクしました。
    • ただし必要な処理だと思うので、大きなデメリットとは思いません。
  • Noneが渡されたとき、何が間違ってNoneが返されたのかわからない

newの返り値にResult<Self, E>をつかう

use regex::Regex;

pub struct MyError {
    message: String,
}

impl MyError {
    pub fn new(name: &str) -> Self {
        return Self { message: name.to_string() };
    }
}

pub struct MyUser {
    name: String,
    mail: String,
}

impl MyUser {
    // MyErrorは自分で定義したものと読み替えてください
    pub fn new(name: &str, mail: &str) -> Result<Self, MyError> {
        // from https://qiita.com/sakuro/items/1eaa307609ceaaf51123
        let re = Regex::new(r#"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$"#)
            .unwrap();
        // validation for params
        if name.len() <= 0 || name.len() >= 30 {
            return Err(MyError::new("名前は0文字以上30文字以下である必要があります。"));
        }
        if !re.is_match(mail) {
            return Err(MyError::new("emailが不正なフォーマットです"));
        }
        return Ok(Self {
            name: name.to_string(),
            mail: mail.to_string(),
        });
    }
}

#[test]
fn test() {
    let usr1 = MyUser::new("ユーザ名", "hoge@example.com");
    assert!(usr1.is_ok());
    let user = usr1.ok().unwrap();
    assert_eq!(user.name, "ユーザ名");
    assert_eq!(user.mail, "hoge@example.com");
    let usr2 = MyUser::new("ユーザ名", "hoge/example.com");
    assert!(usr2.is_err());
    if let Err(err) = usr2 {
        assert_eq!(err.message, "emailが不正なフォーマットです")
    }
}

気になる点

  • Optionを使う場合も同様に書きましたが、大量にValueObjectを生成する際に工夫しないとコードがきたなくなりそうです。
  • エラーメッセージがしっかり取れるところはいいですね。

まとめ

  • 少ないですが、一旦ここらへん終わります。
  • 私見ですが、今列挙した中ではエラー処理をしっかり書くならResultで、成否のエラーをひとまとめに扱う、とかならOptionでいいのかな、とおもったりしました。
    • ただし、Rustのお作法として、もっといい書き方ありそうな気もするので自信はないです。
    • サクッと作るならOption、しっかりつくるならResultですかね。
  • 余談ですが、Rustの標準に用意されている関数が非常に多くて驚きました。
    • ドキュメントに記載されているまだ試していない関数などもあるので、色々試行錯誤したいです。

owari

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1