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


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

# 先行実装

* 巨人の肩に乗るため、すでにこれについての先行実装を調べました。
* あまりヒットするものはなかったものの、以下の記事が非常に良かったです。
* [DDDのパターンをRustで表現する ~ Value Object編 ~ - CADDi Tech Blog](https://caddi.tech/archives/1373)
  * [ddd-in-rust/chapter02_value_object/src at master · kuwana-kb/ddd-in-rust](https://github.com/kuwana-kb/ddd-in-rust/tree/master/chapter02_value_object/src)

# 実装パターン
* 以下の部分について考えてみたことを列挙してみます。
* 例としては`User`を題材にします。

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

```rs
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`はないので`struct`と`impl`をつかっています。
* また(1)`struct`のほうでは`String`で受け取って、(2)`new`では`&str`で受け取っています。
  * (1)にしているのは、文字列の操作で`String`にのほうがなんやかんや柔軟に使えるから、という理由です。
  * (2)にしているのは`new`をする際に毎回`"text".to_string()`と書くのがダルいからです。

### 問題点

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

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

```rs
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");
}
```
* `new`が`None`か`Some(T)`で返却されることによってValueObject生成の成否を検知できるようになった。
* 生成後、判定を正しく行う場合、`match`を使うか、`unwrap`や`unwrap_or`などを使うか、場合によっては`?`を使うのもありですね
  * [std::option::Option - Rust](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap)
  * [?によるOptionのアンパック - Rust By Example 日本語版](https://doc.rust-jp.rs/rust-by-example-ja/error/option_unwrap/question_mark.html)

### 気になる点

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

## newの返り値に`Result<Self, E>`をつかう
```rs
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が不正なフォーマットです")
    }
}
```

* 今回は使ってないですが、errorハンドリング周りはは現在`thiserror`でErrorを自作して、`anyhow`で早期リターンをかくと良いそうです。
  * [Rustのエラー処理 - Qiita](https://qiita.com/fujitayy/items/cafe661415b6aa33d884#202007%E7%8F%BE%E5%9C%A8%E3%81%AE%E7%8A%B6%E6%B3%81%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)　
  * [dtolnay/thiserror: derive(Error) for struct and enum error types](https://github.com/dtolnay/thiserror)
  * [dtolnay/anyhow: Flexible concrete Error type built on std: :Error](https://github.com/dtolnay/anyhow)
  * 実際のコード例
      * [ddd-in-rust/lib.rs at master · kuwana-kb/ddd-in-rust](https://github.com/kuwana-kb/ddd-in-rust/blob/master/common/src/lib.rs)
      * https://github.com/kuwana-kb/ddd-in-rust/blob/master/chapter02_value_object/src/a3_all_vo.rs#L38 

### 気になる点

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

# まとめ

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

owari