├── .gitignore ├── Cargo.toml ├── README.md ├── chapter02_value_object ├── Cargo.toml └── src │ ├── a1_simple_vo.rs │ ├── a2_derive_vo.rs │ ├── a3_all_vo.rs │ ├── a4_vo_with_behavior.rs │ ├── a5_vo_with_phantom.rs │ ├── lib.rs │ └── model_number.rs ├── chapter03_entity ├── Cargo.toml └── src │ ├── a1_simple_user.rs │ ├── a2_1_mutable_entity.rs │ ├── a2_2_mutable_entity.rs │ ├── a3_identify_entity.rs │ └── lib.rs ├── chapter04_domain_service ├── Cargo.toml └── src │ ├── a1_domain_service.rs │ ├── a2_distribution.rs │ └── lib.rs ├── chapter05_repository ├── Cargo.toml └── src │ ├── domain.rs │ ├── lib.rs │ ├── mock_repository.rs │ ├── orm_repository.rs │ ├── schema.rs │ └── simple_repository.rs ├── chapter06_application_service ├── Cargo.toml └── src │ ├── domain.rs │ ├── lib.rs │ └── usecase.rs ├── chapter08_sample_application ├── Cargo.toml └── src │ ├── app │ ├── mock_server.rs │ └── rdb_server.rs │ ├── domain.rs │ ├── domain │ ├── entity.rs │ ├── entity │ │ └── user.rs │ ├── repository.rs │ ├── repository │ │ └── user.rs │ ├── service.rs │ ├── service │ │ └── user.rs │ ├── value_object.rs │ └── value_object │ │ ├── mail_address.rs │ │ ├── name.rs │ │ └── user_id.rs │ ├── infrastructure.rs │ ├── infrastructure │ ├── api.rs │ ├── api │ │ ├── mock_api.rs │ │ └── rdb_api.rs │ ├── datastore.rs │ ├── datastore │ │ ├── dto.rs │ │ ├── mock_context.rs │ │ ├── pg.rs │ │ └── rdb_context.rs │ ├── server.rs │ └── server │ │ ├── filter.rs │ │ ├── handler.rs │ │ └── handler │ │ └── user.rs │ ├── lib.rs │ ├── usecase.rs │ └── usecase │ ├── dto.rs │ ├── dto │ └── user.rs │ ├── input.rs │ ├── input │ └── user.rs │ └── user.rs ├── chapter09_factory ├── Cargo.toml └── src │ ├── domain.rs │ ├── domain │ ├── entity.rs │ ├── entity │ │ ├── user.rs │ │ └── user_factory.rs │ ├── repository.rs │ ├── repository │ │ └── user.rs │ ├── value_object.rs │ └── value_object │ │ ├── name.rs │ │ └── user_id.rs │ ├── infrastructure.rs │ ├── infrastructure │ ├── api.rs │ ├── api │ │ ├── mock_api.rs │ │ └── rdb_api.rs │ ├── datastore.rs │ └── datastore │ │ ├── mock_context.rs │ │ ├── pg.rs │ │ └── rdb_context.rs │ ├── lib.rs │ ├── usecase.rs │ └── usecase │ ├── input.rs │ └── user.rs └── common ├── Cargo.toml └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "./chapter02_value_object/", 4 | "./chapter03_entity/", 5 | "./chapter04_domain_service/", 6 | "./chapter05_repository/", 7 | "./chapter06_application_service/", 8 | "./chapter08_sample_application/", 9 | "./chapter09_factory/", 10 | ] 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 概要 2 | ddd-in-rustは、DDDの実装パターンをRustで表現すること試みたリポジトリです。 3 | 4 | DDDの実装パターンは、「[ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本](https://www.amazon.co.jp/dp/B082WXZVPC/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)」(著: 成瀬 允宣氏)という書籍のサンプルコードをベースにRustで書いています。(一部、書籍には出ていないパターンも試しています) 5 | 6 | # 実装内容 7 | DDDの実装パターンをRustのコードで表現しています。 8 | ディレクトリとしては、「ドメイン駆動設計入門」のチャプター単位で分けています。 9 | 各ディレクトリは独立したcrateになっていて、個別にコンパイル可能です。 10 | 11 | # 進捗 12 | ## Done 13 | * Entity 14 | * Value Object 15 | * Domain Service 16 | * Application Service 17 | * Factory 18 | 19 | ## WIP 20 | * Dependency Injection 21 | * Cake pattern 22 | * Repository 23 | 24 | ## TODO 25 | * Aggregate 26 | * Specification 27 | 28 | # サンプルアプリケーション 29 | `chapter08_sample_application`でWebAPIサーバを実装しています。 30 | rusのエコシステム`cargo`を導入していればすぐに起動することができます。 31 | 32 | 33 | ## 起動 34 | ```shell 35 | $ cargo run --bin mock_server 36 | ``` 37 | ## サンプルリクエスト 38 | 39 | ### CreateUser 40 | ```shell 41 | $ curl -X PUT -H 'Content-Type:application/json' -D - localhost:8080/users/ -d '{"name": "kuwana-kb", "mail_address": "kuwana-kb@hoge.com"}' 42 | 43 | HTTP/1.1 200 OK 44 | content-type: text/plain; charset=utf-8 45 | content-length: 7 46 | date: Sun, 08 Mar 2020 16:28:48 GMT 47 | 48 | success 49 | ``` 50 | 51 | ### GetUserByName 52 | ```shell 53 | $ curl -X GET -H 'Content-Type:application/json' -D - localhost:8080/users/ -d '"kuwana-kb"' 54 | 55 | HTTP/1.1 200 OK 56 | content-type: application/json 57 | content-length: 54 58 | date: Sun, 08 Mar 2020 16:29:01 GMT 59 | 60 | {"id":"01E2XFMGYRG35405W523R0PYYZ","name":"kuwana-kb"} 61 | ``` 62 | -------------------------------------------------------------------------------- /chapter02_value_object/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chapter02_value_object" 3 | version = "0.1.0" 4 | authors = ["kuwana-kb <41671293+kuwana-kb@users.noreply.github.com>"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | anyhow = "1.0.28" 11 | derive-getters = "0.1.0" 12 | derive-new = "0.5.8" 13 | regex = "1.3.7" 14 | rust_decimal = "1.4.1" 15 | 16 | common = {path = "../common"} 17 | -------------------------------------------------------------------------------- /chapter02_value_object/src/a1_simple_vo.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | /// 氏名 4 | // このケースではなるべくderiveを使わずに直接実装を書いている 5 | // そのためコードが長め 6 | // また、フィールド(first_name, last_name)にはプリミティブな型を用いていて制約を設けていない 7 | #[derive(Clone, Debug)] 8 | pub struct FullName { 9 | first_name: String, 10 | last_name: String, 11 | } 12 | 13 | impl FullName { 14 | pub fn new(first_name: &str, last_name: &str) -> Self { 15 | Self { 16 | first_name: first_name.to_string(), 17 | last_name: last_name.to_string(), 18 | } 19 | } 20 | 21 | pub fn first_name(&self) -> String { 22 | self.first_name.clone() 23 | } 24 | 25 | pub fn last_name(&self) -> String { 26 | self.last_name.clone() 27 | } 28 | } 29 | 30 | // trait PartialEqは半同値関係の性質を表す 31 | // PartialEqを実装することで「==」演算子による比較が可能になる 32 | impl PartialEq for FullName { 33 | fn eq(&self, other: &Self) -> bool { 34 | self.first_name() == other.first_name() && self.last_name() == other.last_name() 35 | } 36 | } 37 | 38 | #[test] 39 | fn test_full_name() { 40 | let name1 = FullName::new("taro", "tanaka"); 41 | let name1_copied = name1.clone(); 42 | let name2 = FullName::new("jiro", "suzuki"); 43 | 44 | // name1とそのコピーの比較 45 | assert_eq!(name1, name1_copied); // Ok 46 | // name1とnameの比較 47 | assert_ne!(name1, name2); // Ok 48 | } 49 | -------------------------------------------------------------------------------- /chapter02_value_object/src/a2_derive_vo.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use derive_getters::Getters; 4 | 5 | /// 氏名 6 | // このケースではなるべくderiveを使い、コードの記述量を抑えている 7 | // Getters, PartialEq, Eqによって`a1`で書いたgetterメソッドと比較の実装を省略している 8 | // Getters: フィールドのgetterメソッドを生やすderiveマクロ 9 | #[derive(Clone, Debug, Getters, PartialEq, Eq)] 10 | pub struct FullName { 11 | first_name: String, 12 | last_name: String, 13 | } 14 | 15 | impl FullName { 16 | fn new(first_name: &str, last_name: &str) -> Self { 17 | Self { 18 | first_name: first_name.to_string(), 19 | last_name: last_name.to_string(), 20 | } 21 | } 22 | } 23 | 24 | #[test] 25 | fn test_full_name() { 26 | let name1 = FullName::new("taro", "tanaka"); 27 | let name1_copied = name1.clone(); 28 | let name2 = FullName::new("jiro", "suzuki"); 29 | 30 | let result1 = name1 == name1_copied; 31 | let result2 = name1 == name2; 32 | 33 | println!("{}", result1); 34 | println!("{}", result2); 35 | } 36 | -------------------------------------------------------------------------------- /chapter02_value_object/src/a3_all_vo.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use derive_getters::Getters; 4 | use regex::Regex; 5 | use std::str::FromStr; 6 | 7 | use common::MyError; 8 | 9 | /// 氏名 10 | // このケースでは、プリミティブだったフィールドに対して、独自型(Name)を定義している 11 | // 独自型に対して制約を与えることで、「その型である = 制約を満たした値である」ことが保証される 12 | #[derive(Clone, Debug, Getters, PartialEq, Eq)] 13 | pub struct FullName { 14 | first_name: Name, 15 | last_name: Name, 16 | } 17 | 18 | impl FullName { 19 | pub fn new(first_name: Name, last_name: Name) -> Self { 20 | Self { 21 | first_name, 22 | last_name, 23 | } 24 | } 25 | } 26 | 27 | #[derive(Clone, Debug, PartialEq, Eq)] 28 | pub struct Name(String); 29 | 30 | impl FromStr for Name { 31 | type Err = anyhow::Error; 32 | 33 | fn from_str(s: &str) -> Result { 34 | let re = Regex::new(r#"^[a-zA-Z]+$"#).unwrap(); 35 | if re.is_match(s) { 36 | Ok(Name(s.to_string())) 37 | } else { 38 | bail!(MyError::type_error("許可されていない文字が使われています")) 39 | } 40 | } 41 | } 42 | 43 | #[test] 44 | fn show_full_name() { 45 | let first_name = "taro".parse().unwrap(); 46 | let last_name = "tanaka".parse().unwrap(); 47 | // この時点でfirst_name, last_nameは型の制約によりアルファベットであることが保証されている 48 | let full_name = FullName::new(first_name, last_name); 49 | 50 | println!("{:?}", full_name); 51 | } 52 | 53 | #[test] 54 | fn test_parse_name() { 55 | let valid_name = "taro".parse::(); 56 | let invalid_name_with_num = "taro123".parse::(); 57 | let invalid_name_with_jpn = "太郎".parse::(); 58 | assert!(valid_name.is_ok()); 59 | assert!(invalid_name_with_num.is_err()); 60 | assert!(invalid_name_with_jpn.is_err()); 61 | } 62 | -------------------------------------------------------------------------------- /chapter02_value_object/src/a4_vo_with_behavior.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Add; 2 | 3 | use derive_new::new; 4 | use rust_decimal::Decimal; 5 | 6 | // 振る舞いを持つVO 7 | // 具体的には通貨単位が一致した場合に限り加算が可能 8 | // 9 | // このケースでは通貨単位をフィールドの一部として定義している 10 | // 通貨単位をフィールドではなく、型として表現するケースは`a5_vo_with_phantom`参照 11 | #[derive(Clone, Debug, new, Eq, PartialEq)] 12 | struct Money { 13 | amount: Decimal, 14 | currency: String, 15 | } 16 | 17 | // Add traitは「+」演算子による加算を表現する 18 | impl Add for Money { 19 | type Output = Money; 20 | 21 | fn add(self, other: Money) -> Self::Output { 22 | // 通貨単位のチェック 23 | // 通貨単位が一致しない場合はpanicを起こす 24 | // traitのシグネチャ上、Result型として返せないのでこれは仕方ないはず... 25 | // その意味で、通貨単位を型として表現することでコンパイル時に検査できる方が嬉しいと思われる 26 | if self.currency != other.currency { 27 | panic!("Invalid currency") 28 | } 29 | let new_amount = self.amount + other.amount; 30 | Money::new(new_amount, self.currency) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /chapter02_value_object/src/a5_vo_with_phantom.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::marker::PhantomData; 4 | use std::ops::Add; 5 | 6 | use rust_decimal::Decimal; 7 | 8 | // 振る舞いを持つVO 9 | // 具体的には通貨単位が一致した場合に限り加算が可能 10 | // 11 | // このケースでは通貨単位を型として表現している 12 | // MoneyのTで通貨単位を表すようにする 13 | // ここで嬉しいのは、誤った通貨単位同士の加算をコンパイル時に検査できること 14 | // Tはただのラベルとして扱いたいだけだが消費しないと怒られるので、std::marker::PhantdomDataを用いる 15 | // 参考: https://keens.github.io/blog/2018/12/15/rustdetsuyomenikatawotsukerupart_1__new_type_pattern/ 16 | #[derive(Clone, Debug, PartialEq, Eq)] 17 | pub struct Money { 18 | amount: Decimal, 19 | currency: PhantomData, 20 | } 21 | 22 | impl Money { 23 | fn new(amount: Decimal) -> Self { 24 | Self { 25 | amount, 26 | currency: PhantomData::, 27 | } 28 | } 29 | } 30 | 31 | impl Add for Money { 32 | type Output = Money; 33 | 34 | fn add(self, other: Money) -> Self::Output { 35 | Self::new(self.amount + other.amount) 36 | } 37 | } 38 | 39 | #[derive(Clone, Debug, PartialEq, Eq)] 40 | pub enum JPY {} 41 | 42 | #[derive(Clone, Debug, PartialEq, Eq)] 43 | pub enum USD {} 44 | 45 | #[test] 46 | fn test_phantom_money() { 47 | let jpy_1 = Money::::new(Decimal::new(1, 0)); 48 | let jpy_2 = Money::::new(Decimal::new(2, 0)); 49 | 50 | let _usd = Money::::new(Decimal::new(3, 0)); 51 | 52 | let result = jpy_1 + jpy_2; // コンパイルOk 53 | assert_eq!(result, Money::::new(Decimal::new(3, 0))); 54 | // let invalid_result = jpy_1 + usd; //コンパイルエラー 55 | } 56 | -------------------------------------------------------------------------------- /chapter02_value_object/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate anyhow; 3 | 4 | mod a1_simple_vo; 5 | mod a2_derive_vo; 6 | mod a3_all_vo; 7 | mod a4_vo_with_behavior; 8 | mod a5_vo_with_phantom; 9 | -------------------------------------------------------------------------------- /chapter02_value_object/src/model_number.rs: -------------------------------------------------------------------------------- 1 | /// 製品番号 2 | // 製品番号がどのように構成されているかをコードとしてあらわす 3 | struct ModelNumber { 4 | product_code: String, 5 | branch: String, 6 | lot: String, 7 | } 8 | 9 | impl ModelNumber { 10 | fn new(product_code: &str, branch: &str, lot: &str) -> Self { 11 | Self { 12 | product_code, 13 | branch, 14 | lot, 15 | } 16 | } 17 | } 18 | 19 | // Display trait を実装することで、文字列として出力する際の挙動を制御できる 20 | // 以下の場合は、「{product_code}-{branch}-{lot}」で出力する 21 | impl fmt::Display for ModelNumber { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | write!(f, "{}-{}-{}", self.product_code, self.branch, self.lot) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /chapter03_entity/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chapter03_entity" 3 | version = "0.1.0" 4 | authors = ["kuwana-kb <41671293+kuwana-kb@users.noreply.github.com>"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | anyhow = "1.0.28" 11 | derive-getters = "0.1.0" 12 | derive_more = "0.99.5" 13 | 14 | common = {path = "../common/"} 15 | -------------------------------------------------------------------------------- /chapter03_entity/src/a1_simple_user.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use common::MyError; 4 | 5 | // Userモデルを表現したが、可変性と同一性を持たない状態 6 | #[derive(Clone, Debug)] 7 | pub struct User { 8 | name: String, 9 | } 10 | 11 | impl User { 12 | // ユーザー名は不変なため、後から変更することはできない 13 | pub fn new(name: &str) -> Result { 14 | if name.chars().count() < 3 { 15 | return Err(MyError::type_error("ユーザー名は3文字以上です")); 16 | } 17 | Ok(Self { 18 | name: name.to_string(), 19 | }) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /chapter03_entity/src/a2_1_mutable_entity.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use common::MyError; 4 | 5 | // Userモデルに対して可変性を与えた 6 | #[derive(Clone, Debug)] 7 | pub struct User { 8 | name: String, 9 | } 10 | 11 | impl User { 12 | pub fn new(name: &str) -> Result { 13 | let mut user = Self { 14 | name: Default::default(), 15 | }; 16 | user.change_name(name)?; 17 | Ok(user) 18 | } 19 | 20 | // ふるまいを通じて属性を変更する 21 | // 変更ロジックはメソッド内に閉じ込めている 22 | // (個人的にはName型を定義して引数の時点で値を保証する方が好き) 23 | pub fn change_name(&mut self, name: &str) -> Result<(), MyError> { 24 | if name.chars().count() < 3 { 25 | return Err(MyError::type_error("ユーザー名は3文字以上です")); 26 | } 27 | self.name = name.to_string(); 28 | Ok(()) 29 | } 30 | 31 | pub fn name(&self) -> String { 32 | self.name.clone() 33 | } 34 | } 35 | 36 | #[test] 37 | fn test_change_name_success() { 38 | let name_1 = "Hoge"; 39 | let name_2 = "Fuga"; 40 | let user_before_result = User::new(name_1); 41 | // new() が成功しているかをチェック 42 | assert!(user_before_result.is_ok()); 43 | 44 | let user_before = user_before_result.unwrap(); 45 | let mut user_after = user_before.clone(); 46 | user_after.change_name(name_2).unwrap(); 47 | 48 | // beforeとafterで名前が異なるかを検証 49 | assert_eq!(user_before.name(), name_1.to_string()); // Ok 50 | assert_eq!(user_after.name(), name_2.to_string()); // Ok 51 | } 52 | -------------------------------------------------------------------------------- /chapter03_entity/src/a2_2_mutable_entity.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use common::MyError; 3 | 4 | // Userモデルに対して可変性を与えた 5 | #[derive(Clone, Debug)] 6 | pub struct User { 7 | name: Name, 8 | } 9 | 10 | impl User { 11 | pub fn new(name: Name) -> Self { 12 | Self { name: name } 13 | } 14 | 15 | // バリデーションのロジックは Name 型に移譲している 16 | pub fn change_name(&mut self, name: Name) { 17 | self.name = name; 18 | } 19 | 20 | pub fn name(&self) -> Name { 21 | self.name.clone() 22 | } 23 | } 24 | 25 | // Name 型を新たに Value Object として定義した 26 | #[derive(Clone, Debug)] 27 | pub struct Name(String); 28 | 29 | impl Name { 30 | pub fn new(s: &str) -> Result { 31 | if s.chars().count() < 3 { 32 | bail!(MyError::type_error("ユーザー名は3文字以上です")) 33 | } 34 | Ok(Name(s.to_string())) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /chapter03_entity/src/a3_identify_entity.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use anyhow::Result; 4 | use common::MyError; 5 | use derive_getters::Getters; 6 | 7 | /// Userモデルに対して可変性と同一性を与えた 8 | #[derive(Clone, Debug, Getters)] 9 | pub struct User { 10 | id: UserId, 11 | name: Name, 12 | } 13 | 14 | impl User { 15 | pub fn new(id: UserId, name: Name) -> Self { 16 | Self { id, name } 17 | } 18 | 19 | // nameフィールドは可変性を持つ 20 | pub fn change_username(&mut self, name: Name) { 21 | self.name = name; 22 | } 23 | } 24 | 25 | // Userは識別子による比較が可能 26 | impl PartialEq for User { 27 | fn eq(&self, other: &Self) -> bool { 28 | self.id == other.id 29 | } 30 | } 31 | 32 | // trait Eqは同値関係の性質を表す 33 | impl Eq for User {} 34 | 35 | /// ユーザーID 36 | #[derive(Clone, Debug, PartialEq, Eq)] 37 | pub struct UserId(String); 38 | 39 | impl UserId { 40 | pub fn new(s: &str) -> Self { 41 | Self(s.to_string()) 42 | } 43 | } 44 | 45 | /// 名前 46 | #[derive(Clone, Debug)] 47 | pub struct Name(String); 48 | 49 | impl Name { 50 | pub fn new(s: &str) -> Result { 51 | if s.chars().count() < 3 { 52 | bail!(MyError::type_error("ユーザー名は3文字以上です")) 53 | } 54 | Ok(Name(s.to_string())) 55 | } 56 | } 57 | 58 | #[test] 59 | fn test_user_eq() { 60 | let user_before = User::new(UserId::new("DummyId1"), Name::new("Hoge").unwrap()); 61 | let mut user_after = user_before.clone(); 62 | user_after.change_username(Name::new("Fuga").unwrap()); 63 | 64 | // User の属性を変更しても、同一性は同じまま 65 | // PartialEq trait を実装したので User を比較可能 66 | assert_eq!(user_before, user_after); // Ok 67 | } 68 | -------------------------------------------------------------------------------- /chapter03_entity/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate anyhow; 3 | 4 | mod a1_simple_user; 5 | mod a2_1_mutable_entity; 6 | mod a2_2_mutable_entity; 7 | mod a3_identify_entity; 8 | 9 | pub use a3_identify_entity::*; 10 | -------------------------------------------------------------------------------- /chapter04_domain_service/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chapter04_domain_service" 3 | version = "0.1.0" 4 | authors = ["kuwana-kb <41671293+kuwana-kb@users.noreply.github.com>"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | derive-new = "0.5.8" 11 | 12 | chapter03_entity = {path = "../chapter03_entity"} 13 | -------------------------------------------------------------------------------- /chapter04_domain_service/src/a1_domain_service.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use chapter03_entity::User; 4 | use derive_new::new; 5 | 6 | /// UserServiceはUserが持つと不自然なふるまいを受け持つ 7 | #[derive(Debug, new)] 8 | pub struct UserService {} 9 | 10 | impl UserService { 11 | // ユーザー名が重複するかを確認する 12 | pub fn exists(&self, _user: User) -> bool { 13 | // User.usernameの重複をチェックするような実装 14 | // Infra周りの実装は冗長でなのでRepositoryで扱いたい 15 | unimplemented!() 16 | } 17 | 18 | // 「不自然なふるまい」は実装者の考え次第では、あらゆるふるまいに対して適用できてしまう 19 | // あらゆるふるまいがDomain Service上で表現されると、Domainオブジェクトの表現が削がれて、ドメインモデル貧血症を招く 20 | // 21 | // 例えば、以下のふるまいはUser上に実装する方がよい 22 | // pub fn change_name(&mut user: User, name: Name) { 23 | // user.name = name; 24 | // } 25 | } 26 | 27 | #[test] 28 | fn check_user() { 29 | use chapter03_entity::{Name, UserId}; 30 | 31 | // UserServiceの使い方を表すサンプル 32 | let user_service = UserService::new(); 33 | 34 | let user = User::new(UserId::new("id"), Name::new("Hoge").unwrap()); 35 | // let duplicate_check_result = user_service.exists(user); // 実装がないためpanicする 36 | } 37 | -------------------------------------------------------------------------------- /chapter04_domain_service/src/a2_distribution.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | /// 物流拠点 4 | pub struct PhysicalDistributionBase {} 5 | 6 | impl PhysicalDistributionBase { 7 | /// 出庫 8 | pub fn ship(&self, _baggage: Baggage) -> Baggage { 9 | unimplemented!() 10 | } 11 | /// 入庫 12 | pub fn receive(&self, _baggage: Baggage) { 13 | unimplemented!() 14 | } 15 | 16 | // 輸送(出庫 -> 入庫) 17 | // 物流拠点が輸送のふるまいを持つのは不自然 18 | // 入出庫の記録等も物流拠点が担うことになりそう 19 | // pub fn transport(&self, to: &mut PhysicalDistributionBase, baggage: Baggage) { 20 | // let shipped_baggage = self.ship(baggage); 21 | // to.receive(shipped_baggage); 22 | // } 23 | } 24 | 25 | /// 輸送 26 | // 輸送の概念をDomainServiceとして切り出す 27 | pub struct TransportService {} 28 | 29 | impl TransportService { 30 | pub fn transport( 31 | from: PhysicalDistributionBase, 32 | to: PhysicalDistributionBase, 33 | baggage: Baggage, 34 | ) { 35 | let shipped_baggage = from.ship(baggage); 36 | to.receive(shipped_baggage); 37 | 38 | // 配送の記録等を行う.. 39 | } 40 | } 41 | 42 | pub struct Baggage {} 43 | -------------------------------------------------------------------------------- /chapter04_domain_service/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod a1_domain_service; 2 | mod a2_distribution; 3 | -------------------------------------------------------------------------------- /chapter05_repository/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chapter05_repository" 3 | version = "0.1.0" 4 | authors = ["kuwana-kb <41671293+kuwana-kb@users.noreply.github.com>"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | anyhow = "1.0.28" 11 | derive-getters = "0.1.0" 12 | derive_more = "0.99.5" 13 | derive-new = "0.5.8" 14 | postgres = "0.17.2" 15 | 16 | common = { path = "../common"} 17 | -------------------------------------------------------------------------------- /chapter05_repository/src/domain.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::str::FromStr; 4 | 5 | use anyhow::Result; 6 | use derive_getters::Getters; 7 | use derive_more::Display; 8 | use derive_new::new; 9 | 10 | use common::MyError; 11 | 12 | // ------------------------- 13 | // Repository 14 | // ------------------------- 15 | 16 | // Repositoryはふるまいを定義するためtraitで実装 17 | pub trait IUserRepository: Clone { 18 | // 処理が失敗する可能性があるため、返り値はResult型で定義 19 | fn save(&self, user: User) -> Result<()>; 20 | 21 | // 処理が失敗する可能性がある&Userが存在しない可能性があるため、返り値はResult>型で定義 22 | fn find(&self, username: Name) -> Result>; 23 | 24 | // 永続化と関係がない&実装次第で動作が変わる危険性があるので、 25 | // 以下のようなメソッドはリポジトリとしては不適切 26 | // pub fn exists(exists: User) -> bool; 27 | } 28 | 29 | // ------------------------- 30 | // DomainService 31 | // ------------------------- 32 | 33 | #[derive(Clone, Debug, new, Getters)] 34 | pub struct Program { 35 | repo: Repo, 36 | } 37 | 38 | impl Program 39 | where 40 | Repo: IUserRepository, 41 | { 42 | pub fn create_user(&mut self, username: Name) -> Result<()> { 43 | let user = User::new(username); 44 | 45 | let user_service = UserService::new(self.repo()); 46 | if user_service.exists(user.clone())? { 47 | bail!(MyError::internal_server_error( 48 | "対象のユーザ名は既に存在しています。" 49 | )) 50 | } 51 | self.repo.save(user) 52 | } 53 | } 54 | 55 | #[derive(Clone, Debug, Getters)] 56 | pub struct UserService { 57 | repo: Repo, 58 | } 59 | 60 | // TODO: 内部処理の実装 61 | impl UserService 62 | where 63 | Repo: IUserRepository, 64 | { 65 | pub fn new(repo: &Repo) -> Self { 66 | Self { repo: repo.clone() } 67 | } 68 | 69 | pub fn exists(&self, user: User) -> Result { 70 | let result = self.repo.find(user.name().clone())?; 71 | match result { 72 | Some(_) => Ok(true), 73 | None => Ok(false), 74 | } 75 | } 76 | } 77 | 78 | // ------------------------- 79 | // Entity & Value Object 80 | // ------------------------- 81 | 82 | #[derive(Clone, Debug, Getters)] 83 | pub struct User { 84 | id: UserId, 85 | name: Name, 86 | } 87 | 88 | impl User { 89 | pub fn new(name: Name) -> Self { 90 | Self { 91 | id: UserId::default(), 92 | name, 93 | } 94 | } 95 | 96 | pub fn rebuild(id: UserId, name: Name) -> Self { 97 | Self { id, name } 98 | } 99 | } 100 | 101 | #[derive(Clone, Debug, Display, Hash, PartialEq, Eq)] 102 | pub struct UserId(String); 103 | 104 | impl Default for UserId { 105 | fn default() -> Self { 106 | // TODO: uuid等で自動生成する 107 | UserId("DummyId".to_string()) 108 | } 109 | } 110 | 111 | impl FromStr for UserId { 112 | type Err = anyhow::Error; 113 | 114 | fn from_str(s: &str) -> Result { 115 | // 生成時の制約がある場合、それを満たすようにすること 116 | Ok(UserId(s.to_string())) 117 | } 118 | } 119 | 120 | #[derive(Clone, Debug, Display, PartialEq, Eq)] 121 | pub struct Name(String); 122 | 123 | impl FromStr for Name { 124 | type Err = anyhow::Error; 125 | 126 | fn from_str(s: &str) -> Result { 127 | Ok(Name(s.to_string())) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /chapter05_repository/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate anyhow; 3 | 4 | mod domain; 5 | mod mock_repository; 6 | mod simple_repository; 7 | 8 | pub use domain::*; 9 | -------------------------------------------------------------------------------- /chapter05_repository/src/mock_repository.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::{Arc, Mutex}; 3 | 4 | use anyhow::Result; 5 | use common::MyError; 6 | use derive_new::new; 7 | 8 | use super::{IUserRepository, Name, User, UserId}; 9 | 10 | #[derive(Clone, new)] 11 | pub struct InMemoryUserRepository { 12 | store: Arc>>, 13 | } 14 | 15 | impl IUserRepository for InMemoryUserRepository { 16 | fn save(&self, user: User) -> Result<()> { 17 | let store = self.store.clone(); 18 | let mut store = store 19 | .try_lock() 20 | .map_err(|_| MyError::internal_server_error("failed to try_lock store"))?; 21 | store.insert(user.id().clone(), user); 22 | Ok(()) 23 | } 24 | 25 | fn find(&self, name: Name) -> Result> { 26 | let store = self.store.clone(); 27 | let store = store 28 | .try_lock() 29 | .map_err(|_| MyError::internal_server_error("failed to try_lock store"))?; 30 | let target = store 31 | .values() 32 | .filter(|user| user.name().clone() == name) 33 | .cloned() 34 | .collect::>(); 35 | Ok(target.first().cloned()) 36 | } 37 | } 38 | 39 | #[test] 40 | fn test_mock_repostitory() { 41 | use super::Program; 42 | 43 | let repo = InMemoryUserRepository::new(Arc::new(Mutex::new(HashMap::new()))); 44 | let mut program = Program::new(repo); 45 | program.create_user("Hoge".parse().unwrap()).unwrap(); 46 | let opt_user = program.repo().find("Hoge".parse().unwrap()).unwrap(); 47 | assert!(opt_user.is_some()); 48 | } 49 | -------------------------------------------------------------------------------- /chapter05_repository/src/orm_repository.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use super::{IUserRepository, User, Name, UserId}; 4 | 5 | #[derive(Queryable)] 6 | pub struct UserDataModel { 7 | id: String, 8 | name: String, 9 | } 10 | 11 | // MEMO: contextはConnectionをもたせる形が良いかも 12 | pub struct ORMUserRepository { 13 | ctx: PgPool, 14 | } 15 | 16 | // TODO: Diesel使う形で書く 17 | impl ORMUserRepository { 18 | pub fn new(ctx: PgPool) { 19 | Self { 20 | ctx 21 | } 22 | } 23 | } 24 | 25 | impl IUserRepository for ORMUserRepository { 26 | fn save(&mut self, user: User) -> Result<()> { 27 | unimplemented!() 28 | } 29 | 30 | fn find(&self, name: Name) -> Result> { 31 | unimplemented!() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /chapter05_repository/src/schema.rs: -------------------------------------------------------------------------------- 1 | table! { 2 | users(id) { 3 | id -> Text, 4 | name -> Text, 5 | } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /chapter05_repository/src/simple_repository.rs: -------------------------------------------------------------------------------- 1 | use std::convert::{TryFrom, TryInto}; 2 | 3 | use anyhow::Result; 4 | use derive_new::new; 5 | use postgres::{Client, NoTls}; 6 | 7 | use super::{IUserRepository, Name, User}; 8 | 9 | // ------------------------- 10 | // Infrastucture層 11 | // ------------------------- 12 | 13 | #[derive(Clone, Debug, new)] 14 | pub struct UserRepository {} 15 | 16 | // const CONNECTION_STRING: &str = "host=localhost user=postgres"; 17 | const CONNECTION_STRING: &str = "postgres://postgres:password@localhost:5432/users"; 18 | 19 | impl IUserRepository for UserRepository { 20 | fn save(&self, user: User) -> Result<()> { 21 | // サンプルとしては、DBを操作するための簡易的な実装に留める 22 | // ConnectionPoolをアプリケーションを表すstructに対して持たせて引き回す方がよいはず 23 | let mut client = Client::connect(CONNECTION_STRING, NoTls)?; 24 | 25 | let id = user.id().to_string(); 26 | let name = user.name().to_string(); 27 | client.execute( 28 | // r#"INSERT INTO users (id, name) VALUES ($1, $2)"#, 29 | r#" 30 | INSERT INTO users (id, name) 31 | VALUES ($1, $2) 32 | ON CONFLICT(id) 33 | DO UPDATE SET name = $2; 34 | "#, 35 | &[&id, &name], 36 | )?; 37 | 38 | Ok(()) 39 | } 40 | 41 | fn find(&self, name: Name) -> Result> { 42 | // サンプルとしては、DBを操作するための簡易的な実装に留める 43 | // ConnectionPoolをアプリケーションを表すstructに対して持たせて引き回す方がよいはず 44 | let mut client = Client::connect(CONNECTION_STRING, NoTls)?; 45 | 46 | let name = name.to_string(); 47 | let rows = client.query("SELECT id, name FROM users WHERE name = $1", &[&name])?; 48 | 49 | // ここでは取得結果が0件また1件という前提で処理している 50 | let row = rows.iter().next(); 51 | match row { 52 | Some(row) => { 53 | // 取得した結果をプリミティブな型のUserDtoに格納する 54 | let user_dto = UserDto { 55 | id: row.get(0), 56 | name: row.get(1), 57 | }; 58 | // TryIntoでUser型に変換してから返す 59 | Ok(Some(user_dto.try_into()?)) 60 | } 61 | None => Ok(None), 62 | } 63 | } 64 | } 65 | 66 | // users tableの取得結果を格納するオブジェクト 67 | pub struct UserDto { 68 | id: String, 69 | name: String, 70 | } 71 | 72 | // UserDto型からUser型への変換処理 73 | // 74 | // 失敗の可能性を考慮するならTryFromで実装 75 | // データストアに保存された時点で値が制約に基づいているという前提で、 76 | // 失敗の可能性はないと判断してFromで実装することもできるはず 77 | // ただしその場合、unwrap()を使って失敗するとpanicする点に注意 78 | impl TryFrom for User { 79 | type Error = anyhow::Error; 80 | 81 | fn try_from(v: UserDto) -> Result { 82 | let id = v.id.parse()?; 83 | let name = v.name.parse()?; 84 | Ok(User::rebuild(id, name)) 85 | } 86 | } 87 | 88 | // To run this test: 89 | // cargo test -- --ignored 90 | #[test] 91 | #[ignore] 92 | fn repository_example() { 93 | use super::Program; 94 | 95 | let repo = UserRepository::new(); 96 | let mut program = Program::new(repo); 97 | program.create_user("Taro".parse().unwrap()).unwrap(); // DBインスタンスをたてていないと失敗します。 98 | } 99 | -------------------------------------------------------------------------------- /chapter06_application_service/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chapter06_application_service" 3 | version = "0.1.0" 4 | authors = ["kuwana-kb <41671293+kuwana-kb@users.noreply.github.com>"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | anyhow = "1.0.28" 11 | derive-getters = "0.1.0" 12 | derive_more = "0.99.5" 13 | derive-new = "0.5.8" 14 | ulid = "0.3.1" 15 | 16 | common = {path = "../common"} 17 | -------------------------------------------------------------------------------- /chapter06_application_service/src/domain.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use std::str::FromStr; 3 | 4 | use anyhow::Result; 5 | use common::MyError; 6 | use derive_getters::Getters; 7 | use derive_more::Display; 8 | use ulid::Ulid; 9 | 10 | // ------------------------- 11 | // Entity & Value Object 12 | // ------------------------- 13 | 14 | #[derive(Clone, Debug, Getters)] 15 | pub struct User { 16 | id: UserId, 17 | name: Name, 18 | mail_address: MailAddress, 19 | } 20 | 21 | impl User { 22 | // はじめてインスタンスを生成する際に利用する 23 | pub fn new(name: Name, mail_address: MailAddress) -> Self { 24 | Self { 25 | id: UserId::default(), 26 | name, 27 | mail_address, 28 | } 29 | } 30 | 31 | // インスタンスを再構成する際に利用する 32 | pub fn rebuild(id: UserId, name: Name, mail_address: MailAddress) -> Self { 33 | Self { 34 | id, 35 | name, 36 | mail_address, 37 | } 38 | } 39 | 40 | pub fn change_name(&mut self, name: Name) { 41 | self.name = name; 42 | } 43 | 44 | pub fn change_mail_address(&mut self, mail_address: MailAddress) { 45 | self.mail_address = mail_address; 46 | } 47 | } 48 | 49 | #[derive(Clone, Debug, PartialEq, Eq, Display)] 50 | pub struct UserId(Ulid); 51 | 52 | impl UserId { 53 | fn new(s: &str) -> Result { 54 | Ok(UserId(Ulid::from_string(s).map_err(|_| { 55 | MyError::type_error("failed to parse to user_id") 56 | })?)) 57 | } 58 | } 59 | 60 | impl Default for UserId { 61 | fn default() -> Self { 62 | UserId(Ulid::new()) 63 | } 64 | } 65 | 66 | impl FromStr for UserId { 67 | type Err = anyhow::Error; 68 | 69 | fn from_str(s: &str) -> Result { 70 | Self::new(s) 71 | } 72 | } 73 | 74 | #[derive(Clone, Debug, PartialEq, Eq, Display)] 75 | pub struct Name(String); 76 | 77 | impl FromStr for Name { 78 | type Err = anyhow::Error; 79 | 80 | fn from_str(s: &str) -> Result { 81 | if s.chars().count() < 3 || s.chars().count() > 20 { 82 | bail!(MyError::type_error("ユーザ名は3文字以上、20文字以下です")) 83 | } 84 | Ok(Name(s.to_string())) 85 | } 86 | } 87 | 88 | #[derive(Clone, Debug, PartialEq, Eq, Display)] 89 | pub struct MailAddress(String); 90 | 91 | // ------------------------- 92 | // Domain Service 93 | // ------------------------- 94 | 95 | #[derive(Clone, Debug, Getters)] 96 | pub struct UserService { 97 | repo: Repo, 98 | } 99 | 100 | impl UserService 101 | where 102 | Repo: IUserRepository, 103 | { 104 | pub fn exists(&self, user: &User) -> Result { 105 | let duplicated_user = self.repo().find_by_name(user.clone().name)?; 106 | match duplicated_user { 107 | Some(_) => Ok(true), 108 | None => Ok(false), 109 | } 110 | } 111 | } 112 | 113 | // ------------------------- 114 | // Repository 115 | // ------------------------- 116 | 117 | pub trait IUserRepository { 118 | fn find_by_id(&self, id: UserId) -> Result>; 119 | 120 | fn find_by_name(&self, name: Name) -> Result>; 121 | 122 | fn save(&self, user: User) -> Result<()>; 123 | 124 | fn delete(&self, user: User) -> Result<()>; 125 | } 126 | -------------------------------------------------------------------------------- /chapter06_application_service/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate anyhow; 3 | 4 | mod domain; 5 | mod usecase; 6 | 7 | pub use domain::*; 8 | -------------------------------------------------------------------------------- /chapter06_application_service/src/usecase.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use anyhow::Result; 4 | use common::MyError; 5 | use derive_getters::Getters; 6 | use derive_new::new; 7 | 8 | use super::{IUserRepository, MailAddress, Name, User, UserId, UserService}; 9 | 10 | #[derive(Clone, Debug, Getters)] 11 | pub struct UserApplicationService 12 | where 13 | Repo: IUserRepository, 14 | { 15 | repo: Repo, 16 | user_service: UserService, 17 | } 18 | 19 | // MEMO: ドメインオブジェクトを公開しない前提ならば、 20 | // アプリケーションサービスのもつメソッドの引数は全てプリミティブか型の方がよいかも? 21 | impl UserApplicationService 22 | where 23 | Repo: IUserRepository, 24 | { 25 | pub fn new(repo: Repo, user_service: UserService) -> Self { 26 | Self { repo, user_service } 27 | } 28 | 29 | pub fn register(&self, name: Name, mail_address: MailAddress) -> Result<()> { 30 | let user = User::new(name, mail_address); 31 | if self.user_service().exists(&user)? { 32 | bail!(MyError::internal_server_error("ユーザは既に存在しています")) 33 | } 34 | self.repo().save(user) 35 | } 36 | 37 | // 以下はドメインオブジェクトを直接返す場合の例 38 | // ドメインオブジェクトを公開する = アプリケーションサービスのクライアントが意図せぬ使い方をする危険性を持つ(ダメというわけではない) 39 | // pub fn get(&self, id: UserId) -> Result { 40 | // let user =self.repo().find_by_id(id)?.ok_or_else(|| MyError::internal_server_error("ユーザが見つかりませんでした"))?; 41 | // Ok(user) 42 | // } 43 | 44 | // ドメインオブジェクトを直接返さず、DTOを介す例 45 | pub fn get(&self, id: UserId) -> Result { 46 | let user = self 47 | .repo() 48 | .find_by_id(id)? 49 | .ok_or_else(|| MyError::internal_server_error("ユーザが見つかりませんでした"))?; 50 | Ok(user.into()) 51 | } 52 | 53 | // 更新するパラメータが増えてもシグネチャを変えなくていいように、Command型を定義して引数とする 54 | pub fn update(&self, command: UserUpdateCommand) -> Result<()> { 55 | let mut user = self 56 | .repo() 57 | .find_by_id(command.id().clone())? 58 | .ok_or_else(|| MyError::internal_server_error("ユーザが見つかりませんでした"))?; 59 | 60 | if let Some(name) = command.name() { 61 | user.change_name(name.clone()); 62 | if self.user_service().exists(&user)? { 63 | bail!(MyError::internal_server_error("ユーザは既に存在しています")) 64 | } 65 | } 66 | 67 | if let Some(mail_address) = command.mail_address() { 68 | user.change_mail_address(mail_address.clone()); 69 | } 70 | 71 | self.repo().save(user)?; 72 | Ok(()) 73 | } 74 | 75 | pub fn delete(&self, command: UserDeleteCommand) -> Result<()> { 76 | let target_id = command.id().clone(); 77 | let user = self 78 | .repo() 79 | .find_by_id(target_id)? 80 | .ok_or_else(|| MyError::internal_server_error("ユーザが見つかりませんでした"))?; 81 | self.repo.delete(user)?; 82 | Ok(()) 83 | } 84 | } 85 | 86 | // アプリケーションサービスのクライアントに対して公開するUserのDTO 87 | #[derive(Clone, Debug)] 88 | pub struct UserData { 89 | id: String, 90 | name: String, 91 | } 92 | 93 | // User型からDTOへの変換処理 94 | impl From for UserData { 95 | fn from(v: User) -> Self { 96 | Self { 97 | id: v.id().to_string(), 98 | name: v.name().to_string(), 99 | } 100 | } 101 | } 102 | 103 | // Updateメソッドのパラメータ群。Optionで定義したフィールドは任意の更新項目とする。 104 | #[derive(Clone, Debug, Getters, new)] 105 | pub struct UserUpdateCommand { 106 | id: UserId, 107 | name: Option, 108 | mail_address: Option, 109 | } 110 | 111 | #[derive(Clone, Debug, Getters, new)] 112 | pub struct UserDeleteCommand { 113 | id: UserId, 114 | } 115 | -------------------------------------------------------------------------------- /chapter08_sample_application/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chapter08_sample_application" 3 | version = "0.1.0" 4 | authors = ["kuwana-kb <41671293+kuwana-kb@users.noreply.github.com>"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [[bin]] 10 | name = "mock_server" 11 | path = "src/app/mock_server.rs" 12 | 13 | [[bin]] 14 | name = "rdb_server" 15 | path = "src/app/rdb_server.rs" 16 | 17 | 18 | [dependencies] 19 | anyhow = "1.0.28" 20 | derive-getters = "0.1.0" 21 | derive_more = "0.99.5" 22 | derive-new = "0.5.8" 23 | postgres = "0.17.2" 24 | r2d2_postgres = "0.16.0" 25 | serde = { version = "1.0.106", features = ["derive"]} 26 | tokio = { version = "0.2", features = ["macros"]} 27 | ulid = "0.3.1" 28 | warp = "0.2.2" 29 | 30 | common = { path = "../common"} 31 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/app/mock_server.rs: -------------------------------------------------------------------------------- 1 | use chapter08_sample_application::infrastructure::{users_api, MockApi, MockContext}; 2 | 3 | #[tokio::main] 4 | async fn main() { 5 | let ctx = MockContext::default(); 6 | let api = MockApi::new(ctx); 7 | 8 | warp::serve(users_api(api)) 9 | .run(([127, 0, 0, 1], 8080)) 10 | .await; 11 | } 12 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/app/rdb_server.rs: -------------------------------------------------------------------------------- 1 | use chapter08_sample_application::infrastructure::{users_api, RDBApi, RDBContext}; 2 | 3 | #[tokio::main] 4 | async fn main() { 5 | let ctx = RDBContext::default(); 6 | let api = RDBApi::new(ctx); 7 | 8 | warp::serve(users_api(api)) 9 | .run(([127, 0, 0, 1], 8080)) 10 | .await; 11 | } 12 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/domain.rs: -------------------------------------------------------------------------------- 1 | mod entity; 2 | mod repository; 3 | mod service; 4 | mod value_object; 5 | 6 | pub use entity::*; 7 | pub use repository::*; 8 | pub use service::*; 9 | pub use value_object::*; 10 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/domain/entity.rs: -------------------------------------------------------------------------------- 1 | mod user; 2 | 3 | pub use user::*; 4 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/domain/entity/user.rs: -------------------------------------------------------------------------------- 1 | use derive_getters::Getters; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use super::super::{MailAddress, Name, UserId}; 5 | 6 | #[derive(Clone, Debug, Getters, Serialize, Deserialize)] 7 | pub struct User { 8 | id: UserId, 9 | name: Name, 10 | mail_address: MailAddress, 11 | } 12 | 13 | impl User { 14 | // はじめてインスタンスを生成する際に利用する 15 | pub fn new(name: Name, mail_address: MailAddress) -> Self { 16 | Self { 17 | id: UserId::default(), 18 | name, 19 | mail_address, 20 | } 21 | } 22 | 23 | // インスタンスを再構成する際に利用する 24 | pub fn rebuild(id: UserId, name: Name, mail_address: MailAddress) -> Self { 25 | Self { 26 | id, 27 | name, 28 | mail_address, 29 | } 30 | } 31 | 32 | pub fn change_name(&mut self, name: Name) { 33 | self.name = name; 34 | } 35 | 36 | pub fn change_mail_address(&mut self, mail_address: MailAddress) { 37 | self.mail_address = mail_address; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/domain/repository.rs: -------------------------------------------------------------------------------- 1 | mod user; 2 | 3 | pub use user::*; 4 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/domain/repository/user.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use super::super::{Name, User, UserId}; 4 | 5 | // ------------------------- 6 | // Repository 7 | // ------------------------- 8 | 9 | pub trait UserRepository { 10 | fn find_by_id(&self, id: UserId) -> Result>; 11 | 12 | fn find_by_name(&self, name: Name) -> Result>; 13 | 14 | fn save(&self, user: User) -> Result<()>; 15 | 16 | fn delete(&self, user: User) -> Result<()>; 17 | } 18 | 19 | pub trait HaveUserRepository { 20 | type UserRepository: UserRepository; 21 | 22 | fn provide_user_repository(&self) -> &Self::UserRepository; 23 | } 24 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/domain/service.rs: -------------------------------------------------------------------------------- 1 | mod user; 2 | 3 | pub use user::*; 4 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/domain/service/user.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use crate::domain::{HaveUserRepository, User, UserRepository}; 4 | 5 | // ------------------------- 6 | // Domain Service 7 | // ------------------------- 8 | 9 | pub fn exists(ctx: &T, user: &User) -> Result 10 | where 11 | T: HaveUserRepository, 12 | { 13 | let repo = ctx.provide_user_repository(); 14 | let duplicated_user = repo.find_by_name(user.name().clone())?; 15 | match duplicated_user { 16 | Some(_) => Ok(true), 17 | None => Ok(false), 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/domain/value_object.rs: -------------------------------------------------------------------------------- 1 | mod mail_address; 2 | mod name; 3 | mod user_id; 4 | 5 | pub use mail_address::*; 6 | pub use name::*; 7 | pub use user_id::*; 8 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/domain/value_object/mail_address.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use anyhow::Result; 4 | use derive_more::Display; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | #[derive(Clone, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] 8 | pub struct MailAddress(String); 9 | 10 | impl FromStr for MailAddress { 11 | type Err = anyhow::Error; 12 | 13 | fn from_str(s: &str) -> Result { 14 | Ok(MailAddress(s.to_string())) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/domain/value_object/name.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use anyhow::Result; 4 | use common::MyError; 5 | use derive_more::Display; 6 | use serde::{de, Serialize}; 7 | 8 | #[derive(Clone, Debug, PartialEq, Eq, Display, Serialize)] 9 | pub struct Name(String); 10 | 11 | impl Name { 12 | pub fn new(s: &str) -> Result { 13 | if s.chars().count() < 3 || s.chars().count() > 20 { 14 | bail!(MyError::type_error("ユーザ名は3文字以上、20文字以下です")) 15 | } 16 | Ok(Name(s.to_string())) 17 | } 18 | } 19 | 20 | impl FromStr for Name { 21 | type Err = anyhow::Error; 22 | 23 | fn from_str(s: &str) -> Result { 24 | Self::new(s) 25 | } 26 | } 27 | 28 | impl<'de> de::Deserialize<'de> for Name { 29 | fn deserialize(deserializer: D) -> Result 30 | where 31 | D: de::Deserializer<'de>, 32 | { 33 | let s = String::deserialize(deserializer)?; 34 | Self::new(&s).map_err(de::Error::custom) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/domain/value_object/user_id.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use anyhow::Result; 4 | use common::MyError; 5 | use derive_more::Display; 6 | use serde::{de, Serialize, Serializer}; 7 | use ulid::Ulid; 8 | 9 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Display, Hash)] 10 | pub struct UserId(Ulid); 11 | 12 | impl UserId { 13 | fn new(s: &str) -> Result { 14 | println!("user_id: {}", s); 15 | Ok(UserId( 16 | Ulid::from_string(s).map_err(|_| MyError::type_error("IDに誤りがあります"))?, 17 | )) 18 | } 19 | } 20 | 21 | impl Default for UserId { 22 | fn default() -> Self { 23 | UserId(Ulid::new()) 24 | } 25 | } 26 | 27 | impl FromStr for UserId { 28 | type Err = anyhow::Error; 29 | 30 | fn from_str(s: &str) -> Result { 31 | Self::new(s) 32 | } 33 | } 34 | 35 | impl<'de> de::Deserialize<'de> for UserId { 36 | fn deserialize(deserializer: D) -> Result 37 | where 38 | D: de::Deserializer<'de>, 39 | { 40 | let s = String::deserialize(deserializer)?; 41 | Self::new(&s).map_err(de::Error::custom) 42 | } 43 | } 44 | 45 | impl Serialize for UserId { 46 | fn serialize(&self, serializer: S) -> Result 47 | where 48 | S: Serializer, 49 | { 50 | serializer.serialize_str(&self.0.to_string()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/infrastructure.rs: -------------------------------------------------------------------------------- 1 | mod api; 2 | mod datastore; 3 | mod server; 4 | 5 | pub use api::*; 6 | pub use datastore::*; 7 | pub use server::*; 8 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/infrastructure/api.rs: -------------------------------------------------------------------------------- 1 | mod mock_api; 2 | mod rdb_api; 3 | 4 | pub use mock_api::*; 5 | pub use rdb_api::*; 6 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/infrastructure/api/mock_api.rs: -------------------------------------------------------------------------------- 1 | use derive_new::new; 2 | 3 | use crate::{ 4 | domain::HaveUserRepository, infrastructure::MockContext, usecase::HaveUserApplicationService, 5 | }; 6 | 7 | #[derive(Clone, Debug, new)] 8 | pub struct MockApi { 9 | context: MockContext, 10 | } 11 | 12 | impl HaveUserRepository for MockApi { 13 | type UserRepository = MockContext; 14 | 15 | fn provide_user_repository(&self) -> &Self::UserRepository { 16 | &self.context 17 | } 18 | } 19 | 20 | impl HaveUserApplicationService for MockApi { 21 | type UserApplicationService = Self; 22 | 23 | fn provide_user_service(&self) -> &Self::UserApplicationService { 24 | self 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/infrastructure/api/rdb_api.rs: -------------------------------------------------------------------------------- 1 | use derive_new::new; 2 | 3 | use crate::{ 4 | domain::HaveUserRepository, infrastructure::RDBContext, usecase::HaveUserApplicationService, 5 | }; 6 | 7 | #[derive(Clone, Debug, new)] 8 | pub struct RDBApi { 9 | context: RDBContext, 10 | } 11 | 12 | impl HaveUserRepository for RDBApi { 13 | type UserRepository = RDBContext; 14 | 15 | fn provide_user_repository(&self) -> &Self::UserRepository { 16 | &self.context 17 | } 18 | } 19 | 20 | impl HaveUserApplicationService for RDBApi { 21 | type UserApplicationService = Self; 22 | 23 | fn provide_user_service(&self) -> &Self::UserApplicationService { 24 | self 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/infrastructure/datastore.rs: -------------------------------------------------------------------------------- 1 | mod dto; 2 | mod mock_context; 3 | mod pg; 4 | mod rdb_context; 5 | 6 | pub use dto::*; 7 | pub use mock_context::*; 8 | pub use pg::*; 9 | pub use rdb_context::*; 10 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/infrastructure/datastore/dto.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | use anyhow::Result; 4 | use derive_getters::Getters; 5 | use derive_new::new; 6 | 7 | use crate::domain::User; 8 | 9 | #[derive(Clone, Debug, Getters, new)] 10 | pub struct UserDto { 11 | id: String, 12 | name: String, 13 | mail_address: String, 14 | } 15 | 16 | impl From for UserDto { 17 | fn from(v: User) -> UserDto { 18 | Self { 19 | id: v.id().to_string(), 20 | name: v.name().to_string(), 21 | mail_address: v.mail_address().to_string(), 22 | } 23 | } 24 | } 25 | 26 | impl TryFrom for User { 27 | type Error = anyhow::Error; 28 | 29 | fn try_from(v: UserDto) -> Result { 30 | Ok(Self::rebuild( 31 | v.id().parse()?, 32 | v.name().parse()?, 33 | v.mail_address().parse()?, 34 | )) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/infrastructure/datastore/mock_context.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::{Arc, Mutex}; 3 | 4 | use anyhow::Result; 5 | use common::MyError; 6 | 7 | use crate::domain::{Name, User, UserId, UserRepository}; 8 | 9 | #[derive(Clone, Debug, Default)] 10 | pub struct MockContext { 11 | db: Arc>>, 12 | } 13 | 14 | impl UserRepository for MockContext { 15 | fn save(&self, user: User) -> Result<()> { 16 | let db = self.db.clone(); 17 | let mut db = db 18 | .try_lock() 19 | .map_err(|_| MyError::internal_server_error("failed to try_lock db"))?; 20 | db.insert(user.id().clone(), user); 21 | Ok(()) 22 | } 23 | 24 | fn find_by_name(&self, name: Name) -> Result> { 25 | let db = self.db.clone(); 26 | let db = db 27 | .try_lock() 28 | .map_err(|_| MyError::internal_server_error("failed to try_lock db"))?; 29 | let target = db 30 | .values() 31 | .filter(|user| user.name().clone() == name) 32 | .cloned() 33 | .collect::>(); 34 | Ok(target.first().cloned()) 35 | } 36 | 37 | fn find_by_id(&self, _id: UserId) -> Result> { 38 | unimplemented!() 39 | } 40 | 41 | fn delete(&self, _user: User) -> Result<()> { 42 | unimplemented!() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/infrastructure/datastore/pg.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | use std::time::Duration; 3 | 4 | use anyhow::Result; 5 | use postgres::{Config, NoTls}; 6 | use r2d2_postgres::{ 7 | r2d2::{Pool, PooledConnection}, 8 | PostgresConnectionManager, 9 | }; 10 | 11 | pub struct PgConn(PooledConnection>); 12 | 13 | impl Deref for PgConn { 14 | type Target = PooledConnection>; 15 | 16 | fn deref(&self) -> &Self::Target { 17 | &self.0 18 | } 19 | } 20 | 21 | impl DerefMut for PgConn { 22 | fn deref_mut(&mut self) -> &mut PooledConnection> { 23 | &mut self.0 24 | } 25 | } 26 | 27 | #[derive(Clone)] 28 | pub struct PgPool(Pool>); 29 | 30 | impl PgPool { 31 | pub fn new() -> Self { 32 | // ハードコードしているが、envy等で環境変数から取得する形にもできる 33 | let config = Config::new() 34 | .user("postgres") 35 | .password("password") 36 | .host("localhost") 37 | .port(5432) 38 | .dbname("hoge") 39 | .to_owned(); 40 | 41 | let manager = PostgresConnectionManager::::new(config, NoTls); 42 | let pool = Pool::builder() 43 | .connection_timeout(Duration::from_secs(10)) 44 | .build_unchecked(manager); 45 | 46 | PgPool(pool) 47 | } 48 | 49 | pub fn conn(&self) -> Result { 50 | let pool = self.0.clone(); 51 | let conn = pool.get()?; 52 | Ok(PgConn(conn)) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/infrastructure/datastore/rdb_context.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryInto; 2 | use std::fmt; 3 | 4 | use anyhow::Result; 5 | 6 | use crate::{ 7 | domain::{Name, User, UserId, UserRepository}, 8 | infrastructure::{PgPool, UserDto}, 9 | }; 10 | 11 | #[derive(Clone)] 12 | pub struct RDBContext { 13 | pool: PgPool, 14 | } 15 | 16 | impl Default for RDBContext { 17 | fn default() -> Self { 18 | let pool = PgPool::new(); 19 | Self { pool } 20 | } 21 | } 22 | 23 | // Debug traitの要求を満たすため仮実装 24 | impl fmt::Debug for RDBContext { 25 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 26 | writeln!(f, "RDBContext debug")?; 27 | Ok(()) 28 | } 29 | } 30 | 31 | impl UserRepository for RDBContext { 32 | fn save(&self, user: User) -> Result<()> { 33 | let mut client = self.pool.conn()?; 34 | 35 | let user: UserDto = user.into(); 36 | let stmt = r#" 37 | INSERT INTO 38 | users 39 | VALUE 40 | ($1, $2, $3) 41 | ; 42 | "#; 43 | 44 | client.execute(stmt, &[user.id(), user.name(), user.mail_address()])?; 45 | Ok(()) 46 | } 47 | 48 | fn find_by_name(&self, name: Name) -> Result> { 49 | let mut client = self.pool.conn()?; 50 | 51 | let stmt = r#" 52 | SELECT 53 | * 54 | FROM 55 | users 56 | WHERE 57 | name = $1 58 | "#; 59 | 60 | let row = client.query_one(stmt, &[&name.to_string()])?; 61 | 62 | let id = row.get("id"); 63 | let name = row.get("name"); 64 | let mail_address = row.get("mail_address"); 65 | let dto = UserDto::new(id, name, mail_address); 66 | 67 | Ok(Some(dto.try_into()?)) 68 | } 69 | 70 | fn find_by_id(&self, _id: UserId) -> Result> { 71 | unimplemented!() 72 | } 73 | 74 | fn delete(&self, _user: User) -> Result<()> { 75 | unimplemented!() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/infrastructure/server.rs: -------------------------------------------------------------------------------- 1 | mod filter; 2 | mod handler; 3 | 4 | pub use filter::*; 5 | pub use handler::*; 6 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/infrastructure/server/filter.rs: -------------------------------------------------------------------------------- 1 | use warp::{Filter, Rejection, Reply}; 2 | 3 | use crate::{ 4 | infrastructure::{get_user_handler, register_user_handler}, 5 | usecase::HaveUserApplicationService, 6 | }; 7 | 8 | // TODO: appを参照で渡せたほうがよいかも? 9 | pub fn users_api(app: T) -> impl Filter + Clone 10 | where 11 | T: HaveUserApplicationService + std::marker::Sync + std::marker::Send + Clone, 12 | { 13 | get_user_by_name(app.clone()).or(register(app)) 14 | } 15 | 16 | fn get_user_by_name(app: T) -> impl Filter + Clone 17 | where 18 | T: std::marker::Sync + HaveUserApplicationService + std::marker::Send + Clone, 19 | { 20 | users() 21 | .and(warp::get()) 22 | .and(warp::body::json()) 23 | .and_then(move |name| get_user_handler(app.clone(), name)) 24 | } 25 | 26 | fn register(app: T) -> impl Filter + Clone 27 | where 28 | T: std::marker::Sync + HaveUserApplicationService + std::marker::Send + Clone, 29 | { 30 | users() 31 | .and(warp::put()) 32 | .and(warp::body::json()) 33 | .and_then(move |cmd| register_user_handler(app.clone(), cmd)) 34 | } 35 | 36 | fn users() -> warp::filters::BoxedFilter<()> { 37 | warp::path("users").boxed() 38 | } 39 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/infrastructure/server/handler.rs: -------------------------------------------------------------------------------- 1 | mod user; 2 | 3 | pub use user::*; 4 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/infrastructure/server/handler/user.rs: -------------------------------------------------------------------------------- 1 | use warp::{Rejection, Reply}; 2 | 3 | use crate::{ 4 | domain::Name, 5 | usecase::{CreateUserCommand, HaveUserApplicationService, UserApplicationService}, 6 | }; 7 | 8 | pub async fn get_user_handler(app: T, name: Name) -> Result 9 | where 10 | T: HaveUserApplicationService, 11 | { 12 | let service = app.provide_user_service(); 13 | match service.get_by_name(name) { 14 | Err(_) => Err(warp::reject::not_found()), 15 | Ok(user) => Ok(warp::reply::json(&user)), 16 | } 17 | } 18 | 19 | pub async fn register_user_handler( 20 | app: T, 21 | cmd: CreateUserCommand, 22 | ) -> Result 23 | where 24 | T: HaveUserApplicationService, 25 | { 26 | let service = app.provide_user_service(); 27 | match service.register(cmd) { 28 | Err(_) => Err(warp::reject::reject()), 29 | Ok(_) => Ok(warp::reply::with_status( 30 | "success", 31 | warp::http::StatusCode::OK, 32 | )), 33 | } 34 | } 35 | 36 | // trait として表現したかったが、traitのメソッドでは動的ディスパッチができないようだ 37 | // 38 | // error[E0562]: `impl Trait` not allowed outside of function and inherent method return types 39 | // --> src/c8_user_interface/app/main.rs:27:50 40 | // | 41 | // 27 | async fn list_users_handler(&self) -> Result { 42 | // 43 | // use async_trait::async_trait; 44 | // #[async_trait] 45 | // pub trait Handlers: UserApplicationService { 46 | // async fn list_users_handler(&self) -> Result { 47 | // self. 48 | // Ok(warp::reply::json(&users)) 49 | // } 50 | // } 51 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate anyhow; 3 | 4 | pub mod domain; 5 | pub mod infrastructure; 6 | pub mod usecase; 7 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/usecase.rs: -------------------------------------------------------------------------------- 1 | mod dto; 2 | mod input; 3 | mod user; 4 | 5 | pub use dto::*; 6 | pub use input::*; 7 | pub use user::*; 8 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/usecase/dto.rs: -------------------------------------------------------------------------------- 1 | mod user; 2 | 3 | pub use user::*; 4 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/usecase/dto/user.rs: -------------------------------------------------------------------------------- 1 | use crate::domain::User; 2 | use serde::Serialize; 3 | 4 | // アプリケーションサービスのクライアントに対して公開するUserのDTO 5 | #[derive(Clone, Debug, Serialize)] 6 | pub struct UserData { 7 | id: String, 8 | name: String, 9 | } 10 | 11 | // User型からDTOへの変換処理 12 | impl From for UserData { 13 | fn from(v: User) -> Self { 14 | Self { 15 | id: v.id().to_string(), 16 | name: v.name().to_string(), 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/usecase/input.rs: -------------------------------------------------------------------------------- 1 | mod user; 2 | 3 | pub use user::*; 4 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/usecase/input/user.rs: -------------------------------------------------------------------------------- 1 | use derive_getters::Getters; 2 | use derive_new::new; 3 | use serde::Deserialize; 4 | 5 | use crate::domain::{MailAddress, Name, UserId}; 6 | 7 | #[derive(Clone, Debug, Getters, new, Deserialize)] 8 | pub struct CreateUserCommand { 9 | name: Name, 10 | mail_address: MailAddress, 11 | } 12 | 13 | // Updateメソッドのパラメータ群。Optionで定義したフィールドは任意の更新項目とする。 14 | #[derive(Clone, Debug, Getters, new)] 15 | pub struct UpdateUserCommand { 16 | id: UserId, 17 | name: Option, 18 | mail_address: Option, 19 | } 20 | 21 | #[derive(Clone, Debug, Getters, new)] 22 | pub struct DeleteUserCommand { 23 | id: UserId, 24 | } 25 | -------------------------------------------------------------------------------- /chapter08_sample_application/src/usecase/user.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use common::MyError; 3 | 4 | use crate::{ 5 | domain::{exists, HaveUserRepository, Name, User, UserRepository}, 6 | usecase::{CreateUserCommand, DeleteUserCommand, UpdateUserCommand, UserData}, 7 | }; 8 | 9 | // Cake Patternによる実装 10 | // c6ではApplicationServiceをstructで表現したが、今回のパターンではtraitで表現している 11 | // このパターンだとtrait上のデフォルト実装に加えて、独自の実装に変更することもできる 12 | // 13 | // パターンは以下のブログを参考にした 14 | // https://keens.github.io/blog/2017/12/01/rustnodi/ 15 | pub trait UserApplicationService: HaveUserRepository + std::marker::Sized { 16 | fn register(&self, cmd: CreateUserCommand) -> Result<()> { 17 | let user = User::new(cmd.name().clone(), cmd.mail_address().clone()); 18 | if exists(self, &user)? { 19 | bail!(MyError::internal_server_error("ユーザは既に存在しています")) 20 | } 21 | self.provide_user_repository().save(user) 22 | } 23 | 24 | fn get_by_name(&self, name: Name) -> Result { 25 | let user = self 26 | .provide_user_repository() 27 | .find_by_name(name)? 28 | .ok_or_else(|| MyError::internal_server_error("ユーザが見つかりませんでした"))?; 29 | Ok(user.into()) 30 | } 31 | 32 | fn update(&self, command: UpdateUserCommand) -> Result<()> { 33 | let mut user = self 34 | .provide_user_repository() 35 | .find_by_id(command.id().clone())? 36 | .ok_or_else(|| MyError::internal_server_error("ユーザが見つかりませんでした"))?; 37 | 38 | if let Some(name) = command.name() { 39 | user.change_name(name.clone()); 40 | if exists(self, &user)? { 41 | bail!(MyError::internal_server_error("ユーザは既に存在しています")) 42 | } 43 | } 44 | 45 | if let Some(mail_address) = command.mail_address() { 46 | user.change_mail_address(mail_address.clone()); 47 | } 48 | 49 | self.provide_user_repository().save(user)?; 50 | Ok(()) 51 | } 52 | 53 | fn delete(&self, command: DeleteUserCommand) -> Result<()> { 54 | let target_id = *command.id(); 55 | let user = self 56 | .provide_user_repository() 57 | .find_by_id(target_id)? 58 | .ok_or_else(|| MyError::internal_server_error("ユーザが見つかりませんでした"))?; 59 | self.provide_user_repository().delete(user)?; 60 | Ok(()) 61 | } 62 | } 63 | 64 | // Repositoryを持つものに対して自動で実装を与えられる 65 | impl UserApplicationService for T {} 66 | 67 | pub trait HaveUserApplicationService { 68 | type UserApplicationService: UserApplicationService; 69 | 70 | fn provide_user_service(&self) -> &Self::UserApplicationService; 71 | } 72 | -------------------------------------------------------------------------------- /chapter09_factory/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chapter09_factory" 3 | version = "0.1.0" 4 | authors = ["kuwana-kb <41671293+kuwana-kb@users.noreply.github.com>"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | anyhow = "1.0.28" 11 | derive-getters = "0.1.0" 12 | derive_more = "0.99.5" 13 | derive-new = "0.5.8" 14 | postgres = "0.17.2" 15 | r2d2_postgres = "0.16.0" 16 | serde = { version = "1.0.106", features = ["derive"]} 17 | ulid = "0.3.1" 18 | 19 | common = { path = "../common/"} 20 | -------------------------------------------------------------------------------- /chapter09_factory/src/domain.rs: -------------------------------------------------------------------------------- 1 | mod entity; 2 | mod repository; 3 | mod value_object; 4 | 5 | pub use entity::*; 6 | pub use repository::*; 7 | pub use value_object::*; 8 | -------------------------------------------------------------------------------- /chapter09_factory/src/domain/entity.rs: -------------------------------------------------------------------------------- 1 | mod user; 2 | mod user_factory; 3 | 4 | pub use user::*; 5 | pub use user_factory::*; 6 | -------------------------------------------------------------------------------- /chapter09_factory/src/domain/entity/user.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use derive_getters::Getters; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use super::super::{Name, UserId}; 6 | 7 | #[derive(Clone, Debug, Getters, Serialize, Deserialize)] 8 | pub struct User { 9 | id: UserId, 10 | name: Name, 11 | } 12 | 13 | impl User { 14 | // インスタンスの生成がファクトリに移設されたため、Userをインスタンス化する際には必ず外かからUserIdが引き渡される 15 | // したがってコンストラクタは1つで済む 16 | // TODO: pubの範囲をFactoryに限定したい 17 | pub fn new(id: UserId, name: Name) -> Self { 18 | Self { id, name } 19 | } 20 | 21 | // 以下コンストラクタは不要となる 22 | // pub fn new(name: Name) -> Self { 23 | // Self { 24 | // id: UserId::default(), 25 | // name, 26 | // mail_address, 27 | // } 28 | // } 29 | 30 | pub fn change_name(&mut self, name: Name) { 31 | self.name = name; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /chapter09_factory/src/domain/entity/user_factory.rs: -------------------------------------------------------------------------------- 1 | use super::User; 2 | use anyhow::Result; 3 | 4 | use crate::domain::Name; 5 | 6 | pub trait UserFactory { 7 | fn create(&self, name: Name) -> Result; 8 | } 9 | 10 | pub trait HaveUserFactory { 11 | type UserFactory: UserFactory; 12 | 13 | fn provide_user_factory(&self) -> &Self::UserFactory; 14 | } 15 | -------------------------------------------------------------------------------- /chapter09_factory/src/domain/repository.rs: -------------------------------------------------------------------------------- 1 | mod user; 2 | 3 | pub use user::*; 4 | -------------------------------------------------------------------------------- /chapter09_factory/src/domain/repository/user.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use super::super::User; 4 | 5 | // ------------------------- 6 | // Repository 7 | // ------------------------- 8 | 9 | pub trait UserRepository { 10 | fn save(&self, user: User) -> Result<()>; 11 | } 12 | 13 | pub trait HaveUserRepository { 14 | type UserRepository: UserRepository; 15 | 16 | fn provide_user_repository(&self) -> &Self::UserRepository; 17 | } 18 | -------------------------------------------------------------------------------- /chapter09_factory/src/domain/value_object.rs: -------------------------------------------------------------------------------- 1 | mod name; 2 | mod user_id; 3 | 4 | pub use name::*; 5 | pub use user_id::*; 6 | -------------------------------------------------------------------------------- /chapter09_factory/src/domain/value_object/name.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use anyhow::Result; 4 | use common::MyError; 5 | use derive_more::Display; 6 | use serde::{de, Serialize}; 7 | 8 | #[derive(Clone, Debug, PartialEq, Eq, Display, Serialize)] 9 | pub struct Name(String); 10 | 11 | impl Name { 12 | pub fn new(s: &str) -> Result { 13 | if s.chars().count() < 3 || s.chars().count() > 20 { 14 | bail!(MyError::type_error("ユーザ名は3文字以上、20文字以下です")) 15 | } 16 | Ok(Name(s.to_string())) 17 | } 18 | } 19 | 20 | impl FromStr for Name { 21 | type Err = anyhow::Error; 22 | 23 | fn from_str(s: &str) -> Result { 24 | Self::new(s) 25 | } 26 | } 27 | 28 | impl<'de> de::Deserialize<'de> for Name { 29 | fn deserialize(deserializer: D) -> Result 30 | where 31 | D: de::Deserializer<'de>, 32 | { 33 | let s = String::deserialize(deserializer)?; 34 | Self::new(&s).map_err(de::Error::custom) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /chapter09_factory/src/domain/value_object/user_id.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use anyhow::Result; 4 | use derive_more::Display; 5 | use serde::{de, Serialize, Serializer}; 6 | 7 | #[derive(Clone, Debug, PartialEq, Eq, Display, Hash)] 8 | pub struct UserId(String); 9 | 10 | impl UserId { 11 | fn new(s: &str) -> Self { 12 | println!("user_id: {}", s); 13 | Self(s.to_string()) 14 | } 15 | } 16 | 17 | impl FromStr for UserId { 18 | type Err = anyhow::Error; 19 | 20 | fn from_str(s: &str) -> Result { 21 | Ok(Self::new(s)) 22 | } 23 | } 24 | 25 | impl<'de> de::Deserialize<'de> for UserId { 26 | fn deserialize(deserializer: D) -> Result 27 | where 28 | D: de::Deserializer<'de>, 29 | { 30 | let s = String::deserialize(deserializer)?; 31 | Ok(Self::new(&s)) 32 | } 33 | } 34 | 35 | impl Serialize for UserId { 36 | fn serialize(&self, serializer: S) -> Result 37 | where 38 | S: Serializer, 39 | { 40 | serializer.serialize_str(&self.0.to_string()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /chapter09_factory/src/infrastructure.rs: -------------------------------------------------------------------------------- 1 | mod api; 2 | mod datastore; 3 | 4 | pub use api::*; 5 | pub use datastore::*; 6 | -------------------------------------------------------------------------------- /chapter09_factory/src/infrastructure/api.rs: -------------------------------------------------------------------------------- 1 | mod mock_api; 2 | mod rdb_api; 3 | 4 | pub use mock_api::*; 5 | pub use rdb_api::*; 6 | -------------------------------------------------------------------------------- /chapter09_factory/src/infrastructure/api/mock_api.rs: -------------------------------------------------------------------------------- 1 | use derive_new::new; 2 | 3 | use crate::{ 4 | domain::{HaveUserFactory, HaveUserRepository}, 5 | infrastructure::MockContext, 6 | usecase::HaveUserApplicationService, 7 | }; 8 | 9 | #[derive(Clone, Debug, new)] 10 | pub struct MockApi { 11 | context: MockContext, 12 | } 13 | 14 | impl HaveUserFactory for MockApi { 15 | type UserFactory = MockContext; 16 | 17 | fn provide_user_factory(&self) -> &Self::UserFactory { 18 | &self.context 19 | } 20 | } 21 | 22 | impl HaveUserRepository for MockApi { 23 | type UserRepository = MockContext; 24 | 25 | fn provide_user_repository(&self) -> &Self::UserRepository { 26 | &self.context 27 | } 28 | } 29 | 30 | impl HaveUserApplicationService for MockApi { 31 | type UserApplicationService = Self; 32 | 33 | fn provide_user_service(&self) -> &Self::UserApplicationService { 34 | self 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /chapter09_factory/src/infrastructure/api/rdb_api.rs: -------------------------------------------------------------------------------- 1 | use derive_new::new; 2 | 3 | use crate::{ 4 | domain::{HaveUserFactory, HaveUserRepository}, 5 | infrastructure::RDBContext, 6 | usecase::HaveUserApplicationService, 7 | }; 8 | 9 | #[derive(Clone, Debug, new)] 10 | pub struct RDBApi { 11 | context: RDBContext, 12 | } 13 | 14 | impl HaveUserFactory for RDBApi { 15 | type UserFactory = RDBContext; 16 | 17 | fn provide_user_factory(&self) -> &Self::UserFactory { 18 | &self.context 19 | } 20 | } 21 | 22 | impl HaveUserRepository for RDBApi { 23 | type UserRepository = RDBContext; 24 | 25 | fn provide_user_repository(&self) -> &Self::UserRepository { 26 | &self.context 27 | } 28 | } 29 | 30 | impl HaveUserApplicationService for RDBApi { 31 | type UserApplicationService = Self; 32 | 33 | fn provide_user_service(&self) -> &Self::UserApplicationService { 34 | self 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /chapter09_factory/src/infrastructure/datastore.rs: -------------------------------------------------------------------------------- 1 | mod mock_context; 2 | mod pg; 3 | mod rdb_context; 4 | 5 | pub use mock_context::*; 6 | pub use pg::*; 7 | pub use rdb_context::*; 8 | -------------------------------------------------------------------------------- /chapter09_factory/src/infrastructure/datastore/mock_context.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::{Arc, Mutex}; 3 | 4 | use anyhow::Result; 5 | use common::MyError; 6 | 7 | use crate::domain::{Name, User, UserFactory, UserId, UserRepository}; 8 | 9 | #[derive(Clone, Debug, Default)] 10 | pub struct MockContext { 11 | db: Arc>>, 12 | counter: Arc>, 13 | } 14 | 15 | // MockにおけるUserFactoryの実装 16 | // MockContext内のカウンターを利用して採番する 17 | impl UserFactory for MockContext { 18 | fn create(&self, name: Name) -> Result { 19 | let counter = self.counter.clone(); 20 | let mut counter = counter 21 | .try_lock() 22 | .map_err(|_| MyError::internal_server_error("failed to try_lock counter"))?; 23 | let id = *counter; 24 | let user = User::new(id.to_string().parse()?, name); 25 | 26 | *counter += 1; 27 | Ok(user) 28 | } 29 | } 30 | 31 | impl UserRepository for MockContext { 32 | fn save(&self, _user: User) -> Result<()> { 33 | // 実装は省略する。chapter05_repositoryの実装を参照すること 34 | unimplemented!() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /chapter09_factory/src/infrastructure/datastore/pg.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | use std::time::Duration; 3 | 4 | use anyhow::Result; 5 | use postgres::{Config, NoTls}; 6 | use r2d2_postgres::{ 7 | r2d2::{Pool, PooledConnection}, 8 | PostgresConnectionManager, 9 | }; 10 | 11 | pub struct PgConn(PooledConnection>); 12 | 13 | impl Deref for PgConn { 14 | type Target = PooledConnection>; 15 | 16 | fn deref(&self) -> &Self::Target { 17 | &self.0 18 | } 19 | } 20 | 21 | impl DerefMut for PgConn { 22 | fn deref_mut(&mut self) -> &mut PooledConnection> { 23 | &mut self.0 24 | } 25 | } 26 | 27 | #[derive(Clone)] 28 | pub struct PgPool(Pool>); 29 | 30 | impl PgPool { 31 | pub fn new() -> Self { 32 | // ハードコードしているが、envy等で環境変数から取得する形にもできる 33 | let config = Config::new() 34 | .user("postgres") 35 | .password("password") 36 | .host("localhost") 37 | .port(5432) 38 | .dbname("hoge") 39 | .to_owned(); 40 | 41 | let manager = PostgresConnectionManager::::new(config, NoTls); 42 | let pool = Pool::builder() 43 | .connection_timeout(Duration::from_secs(10)) 44 | .build_unchecked(manager); 45 | 46 | PgPool(pool) 47 | } 48 | 49 | pub fn conn(&self) -> Result { 50 | let pool = self.0.clone(); 51 | let conn = pool.get()?; 52 | Ok(PgConn(conn)) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /chapter09_factory/src/infrastructure/datastore/rdb_context.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use anyhow::Result; 4 | use common::MyError; 5 | 6 | use crate::{ 7 | domain::{Name, User, UserFactory, UserRepository}, 8 | infrastructure::PgPool, 9 | }; 10 | 11 | #[derive(Clone)] 12 | pub struct RDBContext { 13 | pool: PgPool, 14 | } 15 | 16 | impl Default for RDBContext { 17 | fn default() -> Self { 18 | let pool = PgPool::new(); 19 | Self { pool } 20 | } 21 | } 22 | 23 | // Debug traitの要求を満たすため仮実装 24 | impl fmt::Debug for RDBContext { 25 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 26 | writeln!(f, "RDBContext debug")?; 27 | Ok(()) 28 | } 29 | } 30 | 31 | // RDBにおけるUserFactoryの実装 32 | // シーケンスを利用したファクトリとなっている 33 | impl UserFactory for RDBContext { 34 | fn create(&self, name: Name) -> Result { 35 | let mut client = self.pool.conn()?; 36 | 37 | let rows = client.query("SELECT seq = (NEXT VALUE FOR UserSeq)", &[])?; 38 | 39 | let row = rows.iter().next(); 40 | let seq_id = match row { 41 | Some(row) => { 42 | let raw_seq_id: String = row.get(0); 43 | raw_seq_id 44 | } 45 | None => bail!(MyError::internal_server_error( 46 | "Failed to get sequential id." 47 | )), 48 | }; 49 | let user = User::new(seq_id.parse()?, name); 50 | Ok(user) 51 | } 52 | } 53 | 54 | impl UserRepository for RDBContext { 55 | fn save(&self, _user: User) -> Result<()> { 56 | // 実装は省略する。chapter05_repositoryの実装を参照すること 57 | unimplemented!() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /chapter09_factory/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate anyhow; 3 | 4 | mod domain; 5 | mod infrastructure; 6 | mod usecase; 7 | -------------------------------------------------------------------------------- /chapter09_factory/src/usecase.rs: -------------------------------------------------------------------------------- 1 | mod input; 2 | mod user; 3 | 4 | pub use input::*; 5 | pub use user::*; 6 | -------------------------------------------------------------------------------- /chapter09_factory/src/usecase/input.rs: -------------------------------------------------------------------------------- 1 | use derive_getters::Getters; 2 | use derive_new::new; 3 | use serde::Deserialize; 4 | 5 | use crate::domain::Name; 6 | 7 | #[derive(Clone, Debug, Getters, new, Deserialize)] 8 | pub struct CreateUserCommand { 9 | name: Name, 10 | } 11 | -------------------------------------------------------------------------------- /chapter09_factory/src/usecase/user.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use crate::{ 4 | domain::{HaveUserFactory, HaveUserRepository, UserFactory, UserRepository}, 5 | usecase::CreateUserCommand, 6 | }; 7 | 8 | pub trait UserApplicationService: 9 | HaveUserRepository + HaveUserFactory + std::marker::Sized 10 | { 11 | fn register(&self, cmd: CreateUserCommand) -> Result<()> { 12 | let name = cmd.name().clone(); 13 | let user = self.provide_user_factory().create(name)?; 14 | self.provide_user_repository().save(user) 15 | } 16 | } 17 | 18 | // Repositoryを持つものに対して自動で実装を与えられる 19 | impl UserApplicationService for T {} 20 | 21 | pub trait HaveUserApplicationService { 22 | type UserApplicationService: UserApplicationService; 23 | 24 | fn provide_user_service(&self) -> &Self::UserApplicationService; 25 | } 26 | -------------------------------------------------------------------------------- /common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "common" 3 | version = "0.1.0" 4 | authors = ["kuwana-kb <41671293+kuwana-kb@users.noreply.github.com>"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | thiserror = "1.0.15" 11 | -------------------------------------------------------------------------------- /common/src/lib.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum MyError { 5 | #[error("TypeError: {0}")] 6 | TypeError(String), 7 | #[error("InternalServerError: {0}")] 8 | InternalServerError(String), 9 | } 10 | 11 | impl MyError { 12 | pub fn type_error(s: &str) -> MyError { 13 | MyError::TypeError(s.to_string()) 14 | } 15 | 16 | pub fn internal_server_error(s: &str) -> MyError { 17 | MyError::InternalServerError(s.to_string()) 18 | } 19 | } 20 | --------------------------------------------------------------------------------