Last active
February 9, 2025 01:58
-
-
Save non7247/02bb97c2b6d31ed8b9fd291db3a906bb to your computer and use it in GitHub Desktop.
ドメインモデリング(途中)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use chrono::{DateTime, Local}; | |
use derive_more::{From, Deref, Display}; | |
// ------------------------------------------------------------------------ | |
// 入力データ | |
// ------------------------------------------------------------------------ | |
#[derive(Debug, Clone)] | |
pub struct UnvalidatedOrder { | |
pub order_id: OrderId, | |
pub customer_info: UnvalidatedCustomerInfo, | |
pub shipping_address: UnvalidatedAddress, | |
} | |
#[derive(Debug, Clone)] | |
pub struct UnvalidatedCustomerInfo { | |
name: String, | |
email: String, | |
} | |
#[derive(Debug, Clone)] | |
pub struct UnvalidatedAddress(); | |
// ------------------------------------------------------------------------ | |
// 入力コマンド | |
// ------------------------------------------------------------------------ | |
#[derive(Debug, Clone)] | |
pub struct Command<T> { | |
data: T, | |
timestamp: DateTime<Local>, | |
user_id: String, | |
} | |
#[derive(Debug, Clone)] | |
pub struct PlaceOrderCommand { | |
command: Command<UnvalidatedAddress> | |
} | |
// ------------------------------------------------------------------------ | |
// パブリックAPI | |
// ------------------------------------------------------------------------ | |
// 受注確定ワークフローの成功出力 | |
#[derive(Debug, Clone)] | |
pub struct OrderPlaced(PlacedOrder); | |
#[derive(Debug, Clone)] | |
pub struct BillableOrderePlaced { | |
order_id: OrderId, | |
billing_address: Address, | |
amount_to_bill: BillingAmount, | |
} | |
#[derive(Debug, Clone)] | |
pub struct OrderAcknowledgmentSent { | |
order_id: OrderId, | |
email_address: EmailAddress, | |
} | |
#[derive(Debug, Clone)] | |
pub enum PlaceOrderEvent { | |
OrderPlaced(OrderPlaced), | |
BillableOrderePlaced(BillableOrderePlaced), | |
AcknowledgmentSent(OrderAcknowledgmentSent), | |
} | |
// 受注確定ワークフローの失敗出力 | |
#[derive(Debug, Clone)] | |
pub struct PlaceOrderError { | |
validation_error: Vec<ValidationError>, | |
} | |
#[derive(Debug, Clone)] | |
pub struct ValidationError { | |
field_name: String, | |
error_description: String, | |
} | |
pub fn place_order_workflow( | |
command: &PlaceOrderCommand | |
) -> Result<Vec<PlaceOrderEvent>, PlaceOrderError> { | |
todo!("これから"); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
mod domain_api; | |
mod order_taking; | |
use order_taking::*; | |
fn main() { | |
let unit_qty_result = UnitQuantity::new(1); | |
match unit_qty_result { | |
Ok(uqty) => { | |
println!("Success. Value is {}", uqty); | |
let inner_value = *uqty; | |
println!("Inner value is {}", inner_value); | |
}, | |
Err(msg) => println!("Failure, Message is {}", msg), | |
} | |
let kg_qty_result = KilogramQuantity::new(101.0); | |
match kg_qty_result { | |
Ok(kqty) => { | |
println!("Success. Value is {}", kqty); | |
let inner_value = *kqty; | |
println!("Inner value is {}", inner_value); | |
}, | |
Err(msg) => println!("Failure, Message is {}", msg), | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//use chrono::{DateTime, Local}; | |
use derive_more::{From, Deref, Display}; | |
#[derive(Debug, Clone, Display)] | |
pub struct NonEmptyVec<T> { | |
inner: Vec<T>, | |
} | |
impl<T> NonEmptyVec<T> { | |
pub fn new(elements: Vec<T>) -> Self { | |
assert!(!elements.is_empty(), "Vector must not be empty"); | |
Self{inner: elements} | |
} | |
pub fn iter(&self) -> std::slice::Iter<'_, T> { | |
self.inner.iter() | |
} | |
} | |
impl<T> Deref for NonEmptyVec<T> { | |
type Target = Vec<T>; | |
fn deref(&self) -> &Self::Target { | |
&self.inner | |
} | |
} | |
// 型の定義 | |
// 製品コード関連 | |
#[derive(Debug, Clone, From, Deref)] | |
struct WidgetCode(String); | |
#[derive(Debug, Clone, From, Deref)] | |
struct GizmoCode(String); | |
#[derive(Debug, Clone)] | |
enum ProductCode { | |
Widget(WidgetCode), | |
Gizmo(GizmoCode), | |
} | |
// 注文数量関連 | |
#[derive(Debug, Clone, Copy, From, Deref, Display)] | |
pub struct UnitQuantity(i32); | |
impl UnitQuantity { | |
pub fn new(qty: i32) -> Result<Self, String> { | |
if qty < 1 { | |
return Err("UnitQuantity can not be negative".to_string()); | |
} else if qty > 1000 { | |
return Err("UnitQuantity can not be more than 1000".to_string()); | |
} | |
Ok(Self(qty)) | |
} | |
} | |
#[derive(Debug, Clone, Copy, From, Deref, Display)] | |
pub struct KilogramQuantity(f64); | |
impl KilogramQuantity { | |
pub fn new(qty: f64) -> Result<Self, String> { | |
if (qty - 0.05).abs() > 0.001 && qty < 0.05 { | |
return Err("KilogramQuantity can not be negative".to_string()); | |
} else if (qty - 100.0).abs() > 0.001 && qty > 100.0 { | |
return Err("KilogramQuantity can not be more than 100.0".to_string()); | |
} | |
Ok(Self(qty)) | |
} | |
} | |
#[derive(Debug, Clone)] | |
pub enum OrderQuantity { | |
Unit(UnitQuantity), | |
Kilos(KilogramQuantity), | |
} | |
// 注文書 | |
#[derive(Debug, Clone)] | |
struct OrderId(); | |
#[derive(Debug, Clone, Copy, PartialEq)] | |
struct OrderLineId(u32); | |
#[derive(Debug, Clone)] | |
struct CustomerId(); | |
#[derive(Debug, Clone)] | |
struct EmailContactInfo(); | |
#[derive(Debug, Clone)] | |
struct PostalContactInfo(); | |
#[derive(Debug, Clone)] | |
struct BothContactMethods { | |
email: EmailContactInfo, | |
address: PostalContactInfo, | |
} | |
#[derive(Debug, Clone)] | |
enum ContactInfo { | |
EmailOnly(EmailContactInfo), | |
AddrOnly(PostalContactInfo), | |
EmailAndAddr(BothContactMethods), | |
} | |
#[derive(Debug, Clone)] | |
struct Name(); | |
#[derive(Debug, Clone)] | |
struct Contact { | |
name: Name, | |
contact_info: ContactInfo, | |
} | |
#[derive(Debug, Clone)] | |
struct CustomerInfo(); | |
#[derive(Debug, Clone)] | |
struct ShippingAddress(); | |
#[derive(Debug, Clone)] | |
struct BillingAddress(); | |
#[derive(Debug, Clone, Copy, From, Deref)] | |
struct Price(u32); | |
#[derive(Debug, Clone, Copy, From, Deref)] | |
struct BillingAmount(u32); | |
#[derive(Debug, Clone)] | |
struct Order { | |
id: OrderId, | |
customer_id: CustomerId, | |
shipping_address: ShippingAddress, | |
billing_address: BillingAddress, | |
order_lines: NonEmptyVec<OrderLine>, | |
amount_to_bill: BillingAmount, | |
} | |
impl Order { | |
pub fn change_order_line_price(&self, order_line_id: &OrderLineId, new_price: &Price) -> Self { | |
let order_line = self.order_lines.iter().find(|x| x.id == *order_line_id); | |
let order_line = match order_line { | |
Some(value) => value, | |
None => return self.clone() | |
}; | |
let new_order_line = OrderLine { price: *new_price, ..order_line.clone() }; | |
let new_order_lines = self.replace_order_line(order_line_id, &new_order_line); | |
let new_amount_bill = BillingAmount::from(new_order_lines.iter().map(|x| *x.price).sum::<u32>()); | |
Order { | |
order_lines: new_order_lines, | |
amount_to_bill: new_amount_bill, | |
..self.clone() | |
} | |
} | |
fn replace_order_line( | |
&self, | |
order_line_id: &OrderLineId, | |
new_order_line: &OrderLine | |
) -> NonEmptyVec<OrderLine> { | |
let index = self.order_lines.iter().position(|x| x.id == *order_line_id); | |
let mut new_order_lines = (*self.order_lines).clone(); | |
if let Some(i) = index { | |
new_order_lines[i] = new_order_line.clone(); | |
} | |
NonEmptyVec::new(new_order_lines) | |
} | |
} | |
#[derive(Debug, Clone)] | |
struct OrderLine { | |
id: OrderLineId, | |
product_code: ProductCode, | |
order_quantity: OrderQuantity, | |
price: Price, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::iter::Product; | |
use crate::{domain_api::*, OrderQuantity}; | |
// ------------------------------------------------------------------------ | |
// 注文のライフサイクル | |
// ------------------------------------------------------------------------ | |
// 検証済みの状態 | |
#[derive(Debug, Clone)] | |
struct ValidatedOrderLine { | |
id: OrderLineId, | |
product_code: ProductCode, | |
order_quantity: OrderQuantity, | |
price: Price, | |
} | |
#[derive(Debug, Clone)] | |
struct ValidatedOrder { | |
order_id: OrderId, | |
curstomer_info: CustomerInfo, | |
shipping_address: Address, | |
billing_address: Address, | |
order_lines: Vec<ValidatedOrderLine>, | |
} | |
#[derive(Debug, Clone)] | |
struct OrderId(); | |
#[derive(Debug, Clone)] | |
struct CustomerInfo(); | |
#[derive(Debug, Clone)] | |
struct Address(); | |
// 価格計算済みの状態 | |
#[derive(Debug, Clone)] | |
struct PricedOrderLine { | |
id: OrderLineId, | |
product_code: ProductCode, | |
order_quantity: OrderQuantity, | |
price: Price, | |
} | |
#[derive(Debug, Clone)] | |
struct PricedOrder { | |
order_id: OrderId, | |
curstomer_info: CustomerInfo, | |
shipping_address: Address, | |
billing_address: Address, | |
order_lines: Vec<PricedOrderLine>, | |
amount_to_bill: BillingAmount, | |
} | |
// 全状態の結合 | |
#[derive(Debug, Clone)] | |
enum Order { | |
Unvalidated(UnvalidatedOrder), | |
Validated(ValidatedOrder), | |
Priced(PricedOrder), | |
} | |
// ------------------------------------------------------------------------ | |
// 内部ステップの定義 | |
// ------------------------------------------------------------------------ | |
// ----- 注文の検証 ----- | |
// 注文の検証が使用するサービス | |
fn check_product_code_exists(product_code: &ProductCode) -> bool { | |
false | |
} | |
#[derive(Debug, Clone)] | |
struct AddressValidationError(); | |
#[derive(Debug, Clone)] | |
struct CheckedAddress(); | |
fn check_address_exists( | |
unvalidated_address: &UnvalidatedAddress | |
) -> Result<CheckedAddress, AddressValidationError> { | |
todo!("これから"); | |
} | |
fn validate_order( | |
unvalidated_order: &UnvalidatedOrder | |
) -> Result<ValidatedOrder, ValidationError> { | |
check_product_code_exists(product_code); | |
check_address_exists(&unvalidated_order.shipping_address); | |
todo!("これから"); | |
} | |
// ----- 注文の価格計算 ----- | |
// 注文の価格計算が使用するサービス | |
fn get_product_price(product_code: &ProductCode) -> Price { | |
todo!("これから"); | |
} | |
#[derive(Debug, Clone)] | |
struct PricingError(); | |
fn price_order(validated_order: &ValidatedOrder) -> Result<PricedOrder, PricingError> { | |
get_product_price(product_code); | |
todo!("これから"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment