Skip to content

Instantly share code, notes, and snippets.

@non7247
Last active February 9, 2025 01:58
Show Gist options
  • Save non7247/02bb97c2b6d31ed8b9fd291db3a906bb to your computer and use it in GitHub Desktop.
Save non7247/02bb97c2b6d31ed8b9fd291db3a906bb to your computer and use it in GitHub Desktop.
ドメインモデリング(途中)
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!("これから");
}
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),
}
}
//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,
}
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