DMMF #04 - 2025/06/27
5.8 集約から
集約
集約ルート
code:fsharp
namespace OrderTaking.Domain
// 製品コード関連
type WidgetCode = WidgetCode of string // 制約: 先頭が"W"+数字4桁
type GizmoCode = GizmoCode of string // 制約: 先頭が"G"+数字3桁
type ProductCode =
| Widget of WidgetCode
| Gizmo of GizmoCode
// 注文数量関連
type UnitQuantity = UnitQuantity of int
type KilogramQuantity = KilogramQuantity of decimal
type OrderQuantity =
| Unit of UnitQuantity
| Kilos of KilogramQuantity
// まだ具体的にどんな値を取るか分かっていないものは Undefined にしておく
type Undefined = exn
type OrderId = Undefined
type OrderLineId = Undefined
type CustomerId = Undefined
type CustoemrInfo = Undefined
type ShippingAddress = Undefined
type BillingAddress = Undefined
type Price = Undefined
type BillingAmount = Undefined
type Order = {
Id : OrderId
CustomerId : CustomerId
ShippingAddress : ShippingAddress
BillingAddress : BillingAddress
OrderLines : OrderLine List
AmountToBill : BillingAmount
}
and OrderLine = {
Id : OrderLineId
Orderid : OrderId
ProductCode : ProductCode
OrderQuantity : OrderQuantity
Price : Price
}
type UnvalidatedOrder = {
OrderId : string
CustomerInfo : Undefined
ShippingAddress : Undefined
}
type PlaceOrderEvents = {
AcknowledgmentSent : Undefined
OrderPlaced : Undefined
BillableOrderPlaced : Undefined
}
type PlaceOrderError =
| ValidationError of ValidationError list
// | ... その他のエラー
and ValidationError = {
FiledName : string
ErrorDescription : string
}
/// 「注文確定」プロセス
type PlaceOrder =
UnvalidatedOrder -> Result<PlaceOrderEvents,PlaceOrderError>
(素人の) Haskell takezaki.icon
code:haskell
{-# LANGUAGE DuplicateRecordFields #-}
{-# OPTIONS_GHC -Wno-missing-export-lists #-}
module OrderTaking.Domain where
import Data.Decimal as Decimal
-- 製品コード関連
newtype WidgetCode = WidgetCode String
-- 制約: 先頭が"W" + 数字4桁
newtype GizmoCode = GizmoCode String
-- 制約: 先頭が"G" + 数字4桁
data ProductCode = Widget WidgetCode | Gizmo GizmoCode
-- 注文数量関連
newtype UnitQuantity = UnitQuantity Int
newtype KilogramQuantity = KilogramQuantity Decimal
data OrderQuantity = Unit UnitQuantity | Kilos KilogramQuantity
-- 未定義
data OrderId
data OrderLineId
data CustomerId
data CustomerInfo
data ShippingAddress
data BillingAddress
data Price
data BillingAmount
data Order = Order
{ id :: OrderId -- エンティティのID
, customerId :: CustomerId -- 顧客の参照
, shippingAddress :: ShippingAddress
, billingAddress :: BillingAddress
, orderLines :: OrderLine
, amountToBill :: BillingAmount
}
data OrderLine = OrderLine
{ id :: OrderLineId -- エンティティのID
, orderId :: OrderId
, productCode :: ProductCode
, orderQuantity :: OrderQuantity
, price :: Price
}
newtype UnvalidatedOrder = UnvalidatedOrder
{ orderId :: String
-- , customerInfo :: ...
-- , shippingAddress :: ...
}
data PlaceOrderEvents = PlaceOrderEvents
{
-- acknowledgementSent :: ...
-- , orderPlaceId :: ...
-- , billableOrderPlaceId :: ...
}
newtype PlaceOrderError
= ValidationErrors ValidationError
-- ValidationError ValidationError とするとコンパイルエラー
-- | ...
data ValidationError = ValidationError
{ fieldName :: String
, errorDescription :: String
}
-- 「注文確定」プロセス
type PlaceOrder = UnvalidatedOrder -> Either PlaceOrderError PlaceOrderEvents
第6章
完全性(妥当性)
整合性
UnitQuantity module の実装
(素朴な)シグネチャ: int -> UnitQuantity
create 関数は、 int -> Result<UnitQuantiry, string>
takezaki.icon 危険な入力(int) の定義域を制限できないので、値域を「広げる」アプローチですね
F# は依存型をサポートしていない
kakkun61.icon Haskell でどうしても書きたいなら Liquid Haskell かな(篩型のある Haskell)
Liquid Haskell 初めて知った :eyes:
code:lean
def UnitQuantity := { x : Int // 1 < x ∧ x < 1000 }
#check (⟨10, by decide⟩ : UnitQuantity)
#check_failure (⟨1, by decide⟩ : UnitQuantity)
↑のコード https://live.lean-lang.org/#codez=CYUwZgBAqgdglgFwIoFcCGMGIJ4QFwC8EA3hAB74QCSmEA9HRAIwQA85Eg5EQftMAMAiAF8AUAGIAxgAsQEgNYQAFIAvyfgBoIAI1ygJcUIEvySrESoMWBNgCU46bLkB9MGjgAbFACcQS1Ru0RdfRAjPGh4ZHRMHBsgA
空ではないリスト
本文では自作 NonEmptyList を作っている
自作するとめんどい。ヘルパー関数とか山ほど生やす必要がある takezaki.icon
ライブラリの依存増やすのも面倒になる恐れがある。枯れたライブラリがあればいいが...
Haskell では標準のライブラリにある
https://hackage.haskell.org/package/base-4.21.0.0/docs/Data-List-NonEmpty.html
顧客がメールアドレスと住所の少なくとも一方を持つ
本文では EmailOnly | AddrOnly | EmailAndAddr で定義している
Haskell ではわざわざ these というパッケージがあったりします
https://hackage.haskell.org/package/these-1.2.1/docs/Data-These.html#t:These
いいね takezaki.icon
連絡手段が N パターンあったら 2^N - 1 か? やばい takezaki.icon
kakkun61.icon タプルが個数ごとに定義されるように個数ごとの定義が必要そう……
連絡先を NonEmpty な Set として表現しようにも、同じ連絡手段の重複を排除しなければならない
全部 option にして少なくとも一つが some である、という依存型を考えるのもあり?
綺麗にステートマシンに切り出すのはセンスがいるのでは、という雑談 by takezaki.icon
モバイルアプリの例
画面の状態は Initial, Loading, Success, Error くらいしかないと思いがちだが...
pull to refresh で部分的に再読み込みさせる, 特定の表示要素だけ失敗でも画面表示は継続したい, etc...
綺麗にステートマシンに切り出すのはセンスが必要そう
次回 7.3.2