《领域驱动设计15年》第8章

作者:Scott Wlaschin[1]

译者:封小武

校审:覃宇、伍斌

4. 用代数数据类型来建模

万事俱备,现在我们可以开始真正的建模了。让我们回顾一下文章开头那些对领域的描述,使用代数数据类型来为它们建模。

4.1. 为订单行建模

描述是这样的:“订单行包含订单编号、产品编号以及订购数量”。

描述中有三种不同的特定于领域的基本类型,我们先定义三种类型:

type OrderId = OrderId of int
type ProductId = ProductId of int
type OrderQty = OrderQty of int

然后定义一个记录,用 AND 把它们组合起来。

type OrderLine = {OrderId : OrderIdProductId : ProductIdOrderQty : OrderQty
}

4.2. 为联系信息建模

描述是这样的:“联系信息由人名和联系方式组成,联系方式可以是邮箱地址,也可以是电话号码”。

这里又出现了三种“基本”领域类型:

type PersonalName = PersonalName of string
type EmailAddress = EmailAddress of string
type PhoneNumber = PhoneNumber of string

接下来,我们可以定义一个选项是 EmailAddress 或 PhoneNumber 的选择类型。

type ContactMethod =| ByEmail of EmailAddress| ByPhone of PhoneNumber

最后,我们把 PersonalName 和 ContactMethod 组合成一个记录:

type ContactInformation = {Name : PersonalNameContactMethod : ContactMethod
}

4.3. 为买家建模

描述是这样的:“买家可能是一次性‘访客’,也可能已经在网站上注册了。注册过的买家会被分配客户编号,而一次性访客没有”

我们先定义两种不同的类型,分别代表两类访客,外加一个基本类型的 CustomerId:

type GuestPurchaser = {Name : PersonalNameContactMethod : ContactMethod
}type CustomerId = CustomerId of inttype RegisteredPurchaser = {Id: CustomerIdName : PersonalNameContactMethod : ContactMethod
}

然后,我们创建一个选择类型,它包括上面这两种选项。

type Purchaser =| Guest of GuestPurchaser| Registered of RegisteredPurchaser

4.4. 为邮箱地址建模

描述是这样的:“邮箱可能经过了验证也可能没有经过验证。经过验证的邮箱才可以接收密码重置邮件”。

区分 Unvalidated 邮箱和 Validated 邮箱非常重要——它们有着不同的业务规则,因此我们应该要定义两种不同的类型来代表两种邮箱,再用它们组成选择类型:

type UnvalidatedEmailAddress = UnvalidatedEmailAddress of string
type ValidatedEmailAddress = ValidatedEmailAddress of stringtype EmailAddress =| Unvalidated of UnvalidatedEmailAddress| Validated of ValidatedEmailAddress

我们可以把重置密码的流程写成这样:

type SendPasswordResetLink =ResetLinkUrl -> ValidatedEmailAddress -> output???

我们还不确定输出是什么,所以暂时不去定义。另外,邮件发送的细节(比如通过 SMTP 服务器发送)现在与领域模型还没什么关系,因此也不会在这里记录。

最后,我们可以将邮箱的验证过程记录如下:

type ValidationToken = ValidationToken of string // 一个没有字面意义的 tokentype ValidateEmailAddress =UnvalidatedEmailAddress -> ValidationToken -> ValidatedEmailAddress

上面这段代码读作:提供一个未经验证的邮箱地址和一个验证 token,我们就能得到一个已验证的邮箱地址。

4.5. 记录失败

但这里还存在问题,我们之前说过,验证操作可能失败。这就要在两种输出之间做出选择。如果一切顺利,得到的是 ValidatedEmailAddress;但如果出现错误,得到的则是某种错误消息。我们可以创建另一种代表验证结果的选择类型来解决这个问题:

type ValidationResult =| Ok of ValidatedEmailAddress| Error of string

然后我们就可以使用 ValidationResult 作为验证流程的输出了,用它来代替

 ValidatedEmailAddress。
type ValidateEmailAddress =UnvalidatedEmailAddress -> ValidationToken -> ValidationResult

如果我们想让可能发生的错误类型更加明确,可以使用另一种选择类型 ValidationError 来记录错误,然后在结果定义中使用这个类型:

type ValidationError =| WrongEmailAddress| TokenExpiredtype ValidationResult =| Ok of ValidatedEmailAddress| Error of ValidationError

这种表示“结果”的类型很常见,大多数函数式语言中都内建了类似的泛型类型,使用泛型的定义可能是:

type Result<'SuccessType,'FailureType> =| Ok of 'SuccessType| Error of 'FailureType

如果使用内建类型,验证流程的函数定义如下:

type ValidateEmailAddress =UnvalidatedEmailAddress -> ValidationToken -> Result<ValidatedEmailAddress,ValidationError>

上面的定义清楚地表达了验证可能会失败,以及可能出现的错误。而且,这是代码,不是文档,我们可以确保任何实现都能和设计准确地匹配。

4.6. 组合多个类型来勾勒领域模型

这种建模并不是“提前做大量设计”的重量级方法。恰恰相反,在与领域专家讨论时这种方法有助于齐心协力地绘制“设计草图”。

举个例子,假设我们要跟踪一家电子商务网站的支付业务。我们来看看如何在设计研讨时用代码描绘设计。

我们先从 CheckNumber 这样的基本类型包装器开始说起。它们就是前面讨论过的“基本类型”。使用包装器可以赋予它们有意义的命名,这样可以让它们更容易被其它领域模型理解。

type CheckNumber = CheckNumber of int
type CardNumber = CardNumber of string

我们接下来深入地讨论一下信用卡(Credit Card),这可能需要创建更多低层级的类型。CardType 是 OR 类型——它要在 Visa 或(OR)Mastercard 之间做出选择;CreditCardInfo 则是 AND 类型,它是一个包含 CardType 和(AND)CardNumber 的记录:

type CardType = Visa | Mastercardtype CreditCardInfo = {CardType : CardTypeCardNumber : CardNumber
}

我们了解到支付业务可以接受现金(Cash)、支票(Check)或信用卡(Card)三种支付方式,所以我们还需要再定义一个 OR 类型 PaymentMethod,它要在 Cash 或(OR)Check 或(OR)Card 之间作出选择。这不再是一个简单的“枚举”,因为有些选项有关联的数据:Check 要关联 CheckNumber,而 Card 则要关联 CreditCardInfo。

type PaymentMethod =| Cash| Check of CheckNumber| Card of CreditCardInfo

接下来我们要讨论的是金额,它需要定义的类型更多了,比如 PaymentAmount 和Currency:

type PaymentAmount = PaymentAmount of decimal // 必须是正数
type Currency = EUR | USD

最后才是位于最顶层的 Payment 类型,它是一个包含 PaymentAmount 和(AND)Currency 和(AND)PaymentMethod 的记录:

type Payment = {Amount : PaymentAmountCurrency: CurrencyMethod: PaymentMethod
}

建模到此为止。我们只用了大约 25 行代码就完成了领域建模,而这些代码定义出了一组非常有意义的类型,它们具备实现的指导意义。

当然,这些类型没有直接相关的行为,因为它们是函数式的模型,不是面向对象的模型。要记录它们可以采取的操作,我们可以改用函数类型的定义。

例如,如果我们想要说明有这么一种方法,它可以使用 Payment 类型来支付未付款账单,最终得到的结果是支付过的账单,我们可能会定义出下面这个函数类型:

type PayInvoice = UnpaidInvoice -> Payment -> PaidInvoice

这段代码可以读作:提供一个 UnpaidInvoice 和一个 Payment,就可以创建一个 PaidInvoice。

或者想要切换支付币种:

type ConvertPaymentCurrency = Payment -> Currency -> Payment

第一个 Payment 是输入,第二个参数(Currency)是转换后的币种,第二个 Payment 是输出——是切换之后的结果。

(本篇未完待续)

参考

  1. ^Scott Wlaschin is a developer, architect and author. He is the author of the popular F# site fsharpforfunandprofit.com, and the book "Domain Modeling Made Functional", published by Pragmatic Bookshelf.

订单编号的数据类型是什么_领域模型与代数数据类型(第三期)相关推荐

  1. python写入文本文件的数据类型必须是_三:python数据类型和文件操作

    8.必须要知道.会的字符串方法 import string #print(string.ascii_letters+string.digits)#大小写字母+数字 #print(name.find(' ...

  2. mysql数据类型单选类型_单选按钮的mysql数据类型是什么?

    我正在制作一个带有单选按钮的表单(见http://jsfiddle.net/mjmitche/3c6Mc/),用户主要通过单选按钮提交数据.单选按钮的值必须是/ type应该是什么"类型&q ...

  3. 自动匹配未认领订单编号_海量订单系统微服务开发:使用MongoDB支持海量数据...

    海量订单系统微服务开发 订单系统是电商平台中一个非常重要的组成部分,而且它还是一个具有巨大流量和高并发访问的系统,与订单相关的服务涉及库存.支付.物流等.在设计订单系统时,我们选择使用支持海量数据的N ...

  4. mysql 订单号主键_关于订单编号 业务主键的生成

    今天,项目经理咨询我订单编号的随机生成,希望有一个随机产生绝对不重复的办法,实在是没有....不敢打包票一定guid不产生重复,我这方面属于保守派,我所能想到的只有: 1.大多数程序员在用的单库单表单 ...

  5. php 不重复订单号,php如何生成不重复的订单号,php不重复订单编号

    php如何生成不重复的订单号PHP如何生成重复的订单号,php生成不重复订单号的方法如下:1.利用数据库的主键值生成自增订单号:2.实现"日期自增号"的订单号:3.生成随机订单号: ...

  6. 软件测试习题————1.录制登录—订票—退出代码。2.实现舱位随机选择。3.设置检查点检查订单编号格式是否正确,将测试结果采用自定义的方式写入测试报告。

    一.说明 下面提供了代码,需要完整的请到我本人的资源中点击03进行下载,使用的软件是三角型号标志的,录制和运行使用的一个小飞机,因为这个软件环境复杂,我把它删除了,需要使用的可以看一下 二.题目 测试 ...

  7. Android --- 订单编号怎样不重复?一秒钟如果有n个人同时下单怎么解决?凌晨12点限量抢购1000件商品,直到抢完为止订单编号怎么处理?

    今天在做项目的时候发现了这个问题,在我下单的时候,我是采用获取年月日时分秒的方式来生成订单编号,但是这种方式行不通,如果一秒钟有多个人下单的话,直接就会爆掉,比如:淘宝双十一,凌晨12点限量抢购100 ...

  8. asp按时间自动递增编号_Java秒杀系统实战系列-分布式唯一ID生成订单编号

    本文是"Java秒杀系统实战系列文章"的第七篇,在本文中我们将重点介绍 "在高并发,如秒杀的业务场景下如何生成全局唯一.趋势递增的订单编号",我们将介绍两种方法 ...

  9. zencart 如何修改在线人数和订单编号

    zencart 如何修改在线人数和订单编号 1 订单编号的修改办法 进入后台-工具(tool)-店铺管理器(store manager),里面有个重置当前订单号(Reset Current Order ...

最新文章

  1. 干货 | 为你解读34篇ACL论文
  2. SQL 必知必会·笔记5创建计算字段
  3. WIN API当中的堆管理,虚拟内存及常规复制,移动,填充代码
  4. mybatis学习6复杂查询之一对多的处理
  5. 读《图解HTTP》总结--第九章
  6. Win11系统设置绿色护眼模式的方法
  7. 系统安全:Nessus Home版安装使用
  8. oracle procedures批量删除带索引条件数据很慢_redis数据结构、持久化、缓存淘汰策略...
  9. mysql 最左_mysql索引最左匹配原则
  10. 学习如何在matlab用带通滤波器进行滤波
  11. 飞行器制导与控制及其Matlab仿真
  12. Python学习笔记:PYQT5 文字及绘图旋转
  13. 【Android -- 面试】简历模板
  14. mappedBy和JoinColumn实质上指向的是同一个表即外键作为主键所在的表对应的实体
  15. 计算机图形学基础如何学,计算机图形学基础思考
  16. 2011年9月30日
  17. linux 虚拟硬盘释放,虚拟机的磁盘碎片清理
  18. java 阿里云服务器流下载慢的可能原因
  19. 不在乎 -- 陆琪
  20. HUMAnN3的安装经验分享

热门文章

  1. 操作系统(6)-协程
  2. 以太坊—JSON RPC API
  3. Api容器在应用架构演化中的用途
  4. 单行函数(数值函数)
  5. 关于c3样式在浏览器上的兼容问题
  6. 【uva 1395】Slim Span(图论--最小生成树+结构体快速赋值 模版题)
  7. asp.net mvc 前台使用后台变量
  8. 连接上linux上的ip在哪个文件夹,linux – 当IP别名时,操作系统如何确定哪个IP地址将用作出站TCP / IP连接的源?...
  9. github 创建团队_如何为团队创建影响图
  10. RTFM? 如何写一本值得一读的手册