长期以来我都在实践OOP,进而经过OOP来实现DDD,特别是如何经过面向对象的技巧来创建一个领域模型。OO的一些特性在创建领域模型时显得恰如其分,可否掌握OO的技巧,对建立领域模型有着相当重要的做用。
这篇文章为你们介绍一种常见的函数式架构,特别是如何经过函数式语言来实现DDD,进而利用函数式组合的特性,建立函数pipeline。
软件架构是围绕着领域模型而作的若干设计,若是按照c4模型的定义,软件架构由下面四个级别的架构组成的:html
领域驱动设计中有一半概念是在讨论问题域,并非一上来就教你如何写代码,这说明理解一个问题域是复杂的,看清问题的本质是须要时间的。当你开始着手划分限界上下文的时候,说明你已经对需求有了很好的了解。可是经验告诉咱们,刚开始你的理解,每每都不是最终的需求,或者仍然须要屡次跟领域专家确认和交互,才能获得最终的需求。
这个时候,若是你一上来就按照限界上下文划分微服务,每每可能会步入Microservice Premium。
要想软件在一开始就能达到快速试错的目的,一上来就作微服务, 会让步子迈得有点大。微服务架构带来了分布式的复杂性,使得前期生产效率大大下降,另外还存在船大难掉头的状况,一旦设计出现返工,生产效率也会打折扣。固然,这不是绝对的,若是架构师已经在该行业深耕多年,对业务更是了如指掌,项目一开始就设计为微服务也何尝不可。
在项目初期,在需求还不是很是明确的时候,你彻底能够建立一个单体应用,而后经过不一样的模块或程序集来隔离不一样的界限上下文,经过不断的试错和快速反馈来调整你的解决方案。
一种比较严格的说法是,当你关闭其中一个微服务,若是整个应用程序都崩了,其实你设计的不是一个微服务架构,而是一个分布式单体应用程序。git
在过去的若干年里,我常用一种叫“Layer architecture"的软件架构, 这种架构每每把代码分红若干层:web
在问题域里,各类业务之间的边界是模糊的,限界上下文则是业务在解决方案上的映射,是人为划分的边界。在边界里面的内容,是可信任和合法的,相反,界限外面的一切输入,则是非法和不可信任的(图3)。
这就要求咱们在限界上下文的边界,引入验证逻辑,从而阻止外部输入,以及验证对外部的输出。
常见的验证逻辑如:typescript
纵然,经过FP的代数数据类型(Algebraic data type)可以快速完成领域建模,可是咱们知道,领域模型不是静态的,它是由一些列事件组成的过程。而这种转化过程,正是领域模型状态发生变化的过程,即状态机(图4)。
领域模型状态转换的过程跟实现语言无关,一个设计精良的领域模型,就比如一个状态机。例如在买机票的过程当中,填写我的信息,填写联系人,选座,买保险和付款的过程,就是订单状态发生变化的过程。再好比用户注册的过程,填写基本信息,验证邮箱,也是用户信息状态发生变化的过程。以OO为例,咱们习惯于经过增长标志位的方式,进行领域建模:数据库
type User = { name: string password: string email: Email | null isEmailVerified: boolean //当验证完email后设置为true canLogin: boolean //当email被验证后方可login }
业务逻辑的实现过程,就是填充用户属性和修改标志位的过程。然而,这种方式实际上存在若干问题:编程
按照上面的状态从新对用户建模,获得的模型以下:json
type UnVerifiedUser = { name: string password: string } type VerifiedEmailUser = { name: string password: string email: Email } type User = | UnVerifiedUser | VerifiedEmailUser
若是有更多的用户状态,你还能够持续添加到User类型中。
这种经过"|"建立的User类型被称为在FP中被称为union类型,也叫product或sum类型, 在TypeScript被称为Discriminated union。这时候的User类型,能够用来在领域模型中实现领域逻辑,一般这种union类型须要配合模式匹配来完成,例如修改密码,登陆,修改邮件地址等逻辑,都是针对User类型作模式匹配的过程。关于模式匹配的用法,在此再也不细说。
这种经过状态机的方式,实现业务逻辑时有下面几个好处:数组
函数式编程的一个主要目标就是让代码有预测性,经过函数签名理解函数的用途。为了达到这个目的,函数式语言设计了若干特性,例如不可变的数据结构,还有各种Monad来避免反作用。在DDD实践中,应该避免I/O相关的代码出现Domain中。例如读写数据库,调用第三方系统的API等相关代码,须要把这类具备反作用的代码推到Domain的外围。若是须要作的更好,那就必须使用CQRS加Event Sourcing。我在以前一篇文章提到过这个观点,不过部分读者没有理解其中的意思,我在这里再作一些说明。首先,CQRS不只仅是为了读写分离,从而提升读写性能。读模型和写模型(领域模型)的分离意味着职责也是分离的,从而在设计领域模型的时候,打消对查询性能的考虑,有助于设计出纯净的领域模型。固然仅靠CQRS仍是不够的,有些时候任然没法彻底脱离数据库的考虑,由于领域模型始终是要持久化在数据库里,你就要考虑数据库相关的约束,例如主外键,如何建表,如何高效存储一个列表等。而持久化一个Event则彻底摆脱了数据库技术,由于一个Event就是一个json, 只有这样才能设计出理想的领域模型。固然引入CQRS和ES在项目初期成本略高,再也不详细描述。安全
以API为例,一个完整的用户请求就是一个Pipeline(图6)。
假设每一步都是有若干个函数组成,咱们可以将他们组合到一块儿吗?答案是很难,主要缘由以下:数据结构
这篇文章总结了一些使用函数式语言实践DDD的大体思路,也为函数式架构提供了一些参考。因为篇幅的缘由,并无介绍到DDD的方方面面,同时,一些实现细节则是点到为止,例如如何使用Monad。整体来讲,函数式语言的代数数据类型,以及函数式的一些思想,为实践领域驱动设计提供了其余的选项。