设为首页 加入收藏

TOP

零开始的领域驱动设计(一)
2019-09-17 17:35:58 】 浏览:70
Tags:开始 领域 驱动 设计
 
 
领域驱动的火爆程度不用我赘述,但是即便其如此得耳熟能详,但大多数人对其的认识,还只是停留在知道它的缩写是DDD,知道它是一种软件思想,或者知道它和微服务有千丝万缕的关系。Eric Evans对DDD的诠释是那么地惜字如金,而我所认识的领域驱动设计的专家又都是行业中的资深前辈,他们擅长于对软件设计进行高屋建瓴的论述,如果没有丰富的互联网从业经验,是不能从他们的分享中获取太多的营养的,可以用曲高和寡来形容。1000个互联网从业者,100个懂微服务,10个人懂领域驱动设计。可能有很多和我一样的读者,在得知DDD如此火爆之后,尝试去读了开山之作《领域驱动设计——软件核心复杂性应对之道》,翻看了几张之后,晦涩的语句,不明所以的专业术语,加上翻译导致的语句流畅性,可以说观看体验并不是很好,特别是对于开发经验不是很多的读者。我总结了一下,为何这本书难以理解: 
 

1. 没有阅读软件设计丛书的习惯,更多人偏向于阅读偏应用层面的书籍,“talk is cheap,show me the code”往往更符合大多数人的习惯。 

2. 没有太多的开发经验支撑。没有踩过坑,就不会意识到设计的重要性,无法产生共情。 

3. 年代有些久远,这本书写于2004年,书中很多软件设计的反例,在当时是非常流行的,但是在现在已经基本绝迹了。大师之所以为大师,是因为其能跨越时代的限制,预见未来的问题,这也是为什么DDD在十几年前就被提出,却在微服务逐渐流行的现阶段才被大家重视。

诚然如标题所示,本文是领域驱动设计的一个入门文章,或者更多的是一个个人理解的笔记,笔者也正在学习DDD的路上,可能会有很多的疏漏。如有理解有偏颇的地方,还望各位指摘。
 
认识领域驱动设计的意义 领域驱动设计并不会绝对地提高项目的开发效率。 

图1:复杂性与开发周期关系

 

遵循领域驱动设计的规范使得项目初期的开发甚至不如不使用它来的快,原因有很多,程序员的素质,代码的规范,限界上下文的划分…甚至需求修改后导致需要重新建模。但是遵循领域驱动设计的规范,在项目越来越复杂之后,可以不至于让项目僵死。这也是为什么很多系统不断迭代着,最终就黄了。书名的副标题“软件核心复杂性应对之道”正是阐释了这一点。

 

模式: smart ui是个反模式

可能很多读者还不知道smart ui是什么,但是在这本书写作期间,这种设计风格是非常流行的。在与一位领域驱动设计方面的资深专家的交谈中,他如下感慨到软件发展的历史:

2003年时,正是delphi,vb一类的smart ui程序大行其道,Java在那个年代,还在使用jsp来完成大量的业务逻辑操作,4000行的jsp是常见的事;2005年spring hibernate替换了EJB,社区一片欢呼,所有人开始拥护action,service,dao这样的贫血模型(充血模型,贫血模型会在下文论述);2007年,Rails兴起,有人发现了Rails的activeRecord是涨血模型,引起了一片混战;直到现在的2017年,微服务成为主流系统架构。

在现在这个年代,不懂个MVC分层,都不好意思说自己是搞java的,也不会有人在jsp里面写业务代码了
(可以说模板技术freemarker,thymeleaf已经取代jsp了),
但是在那个年代,还没有现在这么普遍地强调分层架构的重要性。

这个章节其实并不重要,因为mvc一类的分层架构已经是大多数java初学者的“起点”了,大多数DDD的文章都不会赘述这一点,我这里列出来是为了让大家知晓这篇文章的时代局限性,在后续章节的理解中,也需要抱有这样的逻辑:这本书写于2004年。

模式: Entity与Value Object

我在不了解DDD时,就对这两个术语早有耳闻。entity又被称为reference object,我们通常所说的Java bean在领域中通常可以分为这两类,(可别把value object和常用于前台展示的view object,vo混为一谈) 
entity的要义在于生命周期和标识,value object的要义在于无标识,通常情况下,entity在通俗意义上可以理解为数据库的实体,(不过不严谨),value object则一般作为一个单独的类,构成entity的一个属性。

举两个例子来加深对entity和value object的理解。

例1:以电商微服务系统中的商品模块,订单模块为例。将整个电商系统划分出商品和订单两个限界上下文(Bound Context)应该是没有争议的。如果是传统的单体应用,我们可以如何设计这两个模块的实体类呢? 
会不会是这样?

复制代码
class Product{
    String id;//主键
    String skuId;//唯一识别号
    String productName;
    Bigdecimal price;
    Category category;//分类
    List<Specification> specifications;//规格 
    ... 
}

class Order{
    String id;//主键
    String orderNo;//订单号
    List<OrderItem> orderItems;//订单明细
    BigDecimal orderAmount;//总金额
    ...
}

class OrderItem{
    String id;
    Product product;//关联商品
    BigDecimal snapshotPrice;//下单时的价格
}
复制代码

 

看似好像没问题,考虑到了订单要保存下单时候的价格(当然,这是常识)但这么设计却存在诸多的问题。
在分布式系统中,商品和订单这两个模块必然不在同一个模块,也就意味着不在同一个网段中。
上述的类设计中直接将Product的列表存储到了Order中,也就是一对多的外键关联。这会导致,每次访问订单的商品列表,都需要发起n次远程调用

反思我们的设计,其实我们发现,订单BC的Product和商品BC的Product其实并不是同一个entity,在商品模块中,我们更关注商品的规格,种类,实时价格,这最直接地反映了我们想要买什么的欲望。而当生成订单后,我们只关心这个商品买的时候价格是多少,不会关心这个商品之后的价格变动,还有他的名称,仅仅是方便我们在订单的商品列表中定位这个商品。

如何改造就变得明了了

复制代码
class OrderItem{
    String id;
    String productId;//只记录一个id用于必要的时候发起command操作
    String skuId;
    String productName;
    ...
    BigDecimal snapshotPrice;//下单时的价格
}
复制代码
 
         

是的,我们做了一定的冗余,这使得即使商品模块的商品,名称发生了微调,也不会被订单模块知晓。这么做也有它的业务含义,用户会声称:我买的时候他的确就叫这个名字。记录productId和skuId的用意不是为

首页 上一页 1 2 3 4 下一页 尾页 1/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇什么是架构属性 下一篇[原创]分布式系统之缓存的微观应..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目