-
Notifications
You must be signed in to change notification settings - Fork 69
多研究些架构,少谈些框架(2)-- 微服务和充血模型 #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
一家之言: 面向对象这个思想也许是一个错误的思想.... 微服务 抛开面向对象思想, 面向数据 |
@XingZheFeng 你的担忧很有道理,我在文章里面也说了,这个世界是纯数学构建的,有一天我们可以用公式来精确的计算任何一个逻辑。就像围棋,我们把每个变化都算清楚就是围棋之神了, 但是现在我们还做不到,只能通过剪枝和蒙特卡洛加上深度学习去寻求近似解。 而面向对象、DDD本身是经验性的,不能严格证明。但是他是我看到的目前最接近业务逻辑近似解的方案了。 |
文章写的不错,求私聊 |
我是电子工业出版社博文视点策划编辑,微信yingzidd |
@yingzidd12 本文采用 CC BY 3.0 CN协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。 有什么问题可以邮件联系我。 |
划分业务的同时,也将存储模型根据业务划分,与业务逻辑构成一个 Bound Context,引入一个问题,这样的微服务是否具有良好的水平扩展性。 |
@magichan 实际上除非是图形处理这种消耗CPU的业务,我们90%以上的业务的性能瓶颈都在IO上,也就是存储上。这种情况下,你用容器创建更多的业务逻辑的微服务进程也是徒劳,并不能解决性能问题。但是如果BC和存储在一起,你反而可以有针对性的发现有性能瓶颈的BC,然后采用分区分片的方式,或者直接用NoSQL去存储热点的数据。不需要所有的数据库都跟着一起变化,充分发挥微服务独立松耦合的特性,去解决水平扩展。 |
赞,已关注已star |
很好的文章。我觉得“充血”模型这种说法是不合适的,模型本来就应该有合适的行为,贫不好,充也不好。 |
看到文中提到的“充血”例子,不可以通过Application中通过加数据库事务实现?忘指教,已经开发了N年的贫血了。 |
@hc24 充血模型已经把状态加载到内存中了,修改都是在自己的内存里面,再写到数据库的时候,必然会有并发的问题。这个缺陷也是充血模型必须付出的代价,任何技术都是有代价的。所以一般简单的基础业务,没有必要使用充血模型。 |
充血模型不等于一定要把状态加载到内存中去操作,重点在于把业务规则封装到对象中 |
【虽然我们用Java这样的面向对象语言来开发,但是其实和过程型语言是一样的,所以很多情况下大家用数据库的存储过程来替代Java写逻辑反而效果会更好】 |
@thefloe 貌似值对象 集合的话 会全部加载进来吧 |
贫血模型还有个很大的问题是可测试性太差 |
同意,在于行为抽象,具体持久化和防并发完全可以依靠于db。 |
多研究些架构,少谈些框架(1) -- 论微服务架构的核心概念
多研究些架构,少谈些框架(2)-- 微服务和充血模型
多研究些架构,少谈些框架(3)-- 微服务和事件驱动
多研究些架构,少谈些框架(2)-- 微服务和充血模型
2017-6-12 曹祖鹏
上篇我们聊了微服务的DDD之间的关系,很多人还是觉得很虚幻,DDD那么复杂的理论,聚合根、值对象、事件溯源,到底我们该怎么入手呢?
实际上DDD和面向对象设计、设计模式等等理论有千丝万缕的联系,如果不熟悉OOA、OOD,DDD也是使用不好的。不过学习这些OO理论的时候,大家往往感觉到无用武之地,因为大部分的Java程序员开发生涯是从学习J2EE经典的分层理论开始的(Action、Service、Dao),在这种分层理论中,我们基本没有啥机会使用那些所谓的“行为型”的设计模式,这里的核心原因,就是J2EE经典分层的开发方式是“贫血模型”。
Martin Fowler在他的《企业应用架构模式》这本书中提出了两种开发方式“事务脚本”和“领域模型”,这两种开发分别对应了“贫血模型”和“充血模型”。
事务脚本开发模式
使用这种开发方式,对象只用于在各层之间传输数据用,这里的对象就是“贫血模型”,只有数据字段和Get/Set方法,没有逻辑在对象中。
我们以一个库存扣减的场景来举例:
首先谈一下业务场景,一个下订单扣减库存(锁库存),这个很简单
先判断库存是否足够,然后扣减可销售库存,增加订单占用库存,然后再记录一个库存变动记录日志(作为凭证)
首先设计一个库存表 Stock,有如下字段
设计一个Stock对象(Getter和Setter省略)
设计一个StockService,在其中的lock方法中写逻辑
入参为(spuId, skuId, num)
实现伪代码
ok,打完收工,如果做的好一些,可以把update和select count合一,这样可以利用一条语句完成自旋,解决并发问题(高手)。
小结一下:
有没有发现,在这个业务领域非常重要的核心逻辑 -- 下订单扣减库存中操作过程中,Stock对象根本不用出现,全部是数据库操作SQL,所谓的业务逻辑就是由多条SQL构成。Stock只是CRUD的数据对象而已,没逻辑可言。
马丁福勒定义的“贫血模型”是反模式,面对简单的小系统用事务脚本方式开发没问题,业务逻辑复杂了,业务逻辑、各种状态散布在大量的函数中,维护扩展的成本一下子就上来,贫血模型没有实施微服务的基础。
虽然我们用Java这样的面向对象语言来开发,但是其实和过程型语言是一样的,所以很多情况下大家用数据库的存储过程来替代Java写逻辑反而效果会更好,(ps:用了Spring boot也不是微服务),
领域模型的开发模式
这样做下单锁库存业务逻辑的时候,每次必须先从Repository根据主键load还原Inventory这个对象,然后执行对应的lock(num)方法改变这个Inventory对象的状态(属性也是状态的一种),然后再通过Repository的save方法把这个对象持久化到存储去。
完成上述一系列操作的是Application,Application对外提供了这种集成操作的接口
领域模型开发方法最重要的是把扣减造成的状态变化的细节放到了Inventory对象执行,这就是对业务逻辑的封装。
Application对象的lock方法可以和事务脚本方法的StockService的lock来做个对比,StockService是完全掌握所有细节,一旦有了变化(比如库存为0也可以扣减),Service方法要跟着变;而Application这种方式不需要变化,只要在Inventory对象内部计算就可以了。代码放到了合适的地方,计算在合适层次,一切都很合理。这种设计可以充分利用各种OOD、OOP的理论把业务逻辑实现的很漂亮。
从上面的例子,在Repository的load 到执行业务方法,再到save回去,这是需要耗费一定时间的,但是这个过程中如果多个线程同时请求对Inventory库存的锁定,那就会导致状态的不一致,麻烦的是针对库存的并发不仅难处理而且很常见。
贫血模型完全依靠数据库对并发的支撑,实现可以简化很多,但充血模型就得自己实现了,不管是在内存中通过锁对象,还是使用Redis的远程锁机制,都比贫血模型复杂而且可靠性下降,这是充血模型带来的挑战。更好的办法是可以通过事件驱动的架构来取消并发。
领域模型和微服务的关系
上面讲了领域模型的实现,但是他和微服务是什么关系呢?在实践中,这个Inventory是一个限界上下文的聚合根,我们可以认为一个限界上下文是一个微服务进程。
不过问题又来了,一个库存的Inventory一定和商品信息是有关联的,仅仅靠Inventory中的冗余那点商品ID是不够的,商品的上下架状态等等都是业务逻辑需要的,那不是又把商品Sku这样的重型对象引入了这个微服务?两个重型的对象在一个服务中?这样的微服务拆不开啊,还是必须依靠商品库?!
请参考下一篇,通过事件驱动架构来完成领域间的松耦合。
版权说明
本文采用 CC BY 3.0 CN协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。
关注我
微信公众号

The text was updated successfully, but these errors were encountered: