本系列目录:java
基于上面提到的读写隔离的思想,那么咱们能够很清楚地看到上面这种状况能够看到:数据库
查询业务,从入口层(如Controller),调用Finder
,而Finder
调用Repository
(具体实现如Hiberante,Mybatis等等都可),这一条线下来,咱们全然不用考虑这个系统的增删改就是如何作的,就像他们彻底处于不一样的空间同样,互不干涉,互不影响,甚至,永远互不相见。 某种程度上来讲,这种这种架构追求的效果,一种美感。安全
因此,接下来,咱们关注的,就是增删改这一部分了,也就是命令操做 是开始要扎扎实实地来对这个系统进行修改了架构
首先让咱们把视野抬高一些,从整个项目产品的上空来看看app
除开少数很是扁平的纯技术服务项目(好比AI识别,文本分析等等),其余绝大部分企业项目都有其核心的商业逻辑,而这些逻辑,每每也会以核心的领域概念来提现,从简单粗暴一点角度映射到设计开发中,那就是类class
框架
而这其中,也有主次,你们能够回头看看本身所开发过的项目。 但凡有状态字段的类,很大可能都是整个项目的核心领域之一。 其实很好理解,由于它有流程,由于它须要被各种操做来变动它的状态,因此,他极可能贯穿了这个项目中某一个关键商业逻辑,好比电商系统中,编码
因此,若是在这些系统的早期设计阶段,要我选择一个最重要的UML图,我会选择状态图,如下就是整个系统中最核心的订单状态图.net
能够看到,把握一个核心领域的状态变动,天然而然就能概括出来很大一部分系统的功能需求。咱们在这里看到,这些全部箭头所触发的动做,其实都是命令,也其实都是会落地到各个相关领域的增删改上。设计
固然这里仍是一个粗粒度的表示,没法单单依据这个就立刻落地开发,由于即便每个箭头所表明的功能均可以写出一个完整甚至很复杂的用例。但至少这是一个很是清晰的引导。code
咱们目前所用的Spring体系,几乎都是贫血模型,也就是说,真正的实体类里,都只有各个属性的Get与Set方法。 而假如咱们要进行一个操做,订单取消
,那么最多见的作法是什么?
//一个大而全的订单服务类 public class OrderService{ public void cancelOrder(Long orderId){ Order order = orderRepository.getById(orderId); order.setStatus(OrderStatus.CANCELLED); //省略,其余属性的操做... } } //而后在上层(如Controller层)中这么调用 orderService.cancelOrder(10086);
这是目前行业中很是流行的作法,也是Spring的IOC机制自然造成的作法————尽量的无状态化。这种作法,在业务迭代时对代码的变更评判标准相对简单,都往Service里放就好了,而后实体对象只须要GetSet便可,简单粗暴,很是容易上手,也正是这种特性,让这种编码风格广为流传。
以上这些话没有任何贬义,由于任何事情,存在即合理,我所经历的公司项目,几乎都是这样作的,你们合做起来没多大问题,业务也都还跑得不错。
那为何我还想去作一些改变呢?
由于我以为咱们须要再从新审视一下实体Entity
实体为何要有主键? 由于没有主键,那咱们怎么知道时要查询/修改哪条数据呢?
这个回答没有问题,只是这句话里其实还蕴藏更深的含义
因此,从“拿取”,到“操做”,这两步,一切瓜熟蒂落,行云流水,因此,以领域驱动设计的作法,或者说,充血模型的作法,会是这样:
//应用层入口类,这里以Controller为例 public class OrderController{ @PostMapping("/cancel") @Transactional public ActionResponse cancelOrder(@RequestBody CancelOrderRequest request){ //拿取:根据标识符定位到咱们要操做的实体 Order order = orderRepository.getById(request.getOrderId()); //操做:对,没错,说的就是你 order,就是对你,进行操做,不是别人! order.cancel(); //返回结果 return ActionResponse.ok(); } } //真正的业务逻辑,就是在Order实体里 @Entity public class Order{ private OrderStatus status; private String customerName; //... public void cancel(){ //变动状态 status = OrderStatus.CANCELLED; //一些其余属性变更,略 } }
好,依旧有很多值得探讨的地方:
最更容易引起槽点的地方,就是order.cancel()
,也就是充血模型的精髓,将行为定位到一个实体类上,而不是不加思考地直接扔进OrderService
里。
业界一直有一种很是“美妙”地说法,曾经我一度很是向往,就是“让代码成诗”。 换句话说,就是既然追求可读性,那么咱们要尽量的让代码自然具备一种“主谓宾”的感受,就拿上面“取消订单”作比方,咱们是否会以为:
订单好端端的在那里放着,它本身又不能对本身作什么,天然应该“别人”对他进行了操做: OrderService.cancel(orderId); 某某某 取消了 这个订单 Perfect! 这样读起来,才很是通顺,可读性才更好!
我曾经也是这种风格死忠,而Spring广为流传的无状态架构模式也将这种风格发扬光大。 只是我如今,在经历了愈来愈多复杂业务,长事务的开发需求后,愈来愈以为,这个还有有些硬伤
若是必定要读得通畅,更应该是someOperator.cancel(orderId)
即某个操做人取消了订单,而不是OrderService
,谁都知道OrderService
就是一个无状态的代码大集合,一个冰冷的代码而已。但显然someOperator.cancel(orderId)
这种作法也是更加不可能实现的,缘由就不用过多解释了。
order.cancel()
,只有2个部分,{操做目标是谁}.{作了什么事情}
,清晰明了,言简意赅。我相信绝大多数人的阅读习惯也都是从左往右,那么视线第一下扫到的目标必定是最左边的执行对象,也就是order
,那么能够在第一时间明确,这个行为是发生在谁身上,而若是是orderService.cancel(orderId)
,无形中,orderService
是一个占据了视线最有力位置的一个巨大的噪点——由于它没有任何的业务意义,你要看的,反而是后面的方法和参数,这在阅读上百行甚至几百行的复合长业务的时候,你会很快困顿,迷失方向。不少时候,咱们真的不是技术不达,而是身心疲惫。