工具人发现一个很有意思的现象,在传统企业中,由于成果导向不同,所以采购师和工具人对产品功能的审美是截然相反的。
采购师以高颜值为价值取向,内在腐朽不重要,无论是加拿大火腿肠还是墨西哥鸡肉卷,只要能评奖,就是好产品。

而工具人则以内在美为审美标准,没有优秀的内部设计,再光鲜的面皮,也难逃照妖镜的聚光。

所以,工具人推荐使用 DDD的缘由在于可以管理业务的复杂度,避免在业务规则愈发复杂的状况下代码以及架构发生腐化,变的难以维护,最终退化成下一个WuYF。
大多数业务系统的复杂度体现在多个层面,比如:繁琐的流程,繁复的校验规则,数据的多样性等。
DDD 对于不一样层面的复杂度提供了不一样的应对模式,今天我们聊了解下规约模式( Specification 模式)是如何解决业务规则的复杂性的。
常见 html
常见的业务规则
作为一个CRUD工具人,日常工作经常会遇到如下场景:
校验业务对象的某些状态是否合法,例如理财服务中,常常需要校验理财产品是否处于申购或认购期,认购额度是否充足,风险等级是否匹配等。
从业务对象的集合中筛选出符合条件的结果集,例如行情服务中,从所有的股票标的中找出融资融券标的。
检查一个新建立的业务对象是否符合某些业务条件,例如卡券系统中,给用户派发一种优惠券,它对应的客户与产品都应该是系统的合法用户和在售产品。
以往我们常常会在Domain service中编写一些简单方法和校验逻辑,但是由于它们被分散在各处,当业务越来越复杂、不同的场景这些规则需要多种不同的排列组合关系时,传统的模式就会变得难以管理。
就像刚才我们提到的理财业务,其校验规则会比较复杂,例如理财购买,你可能需要验证用户账号是否可用、理财产品的剩余额度是否充足、产品是否处于认购期,与用户的风险等级是否匹配等等……仅仅是通过一连串的if判断,那就真的太不利于维护了,并且if嵌套的多了代码难于理解,不好说明白具体意图,而简单短小的方法,若是任其分散在不同的 Domain Service 中,后续的开发过程当中就这些业务知识就很容易被丢失。因此需要将隐式业务规则转换成显示概念,这也是DDD的要求。
规约模式提炼隐式规则
而规约模式经常在DDD中使用,用来将业务规则(通常是隐式业务规则)封装成独立的逻辑单元,从而将隐式业务规则提炼为显示概念,并达到代码复用的目的。
DDD 中认为这些规则都是纯粹的“动词”,所以需要单独的创建模型,而这些模型都应该是简单的值对象。
首先,定义规约接口:
public interface ISpecification<T> {boolean isSatisfied(T entity);}
以风险等级规约为例:
public class FinanceRiskSpecification implements ISpecification<FinanceProduct> {Customer customer;public FinanceRiskSpecification(Customer customer) {this.customer = customer;}@Overridepublic boolean isSatisfied(FinanceProduct financeProduct) {return financeProduct.getRiskLevel().compareTo(this.customer.getRiskLevel()) > 0;}}
同样,我们可以实现认购额度规约等:
public class FinanceQuotaSpecification implements ISpecification<FinanceProduct> {double purchaseQuota;public FinanceRiskSpecification(double purchaseQuota) {this.purchaseQuota = purchaseQuota;}@Overridepublic boolean isSatisfied(FinanceProduct financeProduct) {return financeProduct.getRestQuota() > purchaseQuota;}}
理财购买是一种组合规约的场景,多个规约需要同时满足,所以我们可以对规约模式进行扩展:
public class AndSpecification<T> implements ISpecification<T> {private ISpecification<T> left;private ISpecification<T> right;public AndSpecification(ISpecification<T> left, ISpecification<T> right) {this.left = left;this.right = right;}@Overridepublic boolean isSatisfied(T entity) {return this.left.isSatisfied(entity)&& this.right.isSatisfied(entity);}}
组合使用多个规约:
ISpecification<FinanceProduct> compositSpecification =new AndSpecification<FinanceProduct>(new FinanceRiskSpecification<FinanceProduct>(XXX),new FinanceQuotaSpecification<FinanceProduct>(1000.00));
规约模式过滤查询
从业务对象的集合中筛选出符合条件的结果集,也是工具人日常工作中的常见场景。传统模式中,当数据量很大的情况下,我们常常将逻辑下沉至DAO,直接在SQL中完成。如查询所有的融资融券股票:
// 以下代码为工具人 YYselect * from t_securitywhere t.margin_trade = '1' //融资融券标识and security_type = 'EQUIT' //证券类型为股票and list_status = '1'; // 标的状态为上市
其实,这部分的逻辑原本应该是属于领域层的,如今却泄漏到了数据层,形成的后果就是维护的难度大大提高,不少业务系统到后期都是在和大段大段的 SQL 作斗争,而应该编写逻辑的 Service 层,Domain 层都成了摆设,退化成了纯粹的数据对象,又回到了传统模式的老路。而 规约 模式能够提供一种不错的解决思路。我们可以为每个规约增加一个queryWrapper的方法,返回一个类似查询包装对象(这里和使用的ORM框架有点耦合,工具人这里举例的是baomidou)。
public interface ISpecification<T> {boolean isSatisfied(T entity);QueryWrapper<T> queryWrapper(QueryWrapper<T> tQueryWrapper);}
构建查询股票型基金产品的规约:
public class FinanceInvestStyleSpecification implements ISpecification<FinanceProduct> {String investStyle;QueryWrapper<FinanceProduct> tQueryWrapper;public FinanceRiskSpecification(String investStyle,QueryWrapper<FinanceProduct> tQueryWrapper) {this.investStyle = investStyle;this.tQueryWrapper = tQueryWrapper;}....@Overridepublic QueryWrapper<FinanceProduct> queryWrapper() {return tQueryWrapper.eq("invest_style", investStyle);}}
在理财产品仓库,我们则可以用findBySpecifications,统一抽象该类查询
public List<FinanceProduct> queryProductBySpec(ISpecification<FinanceProduct> spec)
最后,如果台风天气下,上班的工具人,请一定注意安全。





