以 合同 - 产品 - 收入确认 示例来讲解领域逻辑模式

302 阅读7分钟

业务背景:

根据合同类型的不同,会有不同的产品,产品的不同,有不同的收入方式。已知每个合同的合同ID,假设产品类型现有的收入确认方式为(s->a),(w->b),(d->c)

(s->a)表示如果产品类型是s,那么收入确认的方式就是a,依次类推,一共有3中产品

数据库中的三张表: products 、contracts、revenueRecognitions

代码以说清楚为目的,不会完整写下实例,会用参合伪代码

事务脚本

使用过程来组织业务逻辑,每个过程处理来自表现层的单个请求。作者起名由来:一般一个数据库事务对应一个事务脚本

  1. 提供一个数据入口,来查找合同与对应的产品
 public ResultSet findContract(long contractId){

 	//将sql 查到的数据集返回 select * from contracts c , products p where id = contractId and c.product = p.id
 } 
  1. 领域层拿到对应的合同之后,然后按照合同的收入确认逻辑直接组织
public void calculateRevenueRecognitions(long contractId){

	ResultSet controcts = findContract(contractId);
	String productType = controcts.get("productType");
	if( "s".equals(productType) ){
		//执行 a 收入确认逻辑代码块
	}else if( "w".equals(productType) ){
		//执行 b 收入确认逻辑代码块
	}else if( "d".equals(productType) ){
		//执行 c 收入确认逻辑代码块
	}  
}

运行机制

在事务脚本中,领域逻辑主要由组织系统所执行的事务来组织。代码结构本身可以以合理的方式模块化。对于多个事务脚本常用的组织方式是:

  • 每个类围绕一个主题将相关的事务脚本放在一起
  • 每个事务脚本就是一个类,使用 命令模式 来实现

使用时机

少量逻辑的程序来讲,这种实现方式很自然,开发很快,性能以及后期维护理解开销都不大,但是如果业务复杂起来,由于事务脚本本身主要是为了处理一个事务,那么任何公共代码都可能存在多个副本,谨慎的提取公共模块可以解决问题,但再往复杂了去,要更好的组织代码,组织公共模块,需要领域模型

领域模型

合并行为和数据的领域的对象模型

  1. 产品的领域模型负责做不同产品的确认方式
class Product{

	private StrategyParent strategy;

	public static Product newS(){
		return new Product(new AStrategy());
	}

	public static Product newW(){
		return new Product(new BStrategy());
	}

	public static Product newD(){
		return new Product(new CStrategy());
	}

	public void calculateRevenueRecognitions(Contract contract){
		strategy.calculateRevenueRecognitions(contract);
	}
}
  1. 每个策略抽象出来了公共的父类 StrategyParent ,以及对应策略的实现类 AStrategy、BStrategy、CStrategy,这样就可以以插件的形式随时增加不同的策略就可以影响收入确认的方式

  2. 在合同里面计算收入产品的收入确认

class Contract{
	private Product product;

	//会有构造方法来传入合同

	public void calculateRecognitions(){
		product.calculateRevenueRecognitions(this);
	}
}
  1. 使用领域模型
class Test{

	public static void main(String[] args){
		Product s = Product.newS();
		Contract c=new Contract(s);
		c.calculateRevenueRecognitions();
	}
}

运行机制

建立一个完整的由对象组成的层,对目标业务进行领域建模,通过类之间的交互,来完成任务。他有两种风格

  1. 几乎每一个数据库表都对应一个对象,和数据库表类似,可以直接让领域对象本身去存储数据
  2. 使用继承、策略和其它设计模式,有多个互联的细粒度对象组成的复杂网络,需要数据映射器

对象之间的连续传递,本身就把处理逻辑递交给了“有资格”处理这件事情的对象,自身的调用链就是逻辑链,消除了很多条件判断,也提升了内聚,减少了不同对象之间的耦合。只是在阅读的时候需要不停的跳转不同的类来查看逻辑,而且一个领域本身有可能由于自身的业务过多而过于臃肿(实际中臃肿发生概率偏低,建议不要因为臃肿而强行分离,高出一段特殊处理的代码,而产生冗余逻辑,而应该先放到本来就应该在的对象中)

使用时机

如果业务规则复杂多变,涉及校验、计算、衍生应该用对象模型处理,反之只是做空值判断和少量求和计算,事务脚本是个更好的选择

表模块

以一个类对应数据库中的一个表来组织领域逻辑,而且使用单一的类实例包含将对数据进行的各种操作程序。表模块提供了明确的基于方法的接口对数据进行操作

  1. 从表中获取数据将数据记录到数据集 DataSet 中它类似于数据库结构

  2. 每一个表模块 TableModule 都拥有数据集中的一个表 DataTable

class TableModule{
	protected DataTable table;
	protected TableModule(DataSet ds,String tableName){
		table = ds.Tables[tableName];
	}
}

  1. 子类可以继承表模块,同时每个表模块类(子类也是一个)都可以拥有操作 DataTable 的能力

class Contract extends TableModule{
	public Contract(DataSet ds){
		super(ds,"contracts");
	}
	public DataRow thisRowById(long primaryKey){
		return table.select("ID = "+primaryKey)[0];
	}
}
  1. 通过多个表模块的行为来一起完成实际的收入确认工作
class  Contract {
	//...

	public void calculateRecognitions(long contractId){
		DataRow contractRow = thisRowById(contractId);

		//多个表模块公用一个数据集
		Product prod = new Product(table.DataSet);

		//从合同表模块中拿到对应的合同产品数据
		long prodId = GetProductId(contractId);

		//从产品表模块获取产品的数据类型
		String productType = prod.getProductType(prodId);

		//执行计算逻辑,逻辑中凡是涉及到需要操作数据的操作,也是通过表模块来完成
		if( "s".equals(productType) ){
		//执行 a 收入确认逻辑代码块
		}else if( "w".equals(productType) ){
			//执行 b 收入确认逻辑代码块
		}else if( "d".equals(productType) ){
			//执行 c 收入确认逻辑代码块
		}
	}
}
  1. 数据集的逻辑处理并校验正常之后,就可以存入数据库了

运行机制

表模块将数据与行为封装在一起,它可以是一个实例,也可以是一个静态方法的集合。典型的流程是,应用程序首先将数据汇集到一个记录集中,使用该记录集创建一个表模块,如果有多个表模块行为,则一起创建,这样表模块就可以在记录集上应用业务逻辑,然后将修改后的记录集传给表现层,表现层处理完后,数据集给表模块校验,并相应存入数据库

与常规对象的关键区别在于它本身没有标识符来表示它所代表的实体对象,通常需要数据库的主键值来查询对应的数据,比如 prodTableModule.getProductType(prodId)

使用时机

表模块依赖于以表的形式组织数据,适合于使用记录集存取表数据的情况,但是表模块没有提供面向对象能力来组织复杂的领域逻辑,不能在实例之间建立联系

服务层

通过一个服务层来定义应用程序边界,在服务层中建立一组可用的操作集合,并在每个操作内部协调应用程序的响应

  • 提供一个收入确认服务 RecognitionService来组织业务逻辑,具体的事情交给领域对象 Contract 去完成

public class ReconitionService{
	public void calculateRevenueRecognitions(long contractNumber){
		Contract contract = Contract.readForUpdate(contractNumber);
		contract.calculateRevenueRecognitions();
	}
}

运行机制

对于较大的应用,通过垂直企鹅人软件结构将它分成若干个“子系统”,每个子系统包含切出来的一个部分,每个子系统可以用该子系统名字来命名,可选的方案包括按照领域模型来划分 ContractService,ProductService 或者是按照程序行为主题来划分 ReconitionService

使用时机

如果业务逻辑只有一种或者响应不涉及多个事务性资源,就可能不需要服务层,但是只要有多种或多个事务性质资源,则有必要一开始就设计服务层

附录

<企业应用架构模式> 第九章