业务背景:
根据合同类型的不同,会有不同的产品,产品的不同,有不同的收入方式。已知每个合同的合同ID,假设产品类型现有的收入确认方式为(s->a),(w->b),(d->c)
(s->a)表示如果产品类型是s,那么收入确认的方式就是a,依次类推,一共有3中产品
数据库中的三张表: products 、contracts、revenueRecognitions
代码以说清楚为目的,不会完整写下实例,会用参合伪代码
使用过程来组织业务逻辑,每个过程处理来自表现层的单个请求。作者起名由来:一般一个数据库事务对应一个事务脚本
public ResultSet findContract(long contractId){
//将sql 查到的数据集返回 select * from contracts c , products p where id = contractId and c.product = p.id
}
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 收入确认逻辑代码块
}
}
在事务脚本中,领域逻辑主要由组织系统所执行的事务来组织。代码结构本身可以以合理的方式模块化。对于多个事务脚本常用的组织方式是:
少量逻辑的程序来讲,这种实现方式很自然,开发很快,性能以及后期维护理解开销都不大,但是如果业务复杂起来,由于事务脚本本身主要是为了处理一个事务,那么任何公共代码都可能存在多个副本,谨慎的提取公共模块可以解决问题,但再往复杂了去,要更好的组织代码,组织公共模块,需要领域模型
合并行为和数据的领域的对象模型
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);
}
}
class Contract{
private Product product;
//会有构造方法来传入合同
public void calculateRecognitions(){
product.calculateRevenueRecognitions(this);
}
}
class Test{
public static void main(String[] args){
Product s = Product.newS();
Contract c=new Contract(s);
c.calculateRevenueRecognitions();
}
}
建立一个完整的由对象组成的层,对目标业务进行领域建模,通过类之间的交互,来完成任务。他有两种风格
对象之间的连续传递,本身就把处理逻辑递交给了“有资格”处理这件事情的对象,自身的调用链就是逻辑链,消除了很多条件判断,也提升了内聚,减少了不同对象之间的耦合。只是在阅读的时候需要不停的跳转不同的类来查看逻辑,而且一个领域本身有可能由于自身的业务过多而过于臃肿(实际中臃肿发生概率偏低,建议不要因为臃肿而强行分离,高出一段特殊处理的代码,而产生冗余逻辑,而应该先放到本来就应该在的对象中)
如果业务规则复杂多变,涉及校验、计算、衍生应该用对象模型处理,反之只是做空值判断和少量求和计算,事务脚本是个更好的选择
以一个类对应数据库中的一个表来组织领域逻辑,而且使用单一的类实例包含将对数据进行的各种操作程序。表模块提供了明确的基于方法的接口对数据进行操作
class TableModule{
protected DataTable table;
protected TableModule(DataSet ds,String tableName){
table = ds.Tables[tableName];
}
}
class Contract extends TableModule{
public Contract(DataSet ds){
super(ds,"contracts");
}
public DataRow thisRowById(long primaryKey){
return table.select("ID = "+primaryKey)[0];
}
}
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 收入确认逻辑代码块
}
}
}
表模块将数据与行为封装在一起,它可以是一个实例,也可以是一个静态方法的集合。典型的流程是,应用程序首先将数据汇集到一个记录集中,使用该记录集创建一个表模块,如果有多个表模块行为,则一起创建,这样表模块就可以在记录集上应用业务逻辑,然后将修改后的记录集传给表现层,表现层处理完后,数据集给表模块校验,并相应存入数据库
与常规对象的关键区别在于它本身没有标识符来表示它所代表的实体对象,通常需要数据库的主键值来查询对应的数据,比如 prodTableModule.getProductType(prodId)
表模块依赖于以表的形式组织数据,适合于使用记录集存取表数据的情况,但是表模块没有提供面向对象能力来组织复杂的领域逻辑,不能在实例之间建立联系
通过一个服务层来定义应用程序边界,在服务层中建立一组可用的操作集合,并在每个操作内部协调应用程序的响应
public class ReconitionService{
public void calculateRevenueRecognitions(long contractNumber){
Contract contract = Contract.readForUpdate(contractNumber);
contract.calculateRevenueRecognitions();
}
}
对于较大的应用,通过垂直企鹅人软件结构将它分成若干个“子系统”,每个子系统包含切出来的一个部分,每个子系统可以用该子系统名字来命名,可选的方案包括按照领域模型来划分 ContractService,ProductService 或者是按照程序行为主题来划分 ReconitionService
如果业务逻辑只有一种或者响应不涉及多个事务性资源,就可能不需要服务层,但是只要有多种或多个事务性质资源,则有必要一开始就设计服务层
<企业应用架构模式> 第九章