领域驱动设计-贫血模型VS充血模型

项目实现方式

事务脚本

事务脚本的核心是过程,通过过程的调用来组织业务逻辑,每个过程处理来自表现层的单个请求。大部分业务应用都可以被看成一系列事务,从某种程度上来说,通过事务脚本处理业务,就像执行一条条Sql语句来实现数据库信息的处理。事务脚本把业务逻辑组织成单个过程,在过程中直接调用数据库,业务逻辑在服务(Service)层处理

领域模型

领域模型的特点也比较明显, 属于面向对象设计,领域模型具备自己的属性行为状态,并与现实世界的业务对象相映射。各类具备明确的职责划分,领域对象元素之间通过聚合和引用等关系配合解决实际业务应用和规则。可复用,可维护,易扩展,可以采用合适的设计模型进行详细设计。缺点是相对复杂,要求设计人员有良好的抽象能力。

贫血模型VS充血模型

贫血模型

所谓贫血模型,是基于数据库的建模(Database Modeling)。

通过数据抽象系统关系,即传统数据库分析设计。该种模式实际上就是一种典型的贫血模型,通过数据库映射的实体类只有对应的属性,成为了只有getter和setter方法的数据载体,而没有具体行为,相应的行为要通过Service层去实现,随着业务升级积累,会出现胖服务层和贫血的领域模型,维护起来会越发乏力。即便如此,该种模式仍是广泛应用在软件开发领域。

充血模型

所谓充血模型,基于对象的建模: Object Modeling

通过面向对象方式抽象系统关系,也就是面向对象设计。毫无疑问,在面向对象编程环境中,面向对象无疑是领域建模最佳方式。通过面向对象构建的领域模型,因为有类的继承、封装、多态等特性显得生动许多,不仅包含自身属性状态,还包括有方法行为等,即充血的领域模型。一些Service层的行为凝练为领域服务,Service层则变薄了,领域模型则丰富了行为。

举例说明

我们通过一个例子,展现一个贫血模型与充血模型中显而易见的差别。 场景:网上转账

采用贫血模型实现

贫血模型是我们常常采用的MVC的三层架构

Dao 持久层

贫血对象-AccountEntity

/**
 * 账号Entity
 * @author yangyanping
 * @date 2021-08-30
 */
@Data
public class AccountEntity {
    private Integer id;

    /**
     * 账号
     */
    private Integer accountNum;

    /**
     * 用户名称
     */
    private String userName;

    /**
     * 手机号码
     */
    private String mobile;

    /**
     * 用户余额
     */
    private Integer balance;
}

Dao 层

/**
 * 账号Dao层
 * @author yangyanping
 * @date 2021-08-30
 */
public interface AccountMapper {
    int deleteByPrimaryKey(Integer id);

    int insert(AccountEntity record);

    int insertSelective(AccountEntity record);

    AccountEntity selectByPrimaryKey(Integer id);

    int updateByPrimaryKeySelective(AccountEntity record);

    int updateByPrimaryKey(AccountEntity record);
}

 Service 服务层

/**
 * 账户服务
 * @author yangyanping
 * @date 2021-09-20
 */
public interface AccountService {
    /**
     * 转账
     */
    Boolean transferAccount(AccountEntity source, AccountEntity target, Integer money);
}

采用充血模型设计

对传统DDD四层架构升级改造

(1)根据依赖倒置原则对分层结构进行了改进,通过改变不同层的依赖关系(即将基础设施层倒置)来改进具体实现与抽象之间关系;

(2)在基础设施层中增加引用适配层(防腐层)来增强防御策略,用来统一封装外部系统接口的引用。改进的分层结构如下:

依赖倒置原则(DIP):

  • 高层模块不依赖于低层模块,两者都依赖于抽象;
  • 抽象不应该依赖于细节,细节应依赖抽象

分层作用

分层描述
用户接口层用户界面层,或者表现层,负责向用户显示解释用户命令
应用层定义软件要完成的任务,并且指挥协调领域对象进行不同的操作。该层不包含业务领域知识。
领域层或称为模型层,系统的核心,负责表达业务概念,业务状态信息以及业务规则。即包含了该领域(问题域)所有复杂的业务知识抽象和规则定义。该层主要精力要放在领域对象分析上,可以从实体,值对象,聚合(聚合根),领域服务,领域事件,仓储,工厂等方面入手
基础设施层主要有2方面内容,一是为领域模型提供持久化机制,当软件需要持久化能力时候才需要进行规划;一是对其他层提供通用的技术支持能力,如消息通信,通用工具,配置等的实现;

 领域层 Domain Layer

和传统的三层架构不同,DDD领域驱动设计开发先设计好领域对象。我们先定义 Account 账户领域对象

充血模型是有血有肉的,核心领域方法都会放到领域对象中,而不是把领域方法放到模型之上的service层中去。 

package com.yyp.ddd.domain.model.account;

import com.yyp.ddd.domain.factory.RepoFactory;
import com.yyp.ddd.infrastructure.persistence.repository.AccountRepository;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 充血模型
 * @author yangyanping
 * @date 2021-08-30
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Account {
    /**
     * 账号
     */
    private Integer accountNum;

    /**
     * 用户名称
     */
    private String userName;

    /**
     * 手机号码
     */
    private String mobile;

    /**
     * 用户余额
     */
    private Integer balance;

    /**
     * 转入
     */
    public void deposit(Integer amount) {
        if (amount == null || amount.intValue() < 0) {
            throw new IllegalArgumentException("参数错误");
        }

        balance += amount;

        RepoFactory.get(AccountRepository.class).updateAccount(this);
    }

    /**
     * 转出
     */
    public void withdrawal(Integer amount) {
        if (amount == null || amount.intValue() < 0) {
            throw new IllegalArgumentException("参数错误");
        }

        if (balance < amount) {
            throw new IllegalArgumentException("转出金额过大");
        }

        balance -= amount;
        RepoFactory.get(AccountRepository.class).updateAccount(this);
    }
}
/**
 * 仓储工厂用来统一获取仓储实现
 */
public class RepoFactory {

	/**
	 * 根据仓储接口类型获取对应实现且默认取值第一个
	 *
	 * @param tClass 目标仓储接口类型
	 * @param <T>    目标类型
	 * @return 如果不是指定实现,默认获得第一个实现Bean
	 */
	public static <T> T get(Class<? extends T> tClass) {

		Map<String, ? extends T> map = ApplicationUtils.getApplicationContext().getBeansOfType(tClass);
		Collection<? extends T> collection = map.values();
		if (collection.isEmpty()) {
			//throw new PersistException("未找到仓储接口或其指定的实现:" + tClass.getSimpleName() );
		}
		return collection.stream().findFirst().get();
	}
}

应用层 Application Layer  代码

 AccountDTO 账户DTO类定义

/**
 * 账户信息DTO
 * @author yangyanping 
 * @date 2021-09-20
 */
@Data
public class AccountDTO  implements Serializable {
    /**
     * 账号
     */
    private Integer accountNum;

    /**
     * 用户名称
     */
    private String userName;

    /**
     * 手机号码
     */
    private String mobile;

    /**
     * 用户余额
     */
    private Integer balance;
}

 AccountService 账户服务类定义



/**
 * 账户服务
 * @author yangyanping
 * @date 2021-09-20
 */
public interface AccountService {
    /**
     * 转账
     */
    Boolean transferAccount(AccountDTO source, AccountDTO target, Integer money);
}

AccountServiceImpl账户实现类定义

/**
 * 账户服务层定义
 * @author yangyanping
 * @date 2021-08-30
 */
@Service
public class AccountServiceImpl implements AccountService {

    @Override
    public Boolean transferAccount(AccountDTO source, AccountDTO target, Integer money) {

        Account sourceAccount = ConvertUtils.convertVo2Do(source, Account::new);

        Account targetAccount = ConvertUtils.convertVo2Do(target, Account::new);
        // 转出
        sourceAccount.withdrawal(money);
        // 转入
        targetAccount.deposit(money);

        return true;
    }
}

 基础设施层 infrastructure Layer

Repository数据仓库
/**
 * 数据仓库
 * @author yangyanping
 */
@Slf4j
@Repository
@RequiredArgsConstructor(onConstructor = @_(@Autowired))
public class AccountRepository {

    /**
     * 数据持久层
     */
    private AccountMapper accountMapper;

    /**
     * 更新账户信息
     */
    public void updateAccount(Account account) {
        // account 转换AccountEntity
        AccountEntity record = ConvertUtils.convertDo2Vo(account, AccountEntity::new);

        System.out.println("name=" + record.getUserName() + ",balance=" + record.getBalance());
        //accountMapper.updateByPrimaryKeySelective(record);
    }
}

参考

从壹开始微服务 [ DDD ] 之一 ║ D3模式设计初探 与 我的计划书 - 简书

DDD-经典四层架构应用_最爱下雨天-CSDN博客_ddd四层架构

阿里技术专家详解 DDD 系列 第一讲- Domain Primitive - 知乎

https://github.com/alibaba/COLA

Rickie (rickiechina) - Gitee.com

COLA 4.0:直击应用架构本质的最佳实践【图文】_mb6018e8479df66_51CTO博客

殷浩详解DDD系列 第二讲 - 应用架构-阿里云开发者社区

殷浩详解DDD系列 第三讲 - Repository模式-阿里云开发者社区

阿里技术专家详解DDD系列 第五讲:聊聊如何避免写流水账代码 - 知乎

阿里技术专家详解DDD系列 第四讲 - 领域层设计规范 - 知乎

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值