应用服务 和 模板方法 擦出的火花

0. 前言

面对业务,一个永恒的真理:永远不变的就是变化。如何发现变化、封装变化、隔离变化,是每个 程序员 的永恒话题。

本篇文章,将带领大家把 “模板方法” 设计模式应用于领域设计的 “应用服务” 中,以达到如下目的:

  1. 对主流程进行封装,保持主流程的稳定性,不变性;

  2. 对细节步骤进行扩展,保持业务的灵活性,扩展性;

在正式开始之前,先了解下 什么是应用服务,以及他所面对的问题。

1. 什么是应用服务?

应用服务是 DDD 的重要概念之一,它是位于用户接口和领域模型之间薄薄的一层,面向用户用例,主要负责编排,包括流程编排和事件编排。

以下是比较流行的 六边形架构,让我们简单了解下应用服务的角色和作用。

图片

image

从图中可知,应用服务有几个特点:

  1. 面向用户用例,主要负责对业务流程进行编排;

  2. 领域模型的直接使用者,在各组件间进行协调,共同完成业务流程。

  3. 资源管理者,将领域模型和基础设施粘合在一起。

  4. 另外,负责事务、安全等技术保护;

可见,应用服务职能还是很多,在众多职能中,“流程编排” 算是最重要的一个,也是我们这次研究的重点。

首先,我们看一个简单案例,研究下应用服务在写入流程中的标准写法:

1.1 UserApplication 应用服务

应用服务接口,主要是为了对多个实现进行约束,在实际开发中,并不是必须的。

UserApplication 对业务操作进行定义,详细如下:

public interface UserApplication {
    
    void createAndEnableUser(CreateAndEnableUserContext context);

    
    void modifyUserName(ModifyUserNameContext context);
}

接口中主要定义两个业务操作:

  1. createAndEnableUser 创建并激活用户。该业务是个组合业务,由 “创建” 和 “激活” 两个原子操作组成,创建并激活用户后,对外发送领域事件;

  2. modifyUserName 修改用户姓名。单纯的更新操作,在完成用户姓名修改后,对外发送领域事件;

针对这个接口,我们先看第一个简单实现:

1.2 UserV1Application 实现

UserV1Application 是第一个实现类,其他的实现都是在其基础之上进行推演。

UserV1Application 为应用服务的标准实现,具体代码如下:

@Service
public class UserV1Application implements UserApplication {
    private static final Logger LOGGER = LoggerFactory.getLogger(UserV1Application.class);

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Override
    @Transactional(readOnly = false)
    public void createAndEnableUser(CreateAndEnableUserContext context){
        try {
            // 1. 生成 聚合根
            User user = User.create(context.getName(), context.getAge());

            // 2. 执行业务访问
            user.enable();

            // 3. 保存聚合根
            this.userRepository.save(user);

            // 4. 发布领域事件
            user.foreachEvent(this.eventPublisher::publishEvent);

            // 5. 清理领域事件
            user.clearEvents();

            LOGGER.info("success to handle createAndEnableUser and sync {} to DB", user);
        }catch (Exception e){
            LOGGER.error("failed to handle createAndEnableUser", e);
            if (e instanceof RuntimeException){
                throw (RuntimeException) e;
            }
            throw new RuntimeException(e);
        }

    }

    @Override
    @Transactional(readOnly = false)
    public void modifyUserName(ModifyUserNameContext context){
        try {

            // 1. 加载聚合根
            User user = this.userRepository.getById(context.getUserId());

            // 2. 验证聚合根
            if (user == null){
                throw new UserNotFoundException(context.getUserId());
            }

            // 3. 调用聚合根方法
            user.modifyUserName(context.getNewName());

            // 4. 保存对象
            this.userRepository.save(user);

            // 5. 发布领域事件
            user.foreachEvent(this.eventPublisher::publishEvent);

            // 6. 清理领域事件
            user.clearEvents();

            LOGGER.info("success to handle modifyUserName and sync {} to DB", user);
        }catch (Exception e){
            LOGGER.error("failed to handle modifyUserName", e);
            if (e instanceof RuntimeException){
                throw (RuntimeException) e;
            }
            throw new RuntimeException(e);
        }

    }
}

仔细观察 UserV1Application 实现,会发现流程存在一定的相似性(重复性):

  1. 统一的异常处理机制。使用直接抛出异常的方式进行流程中断;

  2. 高度相似的成功日志。在操作完成后,打印成功日志;

  3. 高度一致的业务流程。

  • 创建流程。

  • 更新流程。

  1. 加载聚合根。通过 Repository 从数据库中获取聚合根对象;

  2. 验证聚合根。对 聚合根 有效性进行验证(是否找到);

  3. 执行业务操作。调用聚合根上的方法,完成业务操作;

  4. 保存聚合根。通过 Repository 将变更保存到数据库;

  5. 发布&清理领域事件。使用 ApplicationEventPublisher 对外发布领域事件;

  6. 实例化聚合根对象。使用上下文信息,生成聚合根对象;

  7. 执行业务操作(可选)。调用聚合根上的方法,执行业务操作;

  8. 持久化聚合根。使用 Repository 对聚合根进行持久化,将变更保存到数据库;

  9. 发布&清理领域事件。使用 ApplicationEventPublisher 将业务操作所产生的领域事件进行发布

这是 User 聚合的操作,我们来看另一个聚合 Email。

@Service
public class EmailApplication {
    private static final Logger LOGGER = LoggerFactory.getLogger(EmailApplication.class);

    @Autowired
    private EmailRepository emailRepository;

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Transactional(readOnly = false)
    public void createEmail(CreateEmailContext context){
        try {
            // 1. 生成 聚合根
            Email email = Email.create(context.getUserId(), context.getEmail());

            // 2. 执行业务访问
            email.init();

            // 3. 保存聚合根
            this.emailRepository.save(email);

            // 4. 发布领域事件
            email.foreachEvent(this.eventPublisher::publishEvent);

            // 5. 清理领域事件
            email.clearEvents();

            LOGGER.info("success to handle createEmail and sync {} to DB", email);
        }catch (Exception e){
            LOGGER.error("failed to handle createEmail", e);
            if (e instanceof RuntimeException){
                throw (RuntimeException) e;
            }
            throw new RuntimeException(e);
        }

    }


    @Transactional(readOnly = false)
    public void modifyEmail(ModifyEmailContext context){
        try {

            // 1. 加载聚合根
            Email email = this.emailRepository.getByUserId(context.getUserId());

            // 2. 验证聚合根
            if (email == null){
                throw new UserNotFoundException(context.getUserId());
            }

            // 3. 调用聚合根方法
            email.modifyEmail(context.getEmail());

            // 4. 保存对象
            this.emailRepository.save(email);

            // 5. 发布领域事件
            email.foreachEvent(this.eventPublisher::publishEvent);

            // 6. 清理领域事件
            email.clearEvents();

            LOGGER.info("success to handle modifyEmail and sync {} to DB", email);
        }catch (Exception e){
            LOGGER.error("failed to handle modifyEmail", e);
            if (e instanceof RuntimeException){
                throw (RuntimeException) e;
            }
            throw new RuntimeException(e);
        }

  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值