头图

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

文心紫竹

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);
        }

    }
}

有没有发现,聚合的写操作基本都是相似的逻辑(套路)?

面对“套路”,有没有一种方法能够对其进行统一管理呢?

这正是“模板方法”设计模式擅长的地方,接下来,让我们先停一下,简单温习下标准的模板方法。

2. 模板方法

模板方法:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。使子类在不改变算法结构的情况下,重新定义算法中的某些步骤。

首先,我们看下模板方法的整体结构。

图片

image

熟悉完整体结构,我们先对现有流程进行梳理,找到算法骨架(不变部分)和操作步骤(变化部分),以更好的套用 模板方法 模式。

针对以上分析,可以得出:

  1. 创建和更新流程中,打印成功日志、异常管理、持久化管理、事件管理 是通用逻辑,属于 “算法骨架”;
  2. 创建流程中,聚合实例化、聚合业务操作,可以作为“算法骨架”;
  3. 更新流程中,聚合加载、聚合验证、聚合业务操作,可以作为“算法骨架”;

面对三个需要统一的“算法骨架”,我们通过多级继承的方式进行构建,整体的类图如下:图片

该类图主要有三个模板类:

  1. AbstractDomainService。顶层模板类,是 AbstractCreateService 和 AbstractUpdateService 的父类,主要对 “创建” 和 “更新” 两个流程中的通用部分进行封装;
  2. AbstractCreateService。创建流程模板类,对 创建 流程进行封装;
  3. AbstractUpdateService。更新流程模板类,对 更新流程进行 封装;

具体的代码如下:

AbstractDomainService 源码如下:

abstract class AbstractDomainService<AGG extends Agg, CONTEXT extends DomainServiceContext> {
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDomainService.class);

    private final ApplicationEventPublisher eventPublisher;

    private final CrudRepository<AGG, ?> repository;

    public AbstractDomainService(CrudRepository<AGG, ?> repository,
                                 ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
        this.repository = repository;
    }

    @Transactional(readOnly = false)
    public void handle(CONTEXT context){
        try {
            // 回调子类接口,用于扩展
            AGG agg = doHandle(context);

            // 将变更持久化到 DB
            save2DB(agg);

            // 发布领域事件,完成后对事件进行清理
            publishAndCleanEvent(agg);

            // 成功回调,默认打印日志
            onSuccess(agg, context);
        }catch (Exception e){
            // 异常处理,直接中断流程
            onException(e, context);
        }
    }

    /**
     * 回调接口,用于子类进行扩展
     * @param context
     * @return
     */
    protected abstract AGG doHandle(CONTEXT context);

    /**
     * 异常默认处理策略,子类通过重新可以进行自定义
     * @param e
     * @param context
     */
    protected void onException(Exception e, CONTEXT context){
        LOGGER.error("failed to handle {}", context, e);
        if (e instanceof RuntimeException){
            throw (RuntimeException) e;
        }
        throw new RuntimeException(e);
    }

    /**
     * 默认成功回调,子类通过重写可以进行自定义
     * @param agg
     * @param context
     */
    protected void onSuccess(AGG agg, CONTEXT context){
        LOGGER.info("success to handle {} and sync {} to DB", context, agg);
    }

    /**
     * 发布并清理领域事件
     * @param agg
     */
    private void publishAndCleanEvent(AGG agg){
        // 1. 发布领域事件
        agg.foreachEvent(this.eventPublisher::publishEvent);

        // 2. 清理领域事件
        agg.clearEvents();
    }

    /**
     * 将变更持久化到 DB 中
     * @param agg
     */
    private void save2DB(AGG agg){
        this.repository.save(agg);
    }
}

AbstractDomainService 作为顶层抽象类,主要对流程进行统一,封装 异常处理、事件处理、成功日志、数据持久化等操作,并通过 doHandle 抽象方法对子类进行扩展。接下来我们看下两个子类源码:

AbstractCreateService 源码如下:

public abstract class AbstractCreateService<AGG extends Agg, CONTEXT extends DomainServiceContext>
        extends AbstractDomainService<AGG, CONTEXT>{

    public AbstractCreateService(CrudRepository<AGG, ?> repository, ApplicationEventPublisher eventPublisher) {
        super(repository, eventPublisher);
    }

    /**
     * 重写父类方法,进行扩展
     * @param context
     * @return
     */
    @Override
    protected AGG doHandle(CONTEXT context) {
        // 1. 生成 聚合根
        AGG agg = instance(context);

        // 2. 执行业务访问
        update(agg, context);

        return agg;
    }

    /**
     * 子类扩展点,可以对 聚合 进行业务调用
     * @param agg
     * @param context
     */
    protected void update(AGG agg, CONTEXT context){

    }

    /**
     * 子类扩展点,对 聚合 进行实例化
     * @param context
     * @return
     */
    protected abstract AGG instance(CONTEXT context);

}

AbstractCreateService 暴露 instance 方法由子类实现扩展,对聚合进行实例化,同时提供 update 扩展点,在实例化完成后进行业务处理。

我们看另外一个扩展子类,AbstractUpdateService 源码如下:

public abstract class AbstractUpdateService<AGG extends Agg, CONTEXT extends DomainServiceContext>
    extends AbstractDomainService<AGG, CONTEXT>{

    public AbstractUpdateService(CrudRepository<AGG, ?> repository,
                                    ApplicationEventPublisher eventPublisher) {
        super(repository, eventPublisher);
    }

    /**
     * 重写父类方法,进行扩展
     * @param context
     * @return
     */
    @Override
    protected AGG doHandle(CONTEXT context) {
        // 1. 加载聚合根
        AGG agg = loadAgg(context);

        // 2. 验证聚合根
        if (agg == null){
            notFound(context);
        }

        // 3. 调用聚合根方法
        update(agg, context);

        return agg;
    }

    /**
     * 子类扩展点,加载要操作的聚合
     * @param context
     * @return
     */
    protected abstract AGG loadAgg(CONTEXT context);

    /**
     * 子类扩展点,当聚合丢失时进行回调
     * @param context
     */
    protected abstract void notFound(CONTEXT context);


    /**
     * 子类扩展点,调用 聚合上的业务方法
     * @param agg
     * @param context
     */
    protected abstract void update(AGG agg, CONTEXT context);
}

AbstractUpdateService 为子类提供了 聚合记载、聚合操作、聚合丢失等三个扩展点。

在这个体系下,业务实现将变的整洁,并且高度一致,详细如下:

CreateAndEnableUserService 源码如下:

@Service
public class CreateAndEnableUserService
        extends AbstractCreateService<User, CreateAndEnableUserContext> {

    @Autowired
    public CreateAndEnableUserService(ApplicationEventPublisher eventPublisher, CrudRepository<User, ?> repository) {
        super(repository, eventPublisher);
    }

    @Override
    protected User instance(CreateAndEnableUserContext context) {
        // 实例化 User 聚合对象
        return User.create(context.getName(), context.getAge());
    }

    @Override
    protected void update(User agg, CreateAndEnableUserContext context) {
        // 调用聚合上的 业务方法
        agg.enable();
    }
}

ModifyUserNameService 源码如下:

@Service
public class ModifyUserNameService extends AbstractUpdateService<User, ModifyUserNameContext> {
    private final JpaRepository<User, Long> repository;
    public ModifyUserNameService(JpaRepository<User, Long> repository, ApplicationEventPublisher eventPublisher) {
        super(repository, eventPublisher);
        this.repository = repository;
    }

    @Override
    protected User loadAgg(ModifyUserNameContext context) {
        // 加载要操作的 聚合对象
        return this.repository.getById(context.getUserId());
    }

    @Override
    protected void notFound(ModifyUserNameContext context) {
        // 聚合丢失,直接抛出异常
        throw new UserNotFoundException(context.getUserId());
    }


    @Override
    protected void update(User agg, ModifyUserNameContext context) {
        // 调用聚合上的 业务方法
        agg.modifyUserName(context.getNewName());
    }
}

在模板方法约束下,业务代码变的高度一致,完整类图如下:

图片

image

最后,我们需要修改应用服务,把业务逻辑分发给对于的领域服务:

@Service
public class UserV2Application implements UserApplication {
    @Autowired
    private CreateAndEnableUserService createAndEnableUserService;

    @Autowired
    private ModifyUserNameService modifyUserNameService;

    @Override
    public void createAndEnableUser(CreateAndEnableUserContext context) {
        // 将逻辑分发给领域服务
        this.createAndEnableUserService.handle(context);
    }

    @Override
    public void modifyUserName(ModifyUserNameContext context) {
        // 将逻辑分发给领域服务
        this.modifyUserNameService.handle(context);
    }
}

当然,如果觉得每个操作都需要创建一个新的服务类,还可以使用内部匿名类实现,具体如下:

@Service
public class UserV2Application2
    implements UserApplication {
    @Autowired
    private JpaRepository<User, Long> repository;
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void createAndEnableUser(CreateAndEnableUserContext context) {
        // 使用匿名内部类实现逻辑
        new AbstractCreateService<User, CreateAndEnableUserContext>(this.repository, applicationEventPublisher){
            @Override
            protected User instance(CreateAndEnableUserContext context) {
                return User.create(context.getName(), context.getAge());
            }

            @Override
            protected void update(User agg, CreateAndEnableUserContext context) {
                agg.enable();
            }
        }.handle(context);
    }

    @Override
    public void modifyUserName(ModifyUserNameContext context) {
        // 使用匿名内部类实现逻辑
        new AbstractUpdateService<User, ModifyUserNameContext>(this.repository, this.applicationEventPublisher){
            @Override
            protected void update(User agg, ModifyUserNameContext context) {
                agg.modifyUserName(context.getNewName());
            }

            @Override
            protected void notFound(ModifyUserNameContext context) {
                throw new UserNotFoundException(context.getUserId());
            }

            @Override
            protected User loadAgg(ModifyUserNameContext context) {
                return repository.getById(context.getUserId());
            }
        }.handle(context);
    }
}

匿名内部类使代码变的紧凑,但也丧失了一定的可读性。你觉得简单,还是复杂了?是不是感觉流程被割裂开?不急,那让我们继续。

4. Spring 模板方法

Spring 的一个设计特点就是,提供了大量的 Template 类 以完成对资源的管理。如 JdbcTemplate、RedisTemplate 等。

首先,让我们重新感受一下 Spring 的JdbcTemplate:

public User getByName(String name) {
    String sql = "select " +
            "id, create_time, update_time, status, name, password " +
            "from tb_user " +
            "where " +
            "name = ?";
    List<User> result = jdbcTemplate.query(sql, new Object[]{name}, new RowMapper<User>() {
        @Override
        public User mapRow(ResultSet resultSet, int i) throws SQLException {

            Long idForSelect = resultSet.getLong(1);
            java.sql.Date createDate = resultSet.getDate(2);
            java.sql.Date updateDate = resultSet.getDate(3);
            Integer statusCode = resultSet.getInt(4);
            String name = resultSet.getString(5);
            String password = resultSet.getString(6);
            User user = new User();
            user.setId(idForSelect);
            user.setCreateTime(createDate);
            user.setUpdateTime(updateDate);
            user.setStatus(UserStatus.valueOfCode(statusCode));
            user.setName(name);
            user.setPassword(password);
            return user;
        }
    });
    return result.isEmpty() ? null : result.get(0);
}

JdbcTemplate 完成了对资源的管理,对 jdbc 的标准用法进行封装,通过 入参 + 回调方式,将扩展点暴露给使用方。

Spring 模板方法与标准模板方法有哪些差异呢?

  1. 不变部分的封装基本相同。都是使用方法对算法骨架进行封装;
  2. 变化部分的定义和约束方式不同。标准模板方法使用 抽象方法 规范操作步骤,而 Spring 模板方法使用 接口 约束操作步骤。
  3. 变化部分的扩展方式不同。模板方法使用继承的方法重写进行扩展,Spring 模板方法使用 入参方式进行扩展;
  4. 逻辑组织方式不同。模板方法通过父类回调子类方法的形式以完成流程编排,Spring 模板方法通过入参回调完成流程组织;

完成理论对比后,咱在代码中找下不同的感觉。首先,定义我们自己的模板类:

public final class ApplicationServiceTemplate<AGG extends Agg> {
    private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationServiceTemplate.class);

    private final ApplicationEventPublisher eventPublisher;

    private final CrudRepository<AGG, ?> repository;

    public ApplicationServiceTemplate(ApplicationEventPublisher eventPublisher,
                                      CrudRepository<AGG, ?> repository) {
        this.eventPublisher = eventPublisher;
        this.repository = repository;
    }

    public <CONTEXT extends DomainServiceContext> void executeCreate(CONTEXT context,
                                                                     Function<CONTEXT, AGG> instanceFun,
                                                                     BiConsumer<CONTEXT, AGG> updateFun){
        try {
            // 1. 生成 聚合根
            AGG agg = instanceFun.apply(context);

            // 2. 执行业务访问
            updateFun.accept(context, agg);

            // 3. 保存聚合根
            save2DB(agg);

            publishAndCleanEvent(agg);

            onSuccess(agg, context);
        }catch (Exception e){
            onException(e, context);
        }
    }

    public <CONTEXT extends DomainServiceContext> void executeUpdate(CONTEXT context,
                                                                     Function<CONTEXT, AGG> loadFun,
                                                                     Consumer<CONTEXT> notFoundFun,
                                                                     BiConsumer<CONTEXT, AGG> updateFun){
        try {
            // 1. 加载聚合根
            AGG agg = loadFun.apply(context);

            // 2. 验证聚合根
            if (agg == null){
                notFoundFun.accept(context);
            }

            // 3. 调用聚合根方法
            updateFun.accept(context, agg);

            publishAndCleanEvent(agg);

            onSuccess(agg, context);
        }catch (Exception e){
            onException(e, context);
        }
    }

    private <CONTEXT extends DomainServiceContext> void onException(Exception e, CONTEXT context){
        LOGGER.error("failed to handle {}", context, e);
        if (e instanceof RuntimeException){
            throw (RuntimeException) e;
        }
        throw new RuntimeException(e);
    }

    private <CONTEXT extends DomainServiceContext> void onSuccess(AGG agg, CONTEXT context){
        LOGGER.info("success to handle {} and sync {} to DB", context, agg);
    }

    private void publishAndCleanEvent(AGG agg){
        // 1. 发布领域事件
        agg.foreachEvent(this.eventPublisher::publishEvent);

        // 2. 清理领域事件
        agg.clearEvents();
    }

    private void save2DB(AGG agg){
        this.repository.save(agg);
    }
}
该模板类与之前的代码逻辑基本一致,只是代码组织形式发生了变化。

有了模板方法,那我们看下如何使用:

@Service
public class UserV3Application
        implements UserApplication {
    private final JpaRepository<User, Long> repository;
    private final ApplicationServiceTemplate<User> applicationServiceTemplate;

    @Autowired
    public UserV3Application(ApplicationEventPublisher eventPublisher, JpaRepository<User, Long> repository) {
        this.repository = repository;
        this.applicationServiceTemplate = new ApplicationServiceTemplate<>(eventPublisher, this.repository);
    }

    @Override
    public void createAndEnableUser(CreateAndEnableUserContext cxt) {
        this.applicationServiceTemplate.executeCreate(cxt,
                        context -> User.create(context.getName(), context.getAge()),
                        (createAndEnableUserContext, user) -> user.enable()
                );
    }

    @Override
    public void modifyUserName(ModifyUserNameContext cxt) {
        this.applicationServiceTemplate.executeUpdate(cxt,
                    context -> repository.getById(context.getUserId()),
                    context -> {throw new UserNotFoundException(context.getUserId());},
                    (modifyUserNameContext, user) -> user.modifyUserName(modifyUserNameContext.getNewName())
                );
    }
}

有没有感觉比 标准模板方法简单不少呢?但同样带来几个问题:

  1. 参数过少,功能扩展能力不足;
  2. 参数过多,面对几十个参数,会存在混淆,增加使用难度;
  3. 如果只有几个是必选,其他设置为 null,将会显得非常凌乱;

那方法重载能解决这个问题吗?只能缓解无法根治,那有没有更好的方法?让我们继续往下看。

5. 模板方法 + Builder 模式

模板方法擅长对流程进行规范,Builder模式擅长对对象进行组装。模板方法 和 Builder模式的组合使用,将带来非常清晰,且容易扩展的 API 体验。

相比 Spring模板方法,新的模式只是在 逻辑组织方式上有些不同。Spring 模板方法通过入参回调完成流程组织,该模式使用 Builder 进行逻辑组装。说起来很抽象,让我们看下代码。

public abstract class BaseV4Application {
    private static final Logger LOGGER = LoggerFactory.getLogger(BaseV4Application.class);

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    /**
     * 创建 Creator,已完成对创建流程的组装
     * @param repository
     * @param <A>
     * @param <CONTEXT>
     * @return
     */
    protected <A extends Agg, CONTEXT extends DomainServiceContext> Creator<A, CONTEXT> creatorFor(CrudRepository<A, ?> repository){
        return new Creator<A, CONTEXT>(repository);
    }

    /**
     * 创建 Updater,已完成对更新流程的组装
     * @param aggregateRepository
     * @param <A>
     * @param <CONTEXT>
     * @return
     */
    protected <A extends Agg, CONTEXT extends DomainServiceContext> Updater<A, CONTEXT> updaterFor(CrudRepository<A, ?> aggregateRepository){
        return new Updater<A, CONTEXT>(aggregateRepository);
    }

    /**
     * 创建流程的 Builder,主要:
     * 1. 组装流程
     * 2. 执行流程
     * @param <A>
     * @param <CONTEXT>
     */
    protected class Creator<A extends Agg, CONTEXT extends DomainServiceContext>{
        // 标准仓库
        private final CrudRepository<A, ?> aggregateRepository;

        // 实例化器,用于完成对 聚合 的实例化
        private Function<CONTEXT, A> instanceFun;

        // 默认的操作成功处理器,在操作成功后进行回调
        private BiConsumer<A, CONTEXT> successFun = (agg, context)->{
            LOGGER.info("success to handle {} and sync {} to DB", context, agg);
        };

        // 默认的异常处理器,在操作失败抛出异常时进行回调
        private BiConsumer<Exception, CONTEXT> errorFun = (exception, context) -> {
            LOGGER.error("failed to handle {}", context, exception);
            if (exception instanceof RuntimeException){
                throw (RuntimeException) exception;
            }
            throw new RuntimeException(exception);
        };

        // 聚合业务操作方法回调,最主要的扩展点,用于执行 聚合上的业务方法
        private BiConsumer<A, CONTEXT> updateFun = (a, context) -> {};


        Creator(CrudRepository<A, ?> aggregateRepository) {
            Preconditions.checkArgument(aggregateRepository != null);
            this.aggregateRepository = aggregateRepository;
        }

        /**
         * 设置 聚合的实例化器
         * @param instanceFun
         * @return
         */
        public Creator<A, CONTEXT> instance(Function<CONTEXT, A> instanceFun){
            Preconditions.checkArgument(instanceFun != null);
            this.instanceFun = instanceFun;
            return this;
        }

        /**
         * 增加 聚合上的业务操作,链式模式,可以绑定多的 业务操作
         * @param updater
         * @return
         */
        public Creator<A, CONTEXT> update(BiConsumer<A, CONTEXT> updater){
            Preconditions.checkArgument(updater != null);
            this.updateFun = this.updateFun.andThen(updater);
            return this;
        }

        /**
         * 增加 成功处理器,链式模式,可以绑定多个处理器
         * @param onSuccessFun
         * @return
         */
        public Creator<A, CONTEXT> onSuccess(BiConsumer<A, CONTEXT>  onSuccessFun){
            Preconditions.checkArgument(onSuccessFun != null);
            this.successFun = onSuccessFun.andThen(this.successFun);
            return this;
        }

        /**
         * 增加 异常处理器,链式模式,可以绑定多个处理器
         * @param errorFun
         * @return
         */
        public Creator<A, CONTEXT> onError(BiConsumer<Exception, CONTEXT>  errorFun){
            Preconditions.checkArgument(errorFun != null);
            this.errorFun = errorFun.andThen(this.errorFun);
            return this;
        }

        /**
         * 执行 创建流程
         * @param context
         * @return
         */
        public A call(CONTEXT context){
            Preconditions.checkArgument(this.instanceFun != null, "instance fun can not be null");
            Preconditions.checkArgument(this.aggregateRepository != null, "aggregateRepository can not be null");

            A a = null;
            try{
                // 实例化 聚合根
                a = this.instanceFun.apply(context);

                // 在聚合根上执行业务操作
                this.updateFun.accept(a, context);

                // 持久化聚合根到 DB
                this.aggregateRepository.save(a);

                // 发布领域事件,并进行清理
                if (eventPublisher != null){
                    // 1. 发布领域事件
                    a.foreachEvent(eventPublisher::publishEvent);

                    // 2. 清理领域事件
                    a.clearEvents();
                }

                // 调用 成功回调器
                this.successFun.accept(a, context);
            }catch (Exception e){
                // 调用 异常回调器
                this.errorFun.accept(e, context);
            }
            return a;
        }
    }

    /**
     * 更新流程的 Builder,主要:
     * 1. 组装流程
     * 2. 执行流程
     * @param <A>
     * @param <CONTEXT>
     */
    protected class Updater<A extends Agg, CONTEXT extends DomainServiceContext> {
        // 标准仓库
        private final CrudRepository<A, ?> aggregateRepository;
        // 聚合加载器,用于从 DB 中加载 聚合对象
        private Function<CONTEXT, A> loadFun;
        // 聚合丢失处理器,聚合丢失时进行回调
        private Consumer<CONTEXT> onNotExistFun = context -> {};
        // 成功回调器,链式模式,在操作成功时调用
        private BiConsumer<A, CONTEXT> successFun = (agg, context)->{
            LOGGER.info("success to handle {} and sync {} to DB", context, agg);
        };
        // 异常回调器,链式模式,在操作失败抛出异常时调用
        private BiConsumer<Exception, CONTEXT> errorFun = (exception, context) -> {
            LOGGER.error("failed to handle {}", context, exception);
            if (exception instanceof RuntimeException){
                throw (RuntimeException) exception;
            }
            throw new RuntimeException(exception);
        };
        // 业务更新器,对聚合进行业务操作
        private BiConsumer<A, CONTEXT> updateFun = (a, context) -> {};


        Updater(CrudRepository<A, ?> aggregateRepository) {
            this.aggregateRepository = aggregateRepository;
        }

        /**
         * 设置 聚合对象加载器,用于从 DB 中加载 聚合
         * @param loader
         * @return
         */
        public Updater<A, CONTEXT> loader(Function<CONTEXT, A> loader){
            Preconditions.checkArgument(loader != null);
            this.loadFun = loader;
            return this;
        }

        /**
         * 增加 业务执行器,链式模式,可以绑定多个执行器
         * @param updateFun
         * @return
         */
        public Updater<A, CONTEXT> update(BiConsumer<A, CONTEXT> updateFun){
            Preconditions.checkArgument(updateFun != null);
            this.updateFun = updateFun.andThen(this.updateFun);
            return this;
        }

        /**
         * 增加 成功回调器,链式模式,可以绑定多个回调器
         * @param onSuccessFun
         * @return
         */
        public Updater<A, CONTEXT> onSuccess(BiConsumer<A, CONTEXT>  onSuccessFun){
            Preconditions.checkArgument(onSuccessFun != null);
            this.successFun = onSuccessFun.andThen(this.successFun);
            return this;
        }

        /**
         * 增加 异常回调器,链式模式,可以绑定多个回调器
         * @param errorFun
         * @return
         */
        public Updater<A, CONTEXT> onError(BiConsumer<Exception, CONTEXT>  errorFun){
            Preconditions.checkArgument(errorFun != null);
            this.errorFun = errorFun.andThen(this.errorFun);
            return this;
        }

        /**
         * 增加 聚合丢失处理器,链式模式,可以绑定多个回调器
         * @param onNotExistFun
         * @return
         */
        public Updater<A, CONTEXT> onNotFound(Consumer<CONTEXT>  onNotExistFun){
            Preconditions.checkArgument(onNotExistFun != null);
            this.onNotExistFun = onNotExistFun.andThen(this.onNotExistFun);
            return this;
        }

        /**
         * 执行更新流程
         * @param context
         * @return
         */
        public A call(CONTEXT context){
            Preconditions.checkArgument(this.aggregateRepository != null, "aggregateRepository can not be null");
            Preconditions.checkArgument(this.loadFun != null, "loader can not both be null");
            A a = null;
            try {
                // 从 DB 中加载 聚合根
                a = this.loadFun.apply(context);

                if (a == null){
                    // 聚合根不存在,回调 聚合丢失处理器
                    this.onNotExistFun.accept(context);
                }

                // 在聚合之上,执行业务操作
                updateFun.accept(a, context);

                // 对聚合进行持久化处理
                this.aggregateRepository.save(a);

                // 发布并清理事件
                if (eventPublisher != null){
                    // 1. 发布领域事件
                    a.foreachEvent(eventPublisher::publishEvent);

                    // 2. 清理领域事件
                    a.clearEvents();
                }
                // 操作成功回调
                this.successFun.accept(a, context);

            }catch (Exception e){
                // 异常回调
                this.errorFun.accept(e, context);
            }
            return a;
        }

    }
}

核心在 Creator 和 Updater 两个内部类上,这两个内部类主要由以下职责:

  1. 流程组装。通过 Builder 模式,对流程中所使用的操作步骤进行组装。在封装过程中,大量使用链式模式,在同一扩展点绑定多个操作;
  2. 流程执行。也就是模板方法设计模式中,不变的“算法主体”;

费了老大劲,一起来看下具体效果:

@Service
public class UserV4Application
    extends BaseV4Application
    implements UserApplication {
    @Autowired
    private JpaRepository<User, Long> repository;

    @Override
    public void createAndEnableUser(CreateAndEnableUserContext context) {
        this.<User, CreateAndEnableUserContext>creatorFor(this.repository)
                // 绑定聚合实例化逻辑
                .instance(createAndEnableUserContext -> User.create(createAndEnableUserContext.getName(), createAndEnableUserContext.getAge()))
                // 增加聚合业务操作
                .update((user, createAndEnableUserContext) -> user.enable())
                // 执行创建流程
                .call(context);
    }

    @Override
    public void modifyUserName(ModifyUserNameContext context) {
        this.<User, ModifyUserNameContext>updaterFor(repository)
                // 绑定聚合加载器
                .loader(domainServiceContext -> this.repository.getById(domainServiceContext.getUserId()))
                // 增加 聚合丢失处理器
                .onNotFound(modifyUserNameContext -> {throw new UserNotFoundException(modifyUserNameContext.getUserId());})
                // 增加 聚合业务操作
                .update((user, modifyUserNameContext) -> user.modifyUserName(modifyUserNameContext.getNewName()))
                // 执行更新流程
                .call(context);
    }
}

和之前的玩法相比,是不是清晰很多?

小结

本篇文章聚焦于 DDD 应用服务的标准化处理,在规范化主流程的前提下,最大限度的增加业务的灵活性,从而赋予其强大的流程编排能力。

从标准的 模板方法 出发,推演至 Spring 模板方法,最终定格在 模板方法 + Builder 模式,各种模式各有优缺点,需要根据场景进行定制,以发挥其强大的能力。

各模式特点简单汇总如下:

模式不变部分封装策略变化部分约束策略变化部分扩展方式组装
模板方法算法封装在方法抽象方法通过继承重写方法方法重写
静态模板方法算法封装在方法接口传入不同的接口实现入参 + 回调
模板方法+Builder算法封装在方法接口传入不同的接口实现builder + 入参 + 回调

最后,附上 源码地址 源码

阅读 243

Java基础
Java基础分享
249 声望
14 粉丝
0 条评论
你知道吗?

249 声望
14 粉丝
文章目录
宣传栏