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 为应用服务的规范实现,具体代码如下:

@Servicepublic 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。

@Servicepublic 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 源码如下:

@Servicepublic 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 源码如下:

@Servicepublic 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

最初,咱们须要批改应用服务,把业务逻辑分发给对于的畛域服务:

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

当然,如果感觉每个操作都须要创立一个新的服务类,还能够应用外部匿名类实现,具体如下:

@Servicepublic 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);    }}
该模板类与之前的代码逻辑基本一致,只是代码组织模式产生了变动。

有了模板办法,那咱们看下如何应用:

@Servicepublic 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. 流程执行。也就是模板办法设计模式中,不变的“算法主体”;

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

@Servicepublic 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 + 入参 + 回调

最初,附上 源码地址 源码