黄芪

首页 » 常识 » 诊断 » 应用服务和模板方法擦出的火花
TUhjnbcbe - 2025/2/26 21:10:00
北京治疗白癜风哪家的好 http://www.znlvye.com/

0.前言

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

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

对主流程进行封装,保持主流程的稳定性,不变性;对细节步骤进行扩展,保持业务的灵活性,扩展性;

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

1.什么是应用服务?

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

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

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

面向用户用例,主要负责对业务流程进行编排;领域模型的直接使用者,在各组件间进行协调,共同完成业务流程。资源管理者,将领域模型和基础设施粘合在一起。另外,负责事务、安全等技术保护;

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

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

1.1UserApplication应用服务

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

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

publicinterfaceUserApplication{voidcreateAndEnableUser(CreateAndEnableUserContextcontext);voidmodifyUserName(ModifyUserNameContextcontext);}

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

createAndEnableUser创建并激活用户。该业务是个组合业务,由“创建”和“激活”两个原子操作组成,创建并激活用户后,对外发送领域事件;modifyUserName修改用户姓名。单纯的更新操作,在完成用户姓名修改后,对外发送领域事件;

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

1.2UserV1Application实现

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

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

ServicepublicclassUserV1ApplicationimplementsUserApplication{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(UserV1Application.class);

AutowiredprivateUserRepositoryuserRepository;

AutowiredprivateApplicationEventPublishereventPublisher;

Override

Transactional(readOnly=false)publicvoidcreateAndEnableUser(CreateAndEnableUserContextcontext){try{//1.生成聚合根Useruser=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("successtohandlecreateAndEnableUserandsync{}toDB",user);}catch(Exceptione){LOGGER.error("failedtohandlecreateAndEnableUser",e);if(einstanceofRuntimeException){throw(RuntimeException)e;}thrownewRuntimeException(e);}}

Override

Transactional(readOnly=false)publicvoidmodifyUserName(ModifyUserNameContextcontext){try{//1.加载聚合根Useruser=this.userRepository.getById(context.getUserId());//2.验证聚合根if(user==null){thrownewUserNotFoundException(context.getUserId());}//3.调用聚合根方法user.modifyUserName(context.getNewName());//4.保存对象this.userRepository.save(user);//5.发布领域事件user.foreachEvent(this.eventPublisher::publishEvent);//6.清理领域事件user.clearEvents();LOGGER.info("successtohandlemodifyUserNameandsync{}toDB",user);}catch(Exceptione){LOGGER.error("failedtohandlemodifyUserName",e);if(einstanceofRuntimeException){throw(RuntimeException)e;}thrownewRuntimeException(e);}}}

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

统一的异常处理机制。使用直接抛出异常的方式进行流程中断;高度相似的成功日志。在操作完成后,打印成功日志;高度一致的业务流程。创建流程。更新流程。加载聚合根。通过Repository从数据库中获取聚合根对象;验证聚合根。对聚合根有效性进行验证(是否找到);执行业务操作。调用聚合根上的方法,完成业务操作;保存聚合根。通过Repository将变更保存到数据库;发布清理领域事件。使用ApplicationEventPublisher对外发布领域事件;实例化聚合根对象。使用上下文信息,生成聚合根对象;执行业务操作(可选)。调用聚合根上的方法,执行业务操作;持久化聚合根。使用Repository对聚合根进行持久化,将变更保存到数据库;发布清理领域事件。使用ApplicationEventPublisher将业务操作所产生的领域事件进行发布

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

ServicepublicclassEmailApplication{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(EmailApplication.class);

AutowiredprivateEmailRepositoryemailRepository;

AutowiredprivateApplicationEventPublishereventPublisher;

Transactional(readOnly=false)publicvoidcreateEmail(CreateEmailContextcontext){try{//1.生成聚合根Emailemail=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("successtohandlecreateEmailandsync{}toDB",email);}catch(Exceptione){LOGGER.error("failedtohandlecreateEmail",e);if(einstanceofRuntimeException){throw(RuntimeException)e;}thrownewRuntimeException(e);}}

Transactional(readOnly=false)publicvoidmodifyEmail(ModifyEmailContextcontext){try{//1.加载聚合根Emailemail=this.emailRepository.getByUserId(context.getUserId());//2.验证聚合根if(email==null){thrownewUserNotFoundException(context.getUserId());}//3.调用聚合根方法email.modifyEmail(context.getEmail());//4.保存对象this.emailRepository.save(email);//5.发布领域事件email.foreachEvent(this.eventPublisher::publishEvent);//6.清理领域事件email.clearEvents();LOGGER.info("successtohandlemodifyEmailandsync{}toDB",email);}catch(Exceptione){LOGGER.error("failedtohandlemodifyEmail",e);if(einstanceofRuntimeException){throw(RuntimeException)e;}thrownewRuntimeException(e);}}}

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

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

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

2.模板方法

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

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

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

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

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

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

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

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

具体的代码如下:

AbstractDomainService源码如下:

abstractclassAbstractDomainServiceAGGextendsAgg,CONTEXTextendsDomainServiceContext{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(AbstractDomainService.class);privatefinalApplicationEventPublishereventPublisher;privatefinalCrudRepositoryAGG,?repository;publicAbstractDomainService(CrudRepositoryAGG,?repository,ApplicationEventPublishereventPublisher){this.eventPublisher=eventPublisher;this.repository=repository;}

Transactional(readOnly=false)publicvoidhandle(CONTEXTcontext){try{//回调子类接口,用于扩展AGGagg=doHandle(context);//将变更持久化到DBsave2DB(agg);//发布领域事件,完成后对事件进行清理publishAndCleanEvent(agg);//成功回调,默认打印日志onSuccess(agg,context);}catch(Exceptione){//异常处理,直接中断流程onException(e,context);}}/***回调接口,用于子类进行扩展*

paramcontext*

return*/protectedabstractAGGdoHandle(CONTEXTcontext);/***异常默认处理策略,子类通过重新可以进行自定义*

parame*

paramcontext*/protectedvoidonException(Exceptione,CONTEXTcontext){LOGGER.error("failedtohandle{}",context,e);if(einstanceofRuntimeException){throw(RuntimeException)e;}thrownewRuntimeException(e);}/***默认成功回调,子类通过重写可以进行自定义*

paramagg*

paramcontext*/protectedvoidonSuccess(AGGagg,CONTEXTcontext){LOGGER.info("successtohandle{}andsync{}toDB",context,agg);}/***发布并清理领域事件*

paramagg*/privatevoidpublishAndCleanEvent(AGGagg){//1.发布领域事件agg.foreachEvent(this.eventPublisher::publishEvent);//2.清理领域事件agg.clearEvents();}/***将变更持久化到DB中*

paramagg*/privatevoidsave2DB(AGGagg){this.repository.save(agg);}}

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

AbstractCreateService源码如下:

publicabstractclassAbstractCreateServiceAGGextendsAgg,CONTEXTextendsDomainServiceContextextendsAbstractDomainServiceAGG,CONTEXT{publicAbstractCreateService(CrudRepositoryAGG,?repository,ApplicationEventPublishereventPublisher){super(repository,eventPublisher);}/***重写父类方法,进行扩展*

paramcontext*

return*/

OverrideprotectedAGGdoHandle(CONTEXTcontext){//1.生成聚合根AGGagg=instance(context);//2.执行业务访问update(agg,context);returnagg;}/***子类扩展点,可以对聚合进行业务调用*

paramagg*

paramcontext*/protectedvoidupdate(AGGagg,CONTEXTcontext){}/***子类扩展点,对聚合进行实例化*

paramcontext*

return*/protectedabstractAGGinstance(CONTEXTcontext);}

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

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

publicabstractclassAbstractUpdateServiceAGGextendsAgg,CONTEXTextendsDomainServiceContextextendsAbstractDomainServiceAGG,CONTEXT{publicAbstractUpdateService(CrudRepositoryAGG,?repository,ApplicationEventPublishereventPublisher){super(repository,eventPublisher);}/***重写父类方法,进行扩展*

paramcontext*

return*/

OverrideprotectedAGGdoHandle(CONTEXTcontext){//1.加载聚合根AGGagg=loadAgg(context);//2.验证聚合根if(agg==null){notFound(context);}//3.调用聚合根方法update(agg,context);returnagg;}/***子类扩展点,加载要操作的聚合*

paramcontext*

return*/protectedabstractAGGloadAgg(CONTEXTcontext);/***子类扩展点,当聚合丢失时进行回调*

paramcontext*/protectedabstractvoidnotFound(CONTEXTcontext);/***子类扩展点,调用聚合上的业务方法*

paramagg*

paramcontext*/protectedabstractvoidupdate(AGGagg,CONTEXTcontext);}

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

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

CreateAndEnableUserService源码如下:

ServicepublicclassCreateAndEnableUserServiceextendsAbstractCreateServiceUser,CreateAndEnableUserContext{

AutowiredpublicCreateAndEnableUserService(ApplicationEventPublishereventPublisher,CrudRepositoryUser,?repository){super(repository,eventPublisher);}

OverrideprotectedUserinstance(CreateAndEnableUserContextcontext){//实例化User聚合对象returnUser.create(context.getName(),context.getAge());}

Overrideprotectedvoidupdate(Useragg,CreateAndEnableUserContextcontext){//调用聚合上的业务方法agg.enable();}}

ModifyUserNameService源码如下:

ServicepublicclassModifyUserNameServiceextendsAbstractUpdateServiceUser,ModifyUserNameContext{privatefinalJpaRepositoryUser,Longrepository;publicModifyUserNameService(JpaRepositoryUser,Longrepository,ApplicationEventPublishereventPublisher){super(repository,eventPublisher);this.repository=repository;}

OverrideprotectedUserloadAgg(ModifyUserNameContextcontext){//加载要操作的聚合对象returnthis.repository.getById(context.getUserId());}

OverrideprotectedvoidnotFound(ModifyUserNameContextcontext){//聚合丢失,直接抛出异常thrownewUserNotFoundException(context.getUserId());}

Overrideprotectedvoidupdate(Useragg,ModifyUserNameContextcontext){//调用聚合上的业务方法agg.modifyUserName(context.getNewName());}}

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

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

ServicepublicclassUserV2ApplicationimplementsUserApplication{

AutowiredprivateCreateAndEnableUserServicecreateAndEnableUserService;

AutowiredprivateModifyUserNameServicemodifyUserNameService;

OverridepublicvoidcreateAndEnableUser(CreateAndEnableUserContextcontext){//将逻辑分发给领域服务this.createAndEnableUserService.handle(context);}

OverridepublicvoidmodifyUserName(ModifyUserNameContextcontext){//将逻辑分发给领域服务this.modifyUserNameService.handle(context);}}

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

ServicepublicclassUserV2Application2implementsUserApplication{

AutowiredprivateJpaRepositoryUser,Longrepository;

AutowiredprivateApplicationEventPublisherapplicationEventPublisher;

OverridepublicvoidcreateAndEnableUser(CreateAndEnableUserContextcontext){//使用匿名内部类实现逻辑newAbstractCreateServiceUser,CreateAndEnableUserContext(this.repository,applicationEventPublisher){

OverrideprotectedUserinstance(CreateAndEnableUserContextcontext){returnUser.create(context.getName(),context.getAge());}

Overrideprotectedvoidupdate(Useragg,CreateAndEnableUserContextcontext){agg.enable();}}.handle(context);}

OverridepublicvoidmodifyUserName(ModifyUserNameContextcontext){//使用匿名内部类实现逻辑newAbstractUpdateServiceUser,ModifyUserNameContext(this.repository,this.applicationEventPublisher){

Overrideprotectedvoidupdate(Useragg,ModifyUserNameContextcontext){agg.modifyUserName(context.getNewName());}

OverrideprotectedvoidnotFound(ModifyUserNameContextcontext){thrownewUserNotFoundException(context.getUserId());}

OverrideprotectedUserloadAgg(ModifyUserNameContextcontext){returnrepository.getById(context.getUserId());}}.handle(context);}}

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

4.Spring模板方法

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

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

publicUsergetByName(Stringname){Stringsql="select"+"id,create_time,update_time,status,name,password"+"fromtb_user"+"where"+"name=?";ListUserresult=jdbcTemplate.query(sql,newObject[]{name},newRowMapperUser(){

OverridepublicUsermapRow(ResultSetresultSet,inti)throwsSQLException{LongidForSelect=resultSet.getLong(1);java.sql.DatecreateDate=resultSet.getDate(2);java.sql.DateupdateDate=resultSet.getDate(3);IntegerstatusCode=resultSet.getInt(4);Stringname=resultSet.getString(5);Stringpassword=resultSet.getString(6);Useruser=newUser();user.setId(idForSelect);user.setCreateTime(createDate);user.setUpdateTime(updateDate);user.setStatus(UserStatus.valueOfCode(statusCode));user.setName(name);user.setPassword(password);returnuser;}});returnresult.isEmpty()?null:result.get(0);}

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

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

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

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

publicfinalclassApplicationServiceTemplateAGGextendsAgg{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(ApplicationServiceTemplate.class);privatefinalApplicationEventPublishereventPublisher;privatefinalCrudRepositoryAGG,?repository;publicApplicationServiceTemplate(ApplicationEventPublishereventPublisher,CrudRepositoryAGG,?repository){this.eventPublisher=eventPublisher;this.repository=repository;}publicCONTEXTextendsDomainServiceContextvoidexecuteCreate(CONTEXTcontext,FunctionCONTEXT,AGGinstanceFun,BiConsumerCONTEXT,AGGupdateFun){try{//1.生成聚合根AGGagg=instanceFun.apply(context);//2.执行业务访问updateFun.accept(context,agg);//3.保存聚合根save2DB(agg);publishAndCleanEvent(agg);onSuccess(agg,context);}catch(Exceptione){onException(e,context);}}publicCONTEXTextendsDomainServiceContextvoidexecuteUpdate(CONTEXTcontext,FunctionCONTEXT,AGGloadFun,ConsumerCONTEXTnotFoundFun,BiConsumerCONTEXT,AGGupdateFun){try{//1.加载聚合根AGGagg=loadFun.apply(context);//2.验证聚合根if(agg==null){notFoundFun.accept(context);}//3.调用聚合根方法updateFun.accept(context,agg);publishAndCleanEvent(agg);onSuccess(agg,context);}catch(Exceptione){onException(e,context);}}privateCONTEXTextendsDomainServiceContextvoidonException(Exceptione,CONTEXTcontext){LOGGER.error("failedtohandle{}",context,e);if(einstanceofRuntimeException){throw(RuntimeException)e;}thrownewRuntimeException(e);}privateCONTEXTextendsDomainServiceContextvoidonSuccess(AGGagg,CONTEXTcontext){LOGGER.info("successtohandle{}andsync{}toDB",context,agg);}privatevoidpublishAndCleanEvent(AGGagg){//1.发布领域事件agg.foreachEvent(this.eventPublisher::publishEvent);//2.清理领域事件agg.clearEvents();}privatevoidsave2DB(AGGagg){this.repository.save(agg);}}

该模板类与之前的代码逻辑基本一致,只是代码组织形式发生了变化。

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

ServicepublicclassUserV3ApplicationimplementsUserApplication{privatefinalJpaRepositoryUser,Longrepository;privatefinalApplicationServiceTemplateUserapplicationServiceTemplate;

AutowiredpublicUserV3Application(ApplicationEventPublishereventPublisher,JpaRepositoryUser,Longrepository){this.repository=repository;this.applicationServiceTemplate=newApplicationServiceTemplate(eventPublisher,this.repository);}

OverridepublicvoidcreateAndEnableUser(CreateAndEnableUserContextcxt){this.applicationServiceTemplate.executeCreate(cxt,context-User.create(context.getName(),context.getAge()),(createAndEnableUserContext,user)-user.enable());}

OverridepublicvoidmodifyUserName(ModifyUserNameContextcxt){this.applicationServiceTemplate.executeUpdate(cxt,context-repository.getById(context.getUserId()),context-{thrownewUserNotFoundException(context.getUserId());},(modifyUserNameContext,user)-user.modifyUserName(modifyUserNameContext.getNewName()));}}

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

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

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

5.模板方法+Builder模式

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

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

publicabstractclassBaseV4Application{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(BaseV4Application.class);

AutowiredprivateApplicationEventPublishereventPublisher;/***创建Creator,已完成对创建流程的组装*

paramrepository*

paramA*

paramCONTEXT*

return*/protectedAextendsAgg,CONTEXTextendsDomainServiceContextCreatorA,CONTEXTcreatorFor(CrudRepositoryA,?repository){returnnewCreatorA,CONTEXT(repository);}/***创建Updater,已完成对更新流程的组装*

paramaggregateRepository*

paramA*

paramCONTEXT*

return*/protectedAextendsAgg,CONTEXTextendsDomainServiceContextUpdaterA,CONTEXTupdaterFor(CrudRepositoryA,?aggregateRepository){returnnewUpdaterA,CONTEXT(aggregateRepository);}/***创建流程的Builder,主要:*1.组装流程*2.执行流程*

paramA*

paramCONTEXT*/protectedclassCreatorAextendsAgg,CONTEXTextendsDomainServiceContext{//标准仓库privatefinalCrudRepositoryA,?aggregateRepository;//实例化器,用于完成对聚合的实例化privateFunctionCONTEXT,AinstanceFun;//默认的操作成功处理器,在操作成功后进行回调privateBiConsumerA,CONTEXTsuccessFun=(agg,context)-{LOGGER.info("successtohandle{}andsync{}toDB",context,agg);};//默认的异常处理器,在操作失败抛出异常时进行回调privateBiConsumerException,CONTEXTerrorFun=(exception,context)-{LOGGER.error("failedtohandle{}",context,exception);if(exceptioninstanceofRuntimeException){throw(RuntimeException)exception;}thrownewRuntimeException(exception);};//聚合业务操作方法回调,最主要的扩展点,用于执行聚合上的业务方法privateBiConsumerA,CONTEXTupdateFun=(a,context)-{};Creator(CrudRepositoryA,?aggregateRepository){Preconditions.checkArgument(aggregateRepository!=null);this.aggregateRepository=aggregateRepository;}/***设置聚合的实例化器*

paraminstanceFun*

return*/publicCreatorA,CONTEXTinstance(FunctionCONTEXT,AinstanceFun){Preconditions.checkArgument(instanceFun!=null);this.instanceFun=instanceFun;returnthis;}/***增加聚合上的业务操作,链式模式,可以绑定多的业务操作*

paramupdater*

return*/publicCreatorA,CONTEXTupdate(BiConsumerA,CONTEXTupdater){Preconditions.checkArgument(updater!=null);this.updateFun=this.updateFun.andThen(updater);returnthis;}/***增加成功处理器,链式模式,可以绑定多个处理器*

paramonSuccessFun*

return*/publicCreatorA,CONTEXTonSuccess(BiConsumerA,CONTEXTonSuccessFun){Preconditions.checkArgument(onSuccessFun!=null);this.successFun=onSuccessFun.andThen(this.successFun);returnthis;}/***增加异常处理器,链式模式,可以绑定多个处理器*

paramerrorFun*

return*/publicCreatorA,CONTEXTonError(BiConsumerException,CONTEXTerrorFun){Preconditions.checkArgument(errorFun!=null);this.errorFun=errorFun.andThen(this.errorFun);returnthis;}/***执行创建流程*

paramcontext*

return*/publicAcall(CONTEXTcontext){Preconditions.checkArgument(this.instanceFun!=null,"instancefuncannotbenull");Preconditions.checkArgument(this.aggregateRepository!=null,"aggregateRepositorycannotbenull");Aa=null;try{//实例化聚合根a=this.instanceFun.apply(context);//在聚合根上执行业务操作this.updateFun.accept(a,context);//持久化聚合根到DBthis.aggregateRepository.save(a);//发布领域事件,并进行清理if(eventPublisher!=null){//1.发布领域事件a.foreachEvent(eventPublisher::publishEvent);//2.清理领域事件a.clearEvents();}//调用成功回调器this.successFun.accept(a,context);}catch(Exceptione){//调用异常回调器this.errorFun.accept(e,context);}returna;}}/***更新流程的Builder,主要:*1.组装流程*2.执行流程*

paramA*

paramCONTEXT*/protectedclassUpdaterAextendsAgg,CONTEXTextendsDomainServiceContext{//标准仓库privatefinalCrudRepositoryA,?aggregateRepository;//聚合加载器,用于从DB中加载聚合对象privateFunctionCONTEXT,AloadFun;//聚合丢失处理器,聚合丢失时进行回调privateConsumerCONTEXTonNotExistFun=context-{};//成功回调器,链式模式,在操作成功时调用privateBiConsumerA,CONTEXTsuccessFun=(agg,context)-{LOGGER.info("successtohandle{}andsync{}toDB",context,agg);};//异常回调器,链式模式,在操作失败抛出异常时调用privateBiConsumerException,CONTEXTerrorFun=(exception,context)-{LOGGER.error("failedtohandle{}",context,exception);if(exceptioninstanceofRuntimeException){throw(RuntimeException)exception;}thrownewRuntimeException(exception);};//业务更新器,对聚合进行业务操作privateBiConsumerA,CONTEXTupdateFun=(a,context)-{};Updater(CrudRepositoryA,?aggregateRepository){this.aggregateRepository=aggregateRepository;}/***设置聚合对象加载器,用于从DB中加载聚合*

paramloader*

return*/publicUpdaterA,CONTEXTloader(FunctionCONTEXT,Aloader){Preconditions.checkArgument(loader!=null);this.loadFun=loader;returnthis;}/***增加业务执行器,链式模式,可以绑定多个执行器*

paramupdateFun*

return*/publicUpdaterA,CONTEXTupdate(BiConsumerA,CONTEXTupdateFun){Preconditions.checkArgument(updateFun!=null);this.updateFun=updateFun.andThen(this.updateFun);returnthis;}/***增加成功回调器,链式模式,可以绑定多个回调器*

paramonSuccessFun*

return*/publicUpdaterA,CONTEXTonSuccess(BiConsumerA,CONTEXTonSuccessFun){Preconditions.checkArgument(onSuccessFun!=null);this.successFun=onSuccessFun.andThen(this.successFun);returnthis;}/***增加异常回调器,链式模式,可以绑定多个回调器*

paramerrorFun*

return*/publicUpdaterA,CONTEXTonError(BiConsumerException,CONTEXTerrorFun){Preconditions.checkArgument(errorFun!=null);this.errorFun=errorFun.andThen(this.errorFun);returnthis;}/***增加聚合丢失处理器,链式模式,可以绑定多个回调器*

paramonNotExistFun*

return*/publicUpdaterA,CONTEXTonNotFound(ConsumerCONTEXTonNotExistFun){Preconditions.checkArgument(onNotExistFun!=null);this.onNotExistFun=onNotExistFun.andThen(this.onNotExistFun);returnthis;}/***执行更新流程*

paramcontext*

return*/publicAcall(CONTEXTcontext){Preconditions.checkArgument(this.aggregateRepository!=null,"aggregateRepositorycannotbenull");Preconditions.checkArgument(this.loadFun!=null,"loadercannotbothbenull");Aa=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(Exceptione){//异常回调this.errorFun.accept(e,context);}returna;}}}

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

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

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

ServicepublicclassUserV4ApplicationextendsBaseV4ApplicationimplementsUserApplication{

AutowiredprivateJpaRepositoryUser,Longrepository;

OverridepublicvoidcreateAndEnableUser(CreateAndEnableUserContextcontext){this.User,CreateAndEnableUserContextcreatorFor(this.repository)//绑定聚合实例化逻辑.instance(createAndEnableUserContext-User.create(createAndEnableUserContext.getName(),createAndEnableUserContext.getAge()))//增加聚合业务操作.update((user,createAndEnableUserContext)-user.enable())//执行创建流程.call(context);}

OverridepublicvoidmodifyUserName(ModifyUserNameContextcontext){this.User,ModifyUserNameContextupdaterFor(repository)//绑定聚合加载器.loader(domainServiceContext-this.repository.getById(domainServiceContext.getUserId()))//增加聚合丢失处理器.onNotFound(modifyUserNameContext-{thrownewUserNotFoundException(modifyUserNameContext.getUserId());})//增加聚合业务操作.update((user,modifyUserNameContext)-user.modifyUserName(modifyUserNameContext.getNewName()))//执行更新流程.call(context);}}

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

小结

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

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

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

最后,附上源码地址源码

也可以下载“知识星球”找“草根架构师的成长史”,一起交流一起学习。

1
查看完整版本: 应用服务和模板方法擦出的火花