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;OverrideTransactional(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);}}OverrideTransactional(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模式,各种模式各有优缺点,需要根据场景进行定制,以发挥其强大的能力。
各模式特点简单汇总如下:
最后,附上源码地址源码
也可以下载“知识星球”找“草根架构师的成长史”,一起交流一起学习。