1.接口返回值

在普通的web项目中,调用接口返回数据,如下,不出错返回一种,出错了,返回另外一种。前端是直接可以拿到返回的信息的。

@GetMapping("decreaseMoney")
  @ResponseBody
  public void decreaseMoney(Integer id,BigDecimal money){
      try {
        //do something
        return "成功:something";
      }catch (Exception ex){
        ex.printStackTrace();
        return "失败:something";
      }
  }

但是引入byteTCC框架后,不能这么处理。因为事务是commit还是rollback,就是根据是否有异常来的控制事务走向的,如果try掉了,那事务最终都是会被commit的,就不会再rollback了。

关于这个问题的处理,请教了下byteTCC的维护者,非常耐心的回答了关于这个问题的疑问。这里记录下交流的这个过程,没有格式的是我提问的,有引用格式的是作者的回答:

对话

当调用失败后,我想拿到这个错误堆栈信息,怎么获取呢?我想把错误信息拿到存日志或者是返回

byteTCC框架--关于接口返回问题的讨论_spring


正常情况下,这个修改成功是可以返回到页面,但是调用出错时,这个return没法返回到页面

byteTCC框架--关于接口返回问题的讨论_远程分支_02

错误一般都是用异常来表示啊,用字符串表示很少见。你这个是用于显示的,但是SpringCloud更倾向于代表一个服务一个接口

比如我这个,一个服务调用了2个服务,其中一个出错了,我需要给前端一个反馈,但是我在这里没法拿到出错的那个服务的错误信息

那这种一般怎么处理呢

显示的内容更多的是由Web系统来负责的,微服务负责显示个人觉得不太合理,更何况,服务中的异常会改变事务完成方向呢,如果你把异常捕捉了返回一个字符串,事务就总是commit了。
你可以考虑抛出一个异常啊,不过如果ByteTCC在commit/rollback处理过程中也碰到异常,以事务异常优先抛出

现在出现异常时,页面直接就这样,实际开发中,这样处理不妥啊

byteTCC框架--关于接口返回问题的讨论_远程分支_03

这是ByteTCC在rollback过程中也碰到异常了,抛出的是SystemException

说错了,是在commit过程中
HTTP接口一般返回500码就能标识错误了,当然,如果你想在应用层面设置自己的业务异常码,可以考虑用Filter拦截这个接口然后转换,直接返回字符串肯定是不可以的

还是有点不懂,我们这习惯在正常时返回一种编码和结果,出错时在catch中返回一种编码和结果。意思是,我说的这种实现,这里是做不了的是吧。我看那个catch中的打印语句执行了,但是return未执行。

那就让前端自己判断状态码?

你这种做法不是不可以,只是说:在参与事务处理的controller中这样做不可以,不参与事务处理的controller中这样做是没问题的。原因也很简单,spring的声明式事务是要根据异常来判断事务是否commit/rollback的,如果业务把异常信息都自己吞掉了,那所有事务就都commit再也没有rollback了。。。

然后,再进一步的说,一般而言,SpringCloud的controller更倾向于就是一个接口(类似dubbo接口那种,提供的是一个服务能力),很大概率是要直接参与事务的,所以不建议这样做。但你要是一定让一个spring cloud接口做成显示的接口,倒也不是不行,那就别让它参与事务就好了

“spring的声明式事务是要根据异常来判断事务是否commit/rollback的,如果业务把异常信息都自己吞掉了,那所有事务就都commit再也没有rollback了”这句话,大概理解了,我try catch后,发现即使出错了,事务也commit了,导致数据变更了

这是spring声明式事务的机制,根据异常来判断是否需要回滚。没有异常时肯定就是提交了。

当然,也并不是说你在controller中抛出异常就只能显示那个500了,你可以考虑在框架层面对其进行处理,构建自己业务系统的业务异常码,只要在全局事务之外就可以

还有2个疑问:我A调用B和C服务,比如这里,bank服务调用user,company服务,那我这个接口中,bank不仅掉了b,c的接口,还调用自己本地的方法,那这个本地方法也是需要tcc逻辑的是吧?2.这个时候,b和c的controller中接口我不返回信息,那A这个接口,是要对页面提供返回值的,这种推荐怎么处理?

我个人比较推荐的做法是,就象你调用dubbo接口一样,如果没有实质的信息需要返回,那就别返回信息了。没异常就表示成功了,有异常consumer就会收到一个异常信息。至于页面显示什么,那是consumer收到成功/错误之后自己决定的,而不应该由provider来决定页面来显示什么

provider端接口返回一个“调用成功”、“调用失败”这中信息,是完全没有意义的。

HTTP接口,成功时200返回码就可以;返回4xx/5xx时就是失败了。在此基础上,你可以细化一下,比如你们希望所有的请求都返回200,但是错误时响应体内有failure-code,比如00000是成功,00001是创建订单失败,00002表示库存不足等等,可以考虑通过Filter在框架层面封装,而不是在controller中做这个事情

comsumer的接口,也不需要显式的返回信息,直接void,没问题就成功了,有问题的话,页面调用这个接口时,会直接拿到某种异常信息,判断下即可。可以这么简单的处理吗?或者,用filter来处理。这2种方式?

一般而言,微服务之间很少需要这样的封装,直接以异常或者HTTP返回码更适合,只有web系统和微服务之间,可能才需要这种方式,当然也不能一概而论,主要还是看业务系统自行规划

你可以参考一下ByteTCC的CompensableCoordinatorController的做法,出错时返回500,然后在header中加上错误的类型

注意,是说你的Filter可以参考CompensableCoordinatorController,业务Controller不能象CompensableCoordinatorController那样写,业务controller有错误需要回滚就要抛异常

2.关于远程分支的问题

问:org.bytesoft.bytetcc.supports.rpc.CompensableInterceptorImpl#afterReceiveResponse中,执行javax.transaction.Transaction#delistResource,这个入参的participantDelistFlag感觉始终都是false,进而导致flag始终都是TMSUCCESS。这样的设计是因为一旦远端资源参与到TCC事务,就不可以从resourceList中删除是吗?

正常的逻辑,一个远程分支被加入到事务中,是要在一次远程调用之后的。但是,如果在远程调用之后才将其纳入远程分支列表,有一个问题:远程请求发出去之后,网络中断/发起方宕机,这种情况下,请求接收方确实是发起方的一个远程分支,但是故障恢复时发起方无从得知该远程分支(因为事务日志中没有记录该远程分支信息);
为了解决这个问题,ByteTCC在发起远程调用之前就将该请求的接收方先行纳入远程分支列表,但是这样做又会引入另外一个问题:很可能接收方实际上并没有接收到请求,却存在于发起方的远程分支列表中。
为了解决这个问题,所以在收到响应之后,判断如果该调用成功了或者至少接收方确实收到请求了,就认定enlist是成功的;否则,就认为enlist是失败的,要将该远程分支删除。你看到这个participantDelistFlag的值为false,就说明enlist是成功的,不需要删除