SpringBoot functional Web MVC, missing a way to return CompletableFuture<ResponseEntity<String>>

huangapple go评论67阅读模式
英文:

SpringBoot functional Web MVC, missing a way to return CompletableFuture<ResponseEntity<String>>

问题

我有一个正在运行的生产SpringBoot应用程序,其中的一部分需要重新设计。对我来说,删除旧的@RequestMappingResponseEntity&lt;String&gt; foo()的代码会非常有益,同时在我们尝试通过一个功能开关推出新功能的过程中,保留旧的代码作为重复的代码。所有生产租户都通过我的不再是声明性的foo()函数,而所有的测试和自动化租户可以开始使用全新的EntityResponse&lt;String&gt; bar()进行实验。

在我的脑海中,实施这个改变的方式非常清晰:

class Router{ 
  @Bean
  RouterFunction&lt;ServerResponse&gt; helloWorldRouterFunction(OldHelloWorldService oldHelloWorldService) {
    return RouterFunctions.route()
        .route(RequestPredicates.path(&quot;/helloWorld/{option}&quot;), x -&gt;
            {
              String option = x.pathVariable(&quot;option&quot;);
              if (FeatureManager.isActive()) {
                return ServerResponse.ok().body(String.format(&quot;New implementation of Hello World! your option is: %s&quot;, option));
              } else {
                // FutureServerResponse is my own bad implementation of the ServerResponse interface
                return FutureServerResponse.from(oldHelloWorldService.futureFoo(Integer.parseInt(option)));
              }
            }
        )
        .build();
  }
}

这是OldHelloWorldService::futureFoo的实现:

@RestController
static class OldHelloWorldService {
  @RequestMapping(&quot;/specialCase&quot;)
  ResponseEntity&lt;String&gt; specialCase() {
    // 一些业务逻辑
    return ResponseEntity.ok().body(&quot;Special case for Hello World with option 2&quot;);
  }
  /**
   * 旧的声明性实现,通过功能性的{@link ServerRouteConfiguration}进行路由,以便根据{@link FeatureManager#isActive()}进行动态选择
   * &lt;p&gt;
   * 正如你所看到的,在改变之前,这个函数是一个{@link RequestMapping},它处理了
   * completable future,我们可以返回具有主体的具体OK响应,以及具有位置的FOUND响应。
   */
  // @RequestMapping(&quot;/helloWorld/{option}&quot;)
  CompletableFuture&lt;ResponseEntity&lt;String&gt;&gt; futureFoo(
      // @PathVariable
      int option) {
    return CompletableFuture.supplyAsync(() -&gt; {
      if (option == 2) {
        return ResponseEntity.status(HttpStatus.FOUND)
            .location(URI.create(&quot;/specialCase&quot;))
            .build();
      } else {
        return ResponseEntity.ok().body(String.format(&quot;Old implementation of Hello World! your option is: %s&quot;, option));
      }
    });
  }
}

这个功能允许我的后端代码在将来决定发送哪种ResponseEntity。正如你所看到的,一个智能函数可以决定是显示带有OK状态的字符串消息,还是提供Location标头,并声明FOUND状态,甚至根本不提供字符串主体。因为结果类型是完全流畅的ResponseEntity,所以我有能力做我想做的事情。

现在,使用EntityResponse,你仍然可以使用CompletionStage,但只作为实际实体。在构建EntityResponse时,我需要给它一个明确的final状态。如果是OK的话,我不能在我的CompletionStage运行完之后决定它将是FOUND。

以上代码的唯一问题是,org.springframework.web.servlet.function不包含我需要的FutureServerResponse实现。我创建了自己的实现,它能正常工作,但感觉有点巧合,我不想在生产代码中使用它。

现在我感觉这个功能应该仍然存在于某个地方,为什么没有一个FutureServerResponse可以在将来决定它是什么?也许有一种解决这个问题的方法,可以(滥用)使用视图?

可能不是那么明显的是...我正在考虑切换到响应式和WebFlux,但改变整个运行时对当前生产租户会产生更大的影响,并且在功能开关的情况下进行这种转换是不可能的,因为URL必须在MVC和Flux之间共享。

这是一个小众问题,功能性Web MVC资源有限,所以我会非常感谢任何帮助。

我为这个问题创建了一个GitHub伴侣

英文:

I have a working production SpringBoot application, and part of it is getting a do-over. It would be very beneficial for me to delete my old @RequestMapping from the ResponseEntity&lt;String&gt; foo()s of my world, keeping the old code as an as a duplicate while we try to roll out the new functionality behind a feature gate.. All production tenants go through my no-longer-declarative foo() function, while all my test and automation tenants can start to tinker with a brand new EntityResponse&lt;String&gt; bar().

The way to implement the change was so clear in my mind:

class Router{ 
  @Bean
  RouterFunction&lt;ServerResponse&gt; helloWorldRouterFunction(OldHelloWorldService oldHelloWorldService) {
    return RouterFunctions.route()
        .route(RequestPredicates.path(&quot;/helloWorld/{option}&quot;), x -&gt;
            {
              String option = x.pathVariable(&quot;option&quot;);
              if (FeatureManager.isActive()) {
                return ServerResponse.ok().body(String.format(&quot;New implementation of Hello World! your option is: %s&quot;, option));
              } else {
                // FutureServerResponse is my own bad implementation of the ServerResponse interface
                return FutureServerResponse.from(oldHelloWorldService.futureFoo(Integer.parseInt(option)));
              }
            }
        )
        .build();
  }
}

Here's the implementation for OldHelloWorldService::futureFoo

@RestController
static class OldHelloWorldService {
  @RequestMapping(&quot;/specialCase&quot;)
  ResponseEntity&lt;String&gt; specialCase() {
    // some business logic
    return ResponseEntity.ok().body(&quot;Special case for Hello World with option 2&quot;);
  }
  /**
   * Old declarative implementation, routed via functional {@link ServerRouteConfiguration}
   * to allow dynamic choice based on {@link FeatureManager#isActive()}
   * &lt;p&gt;
   * as you can see, before the change, this function was a {@link RequestMapping} and it handled the
   * completable future, we could return both concrete OK responses with a body, and FOUND responses with a location.
   */
  // @RequestMapping(&quot;/helloWorld/{option}&quot;)
  CompletableFuture&lt;ResponseEntity&lt;String&gt;&gt; futureFoo(
      // @PathVariable
      int option) {
    return CompletableFuture.supplyAsync(() -&gt; {
      if (option == 2) {
        return ResponseEntity.status(HttpStatus.FOUND)
            .location(URI.create(&quot;/specialCase&quot;))
            .build();
      } else {
        return ResponseEntity.ok().body(String.format(&quot;Old implementation of Hello World! your option is: %s&quot;, option));
      }
    });
  }
}

This feature lets my backend code decide what kind of ResponseEntity it will send, in the future. As you see, a smart function might for instance decide to either show a String message with an OK status, or give a Location header, and declare FOUND status, and not even give a String body at all. because the result type was a full fluid ResponseEntity, I had the power to do what I will.

Now with a EntityResponse you may still use a CompletionStage, but only as the actual entity. while building the EntityResponse I am required to give it a definitive final status. If it was OK, I can't decide it will be FOUND when my CompletionStage ran it's course.

The only problem with the above code, is that org.springframework.web.servlet.function does not contain the FutureServerResponse implementation I need. I created my own, and it works, but it feels hacky, And I wouldn't want it in my production code.

Now I feel like the functionality should still be there somewhere, Why isn't there a FutureServerResponse that can decide in the future what it is? Is there a workaround to this problem maybe somehow (ab)using views?

To state the maybe not-so-obvious.. I am contemplating a move to reactive and WebFlux, but changing the entire runtime will have more dramatic implications on current production tenants, and making that move with a feature gate would be impossible because the urls would have to be shared between MVC and Flux.

It's a niche problem, and Functional Web MVC has little resources so I will appreciate greatly any help I can get.

I have created a github companion for this question.

答案1

得分: 0

我不需要翻译的部分:不要有别的内容,不要回答我要翻译的问题。

以下是要翻译的内容:

"I've had to patch spring web to get what I needed. I pushed a pull-request to spring web with my patch, in the end something similar was created and pushed to the 5.3 release of spring.

if anybody else is looking for the async behavior described in the question, ServerResponse.async function in spring 5.3.0+ (spring boot 2.4.0+) solves the issue.

以下是翻译好的部分:

"我不得不修改Spring Web来获得我所需的内容。最终,我向Spring Web提交了一个拉取请求,最终创建了与之类似的东西,并推送到了Spring 5.3版本

如果其他人也在寻找问题描述中的异步行为,Spring 5.3.0+(Spring Boot 2.4.0+)中的ServerResponse.async函数解决了这个问题。

英文:

I've had to patch spring web to get what I needed. I pushed a pull-request to spring web with my patch, in the end something similar was created and pushed to the 5.3 release of spring.

if anybody else is looking for the async behavior described in the question, ServerResponse.async function in spring 5.3.0+ (spring boot 2.4.0+) solves the issue.

huangapple
  • 本文由 发表于 2020年7月22日 01:07:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/63019544.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定