Last active
June 14, 2022 07:52
-
-
Save domdorn/e5e5222ccfea5ffb123cada437c692c8 to your computer and use it in GitHub Desktop.
spring boot / web: allow vavr javaslang future as return type
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import io.micrometer.core.annotation.Timed; | |
import io.swagger.v3.oas.annotations.Operation; | |
import io.swagger.v3.oas.annotations.media.Schema; | |
import io.swagger.v3.oas.annotations.responses.ApiResponse; | |
import io.vavr.concurrent.Future; | |
import io.vavr.control.Option; | |
import lombok.AllArgsConstructor; | |
import lombok.Value; | |
import lombok.extern.slf4j.Slf4j; | |
import lombok.val; | |
import org.springframework.http.ResponseEntity; | |
import org.springframework.validation.annotation.Validated; | |
import org.springframework.web.bind.annotation.GetMapping; | |
import org.springframework.web.bind.annotation.PathVariable; | |
import org.springframework.web.bind.annotation.RequestParam; | |
import org.springframework.web.bind.annotation.RestController; | |
import javax.validation.Valid; | |
import javax.validation.constraints.Max; | |
import javax.validation.constraints.Min; | |
import javax.validation.constraints.NotNull; | |
import java.util.Date; | |
@RestController | |
@Slf4j | |
@Validated | |
@AllArgsConstructor | |
public class ControllerNew implements ControllerHelpers { | |
final Service service; | |
@Timed | |
@GetMapping(path = "${mvc.context-path}/v3/scenario/{scenarioId}/assets/", name = "getAssetList") | |
@Operation(operationId = "getAssetList", method = "GET", | |
responses = { | |
@ApiResponse(responseCode = "200", ref = "#/components/schemas/GetAssetListResponse", description = "success"), | |
@ApiResponse(responseCode = "400", description = "failure") | |
}, | |
tags = {"assets"} | |
) | |
public Future<ResponseEntity<GetAssetListResponse>> getAssetList( | |
@PathVariable("scenarioId") @Valid ScenarioId scenarioId | |
) { | |
val correlationId = CorrelationId.random(); | |
val userId = UserId.of("STATIC") ; | |
val serviceResponse = service.getScenarioAssets( | |
new GetScenarioAssetsQuery( | |
correlationId, | |
userId, | |
scenarioId, | |
... | |
) | |
); | |
return serviceResponse.map(either -> either.map(GetAssetListController.GetAssetListResponse::fromServiceResponse) | |
.getOrElseThrow(left -> badRequest(correlationId, "Failed to get asset list", left.getError()))) | |
.map(ResponseEntity::ok) | |
; // no more converting to completableFuture | |
} | |
.... | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import io.micrometer.core.annotation.Timed; | |
import io.swagger.v3.oas.annotations.Operation; | |
import io.swagger.v3.oas.annotations.media.Schema; | |
import io.swagger.v3.oas.annotations.responses.ApiResponse; | |
import io.vavr.control.Option; | |
import lombok.AllArgsConstructor; | |
import lombok.Value; | |
import lombok.extern.slf4j.Slf4j; | |
import lombok.val; | |
import org.springframework.http.ResponseEntity; | |
import org.springframework.validation.annotation.Validated; | |
import org.springframework.web.bind.annotation.GetMapping; | |
import org.springframework.web.bind.annotation.PathVariable; | |
import org.springframework.web.bind.annotation.RequestParam; | |
import org.springframework.web.bind.annotation.RestController; | |
import javax.validation.Valid; | |
import javax.validation.constraints.Max; | |
import javax.validation.constraints.Min; | |
import javax.validation.constraints.NotNull; | |
import java.util.Date; | |
import java.util.concurrent.CompletableFuture; | |
@RestController | |
@Slf4j | |
@Validated | |
@AllArgsConstructor | |
public class ControllerOld implements ControllerHelpers { | |
final Service service; | |
@Timed | |
@GetMapping(path = "${mvc.context-path}/v3/scenario/{scenarioId}/assets/", name = "getAssetList") | |
@Operation(operationId = "getAssetList", method = "GET", | |
responses = { | |
@ApiResponse(responseCode = "200", ref = "#/components/schemas/GetAssetListResponse", description = "success"), | |
@ApiResponse(responseCode = "400", description = "failure") | |
}, | |
tags = {"assets"} | |
) | |
public CompletableFuture<ResponseEntity<GetAssetListResponse>> getAssetList( | |
@PathVariable("scenarioId") @Valid ScenarioId scenarioId | |
) { | |
val correlationId = CorrelationId.random(); | |
val userId = UserId.of("STATIC") ; | |
val serviceResponse = service.getScenarioAssets( | |
new GetScenarioAssetsQuery( | |
correlationId, | |
userId, | |
scenarioId, | |
... | |
) | |
); | |
return serviceResponse.map(either -> either.map(GetAssetListController.GetAssetListResponse::fromServiceResponse) | |
.getOrElseThrow(left -> badRequest(correlationId, "Failed to get asset list", left.getError()))) | |
.map(ResponseEntity::ok) | |
.toCompletableFuture(); // converting to completableFuture for spring support | |
} | |
... | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.dominikdorn.web.configuration; | |
import io.vavr.concurrent.Future; | |
import org.springframework.core.MethodParameter; | |
import org.springframework.lang.Nullable; | |
import org.springframework.web.context.request.NativeWebRequest; | |
import org.springframework.web.context.request.async.DeferredResult; | |
import org.springframework.web.context.request.async.WebAsyncUtils; | |
import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandler; | |
import org.springframework.web.method.support.ModelAndViewContainer; | |
// implementing AsyncHandlerMethodReturnValueHandler makes sure that this ReturnValueHandler is processed before | |
// normal value handlers (which would transform the future to a json value) | |
// see https://github.com/spring-projects/spring-framework/issues/17674 for details | |
public class VavrFutureHandlerMethodReturnValueHandler implements AsyncHandlerMethodReturnValueHandler { | |
public VavrFutureHandlerMethodReturnValueHandler() { | |
} | |
@Override | |
public boolean supportsReturnType(MethodParameter returnType) { | |
Class<?> type = returnType.getParameterType(); | |
return Future.class.isAssignableFrom(type); | |
} | |
@Override | |
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, | |
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { | |
if (returnValue == null) { | |
mavContainer.setRequestHandled(true); | |
return; | |
} | |
DeferredResult<?> result; | |
if (returnValue instanceof Future) { | |
result = adaptVavrFuture((Future<?>) returnValue); | |
} else { | |
// Should not happen... | |
throw new IllegalStateException("Unexpected return value type: " + returnValue); | |
} | |
WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(result, mavContainer); | |
} | |
private DeferredResult<Object> adaptVavrFuture(Future<?> future) { | |
DeferredResult<Object> result = new DeferredResult<>(); | |
future | |
.onSuccess(result::setResult) | |
.onFailure(result::setErrorResult); | |
return result; | |
} | |
@Override | |
public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) { | |
return returnValue instanceof Future; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.dominikdorn.web.configuration; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.web.method.support.HandlerMethodReturnValueHandler; | |
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | |
import java.util.List; | |
@Configuration | |
public class WebMvcConfiguration { | |
@Bean | |
public WebMvcConfigurer corsConfigurer() { | |
// ... configure all kinds of things here, like how to handle cors etc. | |
return new WebMvcConfigurer() { | |
@Override | |
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) { | |
handlers.add(new VavrFutureHandlerMethodReturnValueHandler()); | |
} | |
}; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment