At my current project, we use snapshots for versioning certain important states in our application. As we have the requirement to “jump back” to these states for some specialised queries, we needed a way to access a specific snapshot, without having to create a Persistent-Actor / Eventsourced-Behaviour that could mess up the journal of that Entity or would start to consume the event-log after loading the snapshot.

So, basically what we needed is the future-api that is already defined in SnapshotStore.scala in Akka.

Unfortunatelly, this API is not easily accessible, as it can only be implemented inside an Actor and we’re not supposed to access an actors internal state from the outside, thus making it impossible to use the already provided Future-based API.

As a workaround, I’ve created these “helpers” to easily access the snapshot-store and interact with its API.

package akka.persistence
// code to accompany the blog post at
// https://dominikdorn.com/2021/02/accessing-the-akka-persistence-snapshot-store-future-api/
// This class gives you access to the future based API of a SnapshotStore by providing the SnapshotStore's ActorRef
private[akka] object SnapshotStoreAccessor {
def apply(ref: ActorRef)(
implicit timeout: Timeout = Timeout(5.seconds)): SnapshotStoreBase =
new SnapshotStoreBase {
import SnapshotProtocol._
override def loadAsync(
persistenceId: String,
criteria: SnapshotSelectionCriteria): Future[Option[SelectedSnapshot]] =
(ref ? SnapshotProtocol.LoadSnapshot(persistenceId, criteria = criteria, toSequenceNr = criteria.maxSequenceNr))
.mapTo[SnapshotProtocol.Response]
.flatMap {
case LoadSnapshotResult(snapshot, _) => Future.successful(snapshot)
case LoadSnapshotFailed(cause) => Future.failed(cause)
case _ => Future.failed(new IllegalStateException("unexpected result"))
}(ExecutionContext.parasitic)
override def saveAsync(metadata: SnapshotMetadata, snapshot: Any): Future[Unit] =
(ref ? SnapshotProtocol.SaveSnapshot(metadata, snapshot))
.mapTo[SnapshotProtocol.Response]
.flatMap {
case SaveSnapshotSuccess(_) => Future.successful(())
case SaveSnapshotFailure(_, cause) => Future.failed(cause)
case _ => Future.failed(new IllegalStateException("unexpected result"))
}(ExecutionContext.parasitic)
override def deleteAsync(metadata: SnapshotMetadata): Future[Unit] =
(ref ? SnapshotProtocol.DeleteSnapshot(metadata))
.mapTo[SnapshotProtocol.Response]
.flatMap {
case DeleteSnapshotSuccess(_) => Future.unit
case DeleteSnapshotFailure(_, cause) => Future.failed(cause)
case _ => Future.failed(new IllegalStateException("unexpected result"))
}(ExecutionContext.parasitic)
override def deleteAsync(persistenceId: String, criteria: SnapshotSelectionCriteria): Future[Unit] =
(ref ? SnapshotProtocol.DeleteSnapshots(persistenceId, criteria))
.mapTo[SnapshotProtocol.Response]
.flatMap {
case DeleteSnapshotsSuccess(_) => Future.unit
case DeleteSnapshotsFailure(_, cause) => Future.failed(cause)
case _ => Future.failed(new IllegalStateException("unexpected result"))
}(ExecutionContext.parasitic)
}
}

Here, I first extracted (bascially copy-pasted) the future based API from SnapshotStore.scala, so I can easily reference the type later – I called this trait SnapshotStoreBase.

Then I created the “SnapshotStoreAccessor”, which basically given the ActorRef of a SnapshotStore gives you access to its Future-based API.

package com.dominikdorn.akkatest
import akka.actor.ActorSystem
import akka.persistence.SnapshotSelectionCriteria
import akka.dominikdorn.SnapshotStoreGetter
import com.dominikdorn.akkatest.testentity.FilledState
class Example {
def main(args: Array[String]): Unit = {
implicit val actorSystem = ActorSystem.create()
implicit val ec = actorSystem.dispatcher
val snapshotStore = SnapshotStoreGetter.getSnapshotStore("jdbc-snapshot-store")
val maybeSnapshot = snapshotStore.loadAsync("my-entity-1", SnapshotSelectionCriteria(maxSequenceNr = 500))
val filledState = maybeSnapshot.collect{
case Some(selectedSnapshot) => selectedSnapshot.snapshot.asInstanceOf[FilledState]
}
filledState.foreach(stateOfSnapshot => {
println("the name of the entity is = " + stateOfSnapshot.name)
})
}
}
view raw Example.scala hosted with ❤ by GitHub

So by now, we have a trait that models the future-based way of accessing the Snapshot-Store and an implementation that allows us to access this using the Ask-Pattern.

What is still missing is a way to get a hold on the ActorRef of the SnapshotStore, so we can use that one together with the SnapshotStoreAccessor.

For this, I created the SnapshotStoreGetter:

package akka.dominikdorn
// code to accompany the blog post at
// https://dominikdorn.com/2021/02/accessing-the-akka-persistence-snapshot-store-future-api/
object SnapshotStoreGetter {
private def getSnapshotStoreActorRef(system: ActorSystem, snapshotPluginId: String): ActorRef =
Persistence(system).snapshotStoreFor(snapshotPluginId)
private def getSnapshotStoreFor(ref: ActorRef)(
implicit ec: ExecutionContext): SnapshotStoreBase = SnapshotStoreAccessor(ref)
def getSnapshotStore(snapshotPluginId: String)(implicit ac: ActorSystem): SnapshotStoreBase = {
val ref = getSnapshotStoreActorRef(ac, snapshotPluginId)
getSnapshotStoreFor(ref)(ac.dispatcher)
}
// zio helper
def snapshotStore(snapshotPluginId: String): ZIO[Has[ActorSystem], Nothing, SnapshotStoreBase] =
for {
actorSystem < ZIO.service[ActorSystem]
ref = getSnapshotStoreActorRef(actorSystem, snapshotPluginId)
snapshotStore < ZIO.fromFuture(implicit ec => Future.successful(getSnapshotStoreFor(ref))).orDie
} yield snapshotStore
}

Here, we first have a Method “getSnapshotStoreActorRef” which uses an internal Akka API to get the reference to the SnapshotStore actor of the given “snapshotStorePluginId”. As the underlying method in akka.persistence.Persistence is scoped to the akka package, we have to make sure this accessor is also in the akka package.
The “getSnapshotStoreFor” method than just takes that ActorRef and created the SnapshotStoreAccessor. Finally, the “getSnapshotStore” is our public API, giving access to the future-based API by just providing the ID and an implicit ActorSystem.

So how do we now use this to access our Snapshot? Easy! Just look at this example:

package com.dominikdorn.akkatest
import akka.actor.ActorSystem
import akka.persistence.SnapshotSelectionCriteria
import akka.dominikdorn.SnapshotStoreGetter
import com.dominikdorn.akkatest.testentity.FilledState
class Example {
def main(args: Array[String]): Unit = {
implicit val actorSystem = ActorSystem.create()
implicit val ec = actorSystem.dispatcher
val snapshotStore = SnapshotStoreGetter.getSnapshotStore("jdbc-snapshot-store")
val maybeSnapshot = snapshotStore.loadAsync("my-entity-1", SnapshotSelectionCriteria(maxSequenceNr = 500))
val filledState = maybeSnapshot.collect{
case Some(selectedSnapshot) => selectedSnapshot.snapshot.asInstanceOf[FilledState]
}
filledState.foreach(stateOfSnapshot => {
println("the name of the entity is = " + stateOfSnapshot.name)
})
}
}
view raw Example.scala hosted with ❤ by GitHub

When you’re using PostgreSQL together with Slick, chances are high, that you’re also using the Slick-PG library. You probably came to this post, because you try to map your custom ADT to a List/Sequence of that and want to store it as a text[] (or other array) in PostgreSQL. When trying to do so, the compiler gave you an error like:

could not find implicit value for parameter tt: slick.ast.TypedType[List[xxx.UseCase]]

You already tried creating a MappedColumnType[UseCase, String], but for some reason, Slick hasn’t picked it up / is not using it for Sequences / Lists.

Go no further, the solution is near!

trait MyPostgresProfile
extends ExPostgresProfile
with PgArraySupport
with PgDate2Support
with PgRangeSupport
with PgHStoreSupport {
def pgjson = "jsonb"
// Add back `capabilities.insertOrUpdate` to enable native `upsert` support; for postgres 9.5+
override protected def computeCapabilities: Set[Capability] =
super.computeCapabilities + slick.jdbc.JdbcCapabilities.insertOrUpdate
override val api = MyAPI
object MyAPI extends API with ArrayImplicits with DateTimeImplicits with RangeImplicits with HStoreImplicits {
val strSimpleJdbcArrayListType: SimpleArrayJdbcType[String] = new SimpleArrayJdbcType[String]("text")
implicit val strListTypeMapper: JdbcType[List[String]] =
strSimpleJdbcArrayListType.to(_.toList)
// here we
implicit val useCaseListTypeMapper: JdbcType[List[UseCase]] = strSimpleJdbcArrayListType
.mapTo[UseCase](str => UseCase.of(str), uc => uc.label)
.to(_.toList)
}

Simply create a new JdbcType[List[YourType]] based on the SimpleArrayJdbcType[String] and then use the mapTo[YourType] method.

I hope you found this helpful!

Scala HMac SHA256

21 Nov
2020

As I repeatedly (e.g. every 2nd year) have to find the right code to generate a HMAC SHA256 hash from a string, I’m sharing this (for me and others) here. The tricky part is the String-Format to 64 chars. Most code I’ve found had this set to 32 chars (like used for MD5) which resulted in too short hashes for certain inputs.

object HMAC {
def generateHMAC(sharedSecret: String, input: String): String = {
val secret = new SecretKeySpec(sharedSecret.getBytes, "HmacSHA256") //Crypto Funs : 'SHA256' , 'HmacSHA1'
val mac = Mac.getInstance("HmacSHA256")
mac.init(secret)
val hashString: Array[Byte] = mac.doFinal(input.getBytes)
// this makes sure that the string is 64chars long and gets padded with 0 if its shorter
String.format("%064x", new BigInteger(1, hashString))
}
}
view raw HMacSha256.scala hosted with ❤ by GitHub

I hope you find this useful!

This is mainly a note to self on how to build inheritance hierarchies (e.g. Animal -> [Cat / Dog] ) with Play-JSON.

import play.api.libs.json._
sealed trait Animal {
val `type`: String
}
case class Cat(name: String) extends Animal {
override val `type`: String = "cat"
}
case class Dog(name: String) extends Animal {
override val `type`: String = "dog"
}
trait AnimalJsonSupport
extends de.heikoseeberger.akkahttpplayjson.PlayJsonSupport {
implicit val catReads: Reads[Cat] =
(JsPath \ "name").read[String].map(Cat(_))
implicit val dogReads: Reads[Dog] =
(JsPath \ "name").read[String].map(Dog(_))
implicit val catWrites: Writes[Cat] = e =>
JsObject(Seq("type" > JsString("cat"), "name" > JsString(e.name)))
implicit val dogWrites: Writes[Dog] = e =>
JsObject(Seq("type" > JsString("dog"), "name" > JsString(e.name)))
implicit val animalReads: Reads[Animal] = Reads[Animal](jsValue => {
(jsValue \ "type").toEither
.map(
v =>
v.as[String] match {
case "cat" => catReads.map(_.asInstanceOf[Animal]).reads(jsValue)
case "dog" => dogReads.map(_.asInstanceOf[Animal]).reads(jsValue)
case t =>
JsError((JsPath \ "type"), "unkown animal type $t")
.asInstanceOf[JsResult[Animal]]
}
)
.fold(v => JsError(JsPath, v).asInstanceOf[JsResult[Animal]], v => v)
})
implicit val animalWrites: Writes[Animal] = Json.writes[Animal]
}
view raw Animal.scala hosted with ❤ by GitHub

import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import play.api.libs.json.Json
class AnimalJsonSupportSpec
extends AnyWordSpec
with Matchers
with AnimalJsonSupport
{
val catJsonString =
"""
|{
|"type":"cat",
|"name":"test"
|}
|""".stripMargin.replace("\n", "")
val dummyRequestResponse = Cat("test")
"The AnimalJsonSupport" when {
"handling the Cat-Type" should {
"correctly serialize the response" in {
Json.toJson(dummyRequestResponse).toString() should ===(catJsonString)
}
"correctly deserialize the cat response" in {
val parseResult = Json.fromJson[Animal](Json.parse(catJsonString))
parseResult.get should ===(dummyRequestResponse)
}
}
"handling the Dog-Type" should {
"correctly deserialize the dog response" in {
val json =
"""
|{
|"type":"dog",
|"name":"test2"
|}
|""".stripMargin.replace("\n", "")
val parseResult = Json.fromJson[Animal](Json.parse(json))
parseResult.get should ===(Dog("test2"))
}
}
"handling other types" should {
"fail trying to deserialize a unknown type" in {
val json = """
|{
|"type":"unknown",
|"name":"test"
|}
|""".stripMargin.replace("\n", "")
val result = Json.fromJson[Animal](Json.parse(json))
result.isError shouldBe(true)
}
}
}
}
view raw AnimalJsonSpec.scala hosted with ❤ by GitHub

Documentation can be found here:

As mentioned in my previous articles about using VAVR together with Spring, use the following classes to make VAVRs Option and all Collection types based on Seq work correctly with SpringDoc. Just place them in a package that gets scanned by Spring!

import com.fasterxml.jackson.databind.JavaType;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.converter.ModelConverter;
import io.swagger.v3.core.converter.ModelConverterContext;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.media.Schema;
import java.util.Iterator;
import org.springframework.stereotype.Component;
@Component
public class VavrOptionSupportConverter implements ModelConverter {
@Override
public Schema resolve(
AnnotatedType annotatedType, ModelConverterContext context, Iterator<ModelConverter> chain) {
JavaType javaType = Json.mapper().constructType(annotatedType.getType());
if (javaType != null) {
Class<?> cls = javaType.getRawClass();
if (io.vavr.control.Option.class.equals(cls)) {
annotatedType =
new AnnotatedType()
.type(javaType.containedType(0))
.ctxAnnotations(annotatedType.getCtxAnnotations())
.parent(annotatedType.getParent())
.schemaProperty(annotatedType.isSchemaProperty())
.name(annotatedType.getName())
.resolveAsRef(annotatedType.isResolveAsRef())
.jsonViewAnnotation(annotatedType.getJsonViewAnnotation())
.propertyName(annotatedType.getPropertyName())
.skipOverride(true);
return this.resolve(annotatedType, context, chain);
}
}
return (chain.hasNext()) ? chain.next().resolve(annotatedType, context, chain) : null;
}
}

import com.fasterxml.jackson.databind.JavaType;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.converter.ModelConverter;
import io.swagger.v3.core.converter.ModelConverterContext;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import java.util.Iterator;
import org.springframework.stereotype.Component;
@Component
public class VavrSeqSupportConverter implements ModelConverter {
@Override
public Schema resolve(
AnnotatedType annotatedType, ModelConverterContext context, Iterator<ModelConverter> chain) {
JavaType javaType = Json.mapper().constructType(annotatedType.getType());
if (javaType != null) {
Class<?> cls = javaType.getRawClass();
if (io.vavr.collection.Seq.class.isAssignableFrom(cls)) {
annotatedType =
new AnnotatedType()
.type(javaType.containedType(0))
.ctxAnnotations(annotatedType.getCtxAnnotations())
.parent(annotatedType.getParent())
.schemaProperty(annotatedType.isSchemaProperty())
.name(annotatedType.getName())
.resolveAsRef(annotatedType.isResolveAsRef())
.jsonViewAnnotation(annotatedType.getJsonViewAnnotation())
.propertyName(annotatedType.getPropertyName())
.skipOverride(true);
return new ArraySchema().items(this.resolve(annotatedType, context, chain));
}
}
return (chain.hasNext()) ? chain.next().resolve(annotatedType, context, chain) : null;
}
}

A big shoutout to bnasslahsen who provided me with the VavrOptionSupportConverter that I adjusted to work with the collection types (see this issue)

During the Corona crisis, I was in urgent need of a Webcam. While I thought I ordered a Logitech C930e Business webcam, I got shipped a C930c which is basically the same one but the chinese version.

As it is now the second time I was spending time to find drivers / controller software for this piece of hardware, I’ll save the driver links here (for myself and whoever finds this):

MacOSX: LogiCameraSettings_3.0.12.pkg taken from here: https://support.logi.com/hc/en-001/articles/360024693154-Downloads-Webcam-C930e – yes I know it says c930e), but the software works:

For Windows 10, this package worked for me:

https://download01.logi.com/web/ftp/pub/techsupport/cameras/Webcams/LogiCameraSettings_2.10.4.exe

I’m not really sure why Logitech has different versions for europe and china, or why they don’t let us flash the european firmware on the china one, but they’ll probably have their reasons. As my right the return the camera is already over, I’ll be sticking with it for now and just handle the few chinese characters.

As already documented in my previous article about Spring and VAVR, I’m using the Future’s provided by VAVR as return types in my controller.

What I’m also using, is the SpringDoc OpenAPI library to generate my OpenAPI specification. By default, SpringDoc has no clue about VAVR and how to handle its Future type.

Thus, by default, SpringDoc transfers the VAVR-Future into a wrapper and thus creates a Swagger/OpenAPI-Component for this wrapper, having all the methods of VAVRs Future instead of the type it is wrapping.

To disable this, I added the following call to the class configuring my SpringDoc integration:

package com.dominikdorn.sample.configuration;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.vavr.concurrent.Future;
import org.springdoc.core.GroupedOpenApi;
import org.springdoc.core.converters.ConverterUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class OpenAPIConfiguration {
static {
ConverterUtils.addResponseWrapperToIgnore(Future.class);
}
@Bean
public GroupedOpenApi publicOpenAPI( ) {
return GroupedOpenApi
.builder()
.setGroup("web")
.packagesToScan("com.dominikdorn.sample.web")
.build();
}
@Bean
public GroupedOpenApi v3OpenAPI() {
return GroupedOpenApi
.builder()
.setGroup("v3")
.packagesToScan("com.dominikdorn.sample.v3.web")
.build();
}
}

I’ve specified it in a static block in the component so that it calls the also static method “addResponseWrapperToIgnore” in SpringDocs’ ConverterUtils, telling it to treat VAVRs Future as a wrapper (or better to ignore it and look at the underlying value).

As I’m using VAVR (formerly known as JavaSLang) in my current Spring-Boot project, I was looking for a way to use the futures returned by my services directly instead of converting them to CompletionStages.

My old controller code looked like this:

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
}
}
view raw ControllerOld.java hosted with ❤ by GitHub

as you can see, the return type of the controller is set to be a CompletionStage and we have to convert the vavr-future to that data type manually.

Wouldn’t it be much nicer, to simple be able to return the Future<T> type of vavr directly? This is how the controller should look like!

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
}
….
}
view raw ControllerNew.java hosted with ❤ by GitHub

Awesome, so no more converting to CompletableFuture. But how do we get there?

As long as there is no published project containing the following file, we have to add it ourselves:

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;
}
}

We’re specifying our own “HandlerMethodReturnValueHandler” . Important here is that it implements the “AsyncHandlerMethodReturnValueHandler” interface which makes sure that Spring will handle this handler before normal handlers that e.g. transform values to json.

After that, we simply need to register this Handler with Spring. Most likely, you already have a WebMvcConfigurer in your project. If yes, just add the “addReturnValueHandlers” method to your existing one or create a completely new one like I did here:

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());
}
};
}
}

Enjoy using your vavr-futures in your Spring Boot applications now.

In an upcoming post I’ll probably look on how to effectively work with the Either type of Vavr (because I can’t stand these nasty exceptions just to return expected error states!)

Like what you see? Let me know in the comments!

If you’re developing Scala apps and let them be checked by Codacy, you might have enabled the check “Imports should be sorted alphabetically“.
What sounds easy, isn’t in fact.

Here are my findings, summarised as example imports.

import akka.actor.ActorRef import akka.Done // upper/lowercase doesn't matter import akka.streams.Source import java.util.ArrayList // this needs to be in the middle of the packages, most IDEs put it into an extra block import mycompany.mypackage._ // underscore is always before import mycompany.mypackage.mysubpackage.mysubsubpackage.A import mycompany.mypackage.{ClassA, ClassB, ClassC} import mycompany.otherpackage.SomeProtocol // space before dot import mycompany.otherpackage.SomeProtocol.{MessageA, MessageB} import org.slf4j.LoggerFactory import pureconfig._ // again, underscore before import pureconfig.generic.{ExportMacros, ProductHint} import scala.concurrent.{ExecutionContext, Future} import scala.collection.JavaConverters._ import scala.io.Source import scala.util.{Failure, Success} // normal IDEs put all scala.* classes into an extra block at the end of all import statements import slick.basic.DatabaseConfig import slick.jdbc.JdbcProfile
Code language: Scala (scala)

I hope this helps someone.. I’m sure, I’ll check back on this page in about a week again….

In an event-sourced environment, you sometimes have to introduce an artificial delay to some actions to make sure read-sides had the time to update themselves. If you’re using the classic routing mechanism with a routes-file, you can add such a delay declaratively, like here:

+ delay800 PUT /products/:productId controllers.ProductCRUDController.update(productId: UUID)

This would delay the response of the update-action by 800 Milliseconds, hopefully giving the other consuming services enough time to catch up.

To make this work, you have to add a filter to your application, like this one:

import akka.actor.ActorSystem import akka.stream.Materializer import play.api.mvc.{Filter, RequestHeader, Result} import play.api.routing.Router import scala.concurrent.duration._ import scala.concurrent.{ExecutionContext, Future} class DelayFilter(val mat: Materializer, implicit val ec: ExecutionContext, actorSystem: ActorSystem) extends Filter { override def apply(nextFilter: RequestHeader => Future[Result])(rh: RequestHeader): Future[Result] = { val handler = rh.attrs.get(Router.Attrs.HandlerDef) val delay: Option[Int] = handler .flatMap( _.modifiers .find(s => s.startsWith("delay"))) .map(s => Integer.parseInt(s.substring("delay".length))) .filter(_ > 0) val result = nextFilter.apply(rh) delay match { case Some(d) => akka.pattern.after(d.millis, actorSystem.scheduler)(result) case None => result } } }
Code language: Scala (scala)

and of course, enable the filter in your application.conf

play.filters.enabled+=mypackage.filters.DelayFilter
Code language: Properties (properties)

Did this help you in your project? Let me know in the comments!

top