Search code examples
scalaakkadomain-driven-designakka-httpmdc

How should I get this value through DDD to async code in Akka HTTP


I'm trying to write an Akka HTTP webservice using domain-driven design but I'm struggling to pass technical data received by the webservice to the code doing the work inside a Future, namely a correlationId sent by the client to my webservice.

My understanding of DDD is that as a choice of implementation, the correlationId shouldn't appear in the domain:

package domain
trait MyRepository {
  def startWork(): Future[Unit]
}

To make it available to my implementation code without it being a parameter of the method, I'm thinking of using thread-local storage like org.slf4j.MDC or a ThreadLocal. However, I don't know if that would work or if several calls to the webservice would be handled by the same thread and overwrite the value.

Here is the implementation:

package infra
class MyRepository(implicit executor: ExecutionContextExecutor) extends domain.MyRepository {
  override def startWork(): Future[Unit] = {
    Future {
      val correlationId = ??? // MDC.get("correlationId") ?
      log(s"The correlationId is $correlationId")
    }
  }
}

And the route in my webservice:

val repo = new infra.MyRepository()
val route = path("my" / "path") {
  post {
    parameter('correlationId) { correlationId =>
      ??? // MDC.put("correlationId", correlationId) ?
      onComplete(repo.startWork()) {
        complete(HttpResponse(StatusCodes.OK))
      }
    }
  }
}

My question is twofold:

  • Is my general design sound and good DDD?
  • Would using org.slf4j.MDC or a ThreadLocal work or is there a better / more Akka-friendly way to implement it?

Solution

  • Thread-locals (including MDC, though a Lightbend subscription includes tooling to propagate the MDC alongside messages and futures) in Akka are generally a poor idea because it's generally not guaranteed that a given task (a Future in this case, or the actor handling a sent message) will execute on the same thread as the thread that requested the task (and in the specific case where that task is performing [likely-blocking] interactions with an external service/DB (implied by the use of Future {}), you pretty much don't want that to happen). Further, even if the task ends up executing on the same thread that requested the task, it's somewhat unlikely that no other task which could have mutated the MDC/thread-local would've executed in the meantime.

    I myself don't see a problem with passing the correlation ID as an argument to startWork: you've already effectively exposed it by passing it through the HTTP endpoint.