Introducing Fluent – the seamless translation layer

In Domain Driven Design (DDD) it is recommended to introduce a translation layer (aka anticorruption layer) between 2 bounded contexts. The role of the anticorruption layer is to avoid any concepts to leak from one domain into the other.

This is a sound idea as it keeps the domains isolated from each other ensuring they can evolve independently. After having implemented several anticorruption layers I realised that, although useful, they also introduced a lot of boilerplate code that doesn’t add much value to the business.

To this extent, let me introduce Fluent, a library that aims at getting rid of this boilerplate code by leveraging all the power of Shapeless and its generic programming.

If you follow this blog you’d probably noticed it’s been a while since the last post. Part of the reasons was that I was working on the first implementation of Fluent, struggling to understand how Shapeless and implicit resolution works.

First things first, let’s start by a brief overview of Fluent, how to get started and use it. Then in another post I’ll show you some of the internals and the lessons I learned along the way.

Setup

First of all you need to add the following lines to your build.sbt

resolvers += Resolver.bintrayRepo("beyondthelines", "maven")

libraryDependencies += "beyondthelines" %% "fluent" % "0.0.1"

Context

As we don’t want to have any leaks into/from our internal domain model we need a translation layer to convert domain objects between their external and internal representation. Quite often both domains share some similarities and writing code to translate a case class into another similar (but not the same) case class isn’t very exciting.

Fluent exploits the fact that both domain models share some similarities (e.g. some fields are named the same but have different types) in order to convert a case class into another one.

Just a side note on protobuf: Protobuf is a handy tool to define the external domain model, plus there is a Scala plugin for protobuf: ScalaPB. ScalaPB generates case classes corresponding to the protobuf definitions. So in the end it doesn’t really matter how you get your domain case classes because Fluent is just a tool to translate one case class into another case class.

Converting a simple case class

So let’s walk through an example and define our domain models. (I am not going to use protobuf here in order to keep things simple but the only thing that matter is that you end up with a set of case classes):

object External {
  case class Circle(x: Double, y: Double, radius: Double, colour: Option[String])
}
object Internal {
  case class Point(x: Double, y: Double)
  sealed trait Colour
  object Colour {
    case object Blue extends Colour
    case object Red extends Colour
    case object Yellow extends Colour
  }
  case class Circle(origin: Point, radius: Double, colour: Option[Colour])
}

As you can see the External.Circle and Internal.Circle look similar. However the Internal model is reacher as it defines types for Point and Colour whereas the External model uses only primitive types (which makes sense if the external domain has to be serialised and set over the network or stored on disk).

Let’s create an instance from the external domain:

val externalCircle = External.Circle(
  x = 1.0,
  y = 2.0,
  radius = 3.0,
  colour = Some("Red")
)

Writing code to translate from one domain to the other isn’t very exciting, so let’s use Fluent to do just that.

import fluent._
import cats.instances.option._

val internalCircle = external.translateTo[Internal.Circle]

Yes, that’s it only 2 imports and a call to translateTo and we’re done! Isn’t that great?
All the rules used to translate from one domain to the other are provided by a set of implicit definitions provided by Fluent.

In this case it’s easy to figure out what Fluent does:
External.Circle contains a x and a y of type Double so Fluent can create a Point from an External.Circle. The radius can be taken as it is and colour needs to be turned into the correct type (Note that if the colour doesn’t exist in the expected values it would fail at runtime).

As you can see by using the same field names in both domains (although with different types) Fluent is able to generate the right instance using Shapeless Generic under the hood.

Using user-defined functions

Great, but what if we need more rules than what’s already available? Well, in that case one can always define its own rules using implicit functions. Let’s consider the following case to illustrate this use case:

object External {
  case class Post(author: String, body: String, timestamp: Long)
}
import java.time.Instant

object Internal {
  case class Author(name: String)
  case class Post(author: Author, body: String, tags: List[String], timestamp: Instant)
}

Let’s create a simple external message:

val externalPost = External.Post(
  author = "Misty",
  body = "#Fluent is a cool library to implement your #DDD #translationLayer seamlessly",
  timestamp = 1491823712002L
)

Now if we try to convert it straight away with

val internalPost = externalPost.transformTo[Internal.Post]

we get a compilation error

could not find implicit value for parameter transformer: 
  fluent.shapeless.Transformer[External.Post,Internal.Post]

This is just the compiler telling us that it doesn’t know how to transform an External.Post into an Internal.Post.

Let’s have a closer look at the message fields: author is fine – Fluent knows how to transform a String into an Author case class and the author field name matches in both representation. body is even easier as both name and type match.

There is no obvious way (for the compiler) to extract the tags from the External.Post. Let’s define a simple function that do just that:

implicit def extractTags(post: External.Post): List[String] =
  post.body.split("\\s").toList.filter(_.startsWith("#"))

Note that we can’t use a function that takes only a string to extract the tags

// doesn't work as Fluent can't figure out which field to use to extract the tags
implicit def extractTags(body: String): List[String] = ???

That’s because Fluent can’t figure out that it needs to apply this function on the body field.

There is a similar problem for timestamp. This time the field name matches but there is no implicit definition to turn a Long into an Instant. Let’s fix it:

implicit def toInstant(timestamp: Long): Instant =
  Instant.ofEpochMilli(timestamp)

Now we these 2 implicit functions in scope we can compile again and run it.

Conclusion

Really this is all there is to know if you want to use this library. It is still in a very experimental stage however you can have a look at the source code on github.

The error messages you get at compile time are not always useful but reading through this blog post should have give you a clue on the way Fluent works and how to overcome such errors by defining your own implicit functions.

In case this sounds like magic to you (or you’re just curious about how things work) stay tuned for the next post covering the internals of Fluent where we’ll explore some Shapeless features and implicit resolution rules.