Use of implicit to transform case classes in Scala

Sujit Kamthe
Being Professional
Published in
4 min readMar 10, 2018

Using different entities for different layers like repository, service and view is a common (anti)pattern followed while developing web applications.

Separate entity per layer pattern

Using different model at each layer allows us to represent entities differently at each layer in a way such that the structure of the model makes sense at that layer. This is a good way of achieving separation of concern and lose coupling.

On the contrary use of single domain model across all layers simplifies writing the code in many cases but many times we end up polluting domain with view or repository layer related logic. Also use of single domain across layers brings in tight coupling between layers.

The most annoying problem with using separate entities at each layer is mapping them to and from other entities in neighboring layers. In languages like Java it’s very difficult to tackle this situation. Hence I prefer to use a single domain across all layers as much as possible and use separate entities only when it is absolutely needed.

Is it good or bad to use separate entity per layer is a bigger discussion and is not a part of this article. In this article we will try to address the issue of mapping entities with the help of implicit in Scala.

Let’s assume we want to find a person with given name from the database.

Following PersonEntity case class represents a row in database and is used at repository layer to return a row from database.

Different models PersonEntity and Person are used at database and service layer respectively
PersonEntity.scalacase class PersonEntity(firstName: String, lastName: String, city: String, state: String, pin: String)

From the perspective of domain modelling in service layer, it makes sense to create a separate entity for Addressand then use it in a Person entity as a composition.

Person.scalacase class Address(city: String, state: String, pin: String)

case class Person(firstName: String, lastName: String, address: Address)

Let getPerson be a function in a repository which returns a Person with given name.

PersonRepository.scaladef getPerson(name: String): Person = {
val personEntity: PersonEntity =
db.run {
persons.find(_.firstName == name).result
}
Person(
personEntity.firstName,
personEntity.lastName,
Address(
personEntity.city,
personEntity.state,
personEntity.pin
)
)
}

If you observe the code snippet above, it’s too much of a work to transform PersonEntity into Person and we have to repeat this every time we want to map the entity.

Let’s add a as method to PersonEntity which takes a mapper function f which will transform it into the target entity T.

PersonEntity.scalacase class PersonEntity(firstName: String,
lastName: String,
city: String,
state: String,
pin: String) {
def as[T](f: PersonEntity => T) = f(this)
}

and let’s define a mapper function f in a companion object of PersonEntity, which transforms PersonEntity into Person case class.

PersonEntity.scalaobject PersonEntity {
def personMapper = (personEntity: PersonEntity) =>
Person(
personEntity.firstName,
personEntity.lastName,
Address(
personEntity.city,
personEntity.state,
personEntity.pin
)
)
}

This will simplify the repository method as follow:

PersonRepository.scaladef getPerson(name: String): Person = {
val personEntity: PersonEntity = ...
personEntity.as(PersonEntity.personMapper)
}

Now we can reuse the mapper function anywhere we want to map PersonEntity into Person. But still we have to always pass the person mapper explicitly. This can be avoided by the use of implicit.

Lets mark the mapper function parameter f in as function implicit.

PersonEntity.scalacase class PersonEntity(firstName: String,
lastName: String,
city: String,
state: String,
pin: String) {
def as[T](implicit f: PersonEntity => T): T = f(this)
}

Let’s also mark the personMapper function defined in the companion object as implicit.

PersonEntity.scalaobject PersonEntity {
implicit def personMapper = (personEntity: PersonEntity) =>
Person(...)
}

Then we can transform PersonEntity into Person using following concise syntax.

PersonRepository.scaladef getPerson(name: String): Person = {
val personEntity: PersonEntity = ...
personEntity.as[Person]
}

This is possible because we have marked the mapper function f as implicit. And we have defined an implicit mapper function which goes from PersonEntity to Person (PersonEntity => Person) .

Scala compiler find the implicitly defined mapping function which is in the lexical scope as it’s defined in the companion object of the PersonEntity. It picks the appropriate function based on the generic type parameter passed to the application of as function.

We can map the same entity into different target entities, since our as method is generic. All we have to do is add another mapper function for the target entity type.

Let’s say we want to map the same PersonEntity to Student class.

Student.scalacase class Student(firstName: String, city: String)

All we have to do is define another mapper function in the companion object of PersonEntity.

PersonEntity.scala
...
implicit def
studentMapper = (personEntity: PersonEntity) =>
Student(personEntity.firstName, personEntity.city)

Then we can do the transformation similarly.

personEntity.as[Student]

This pattern using implicit simplifies the use of separate models at each level as the overhead of mapping entities is completely abstracted out.

Happy coding. :)

Read More:

https://medium.com/beingprofessional/understanding-functor-and-monad-with-a-bag-of-peanuts-8fa702b3f69e

--

--