Being Professional
Published in

Being Professional

Use of implicit to transform case classes in Scala

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

--

--

--

Learning to become a professional one step at a time

Recommended from Medium

How to Export Pandas DataFrame to CSV

Day 28 of 30-Day LeetCode Challenge

Telemedicine Software Cost, Technologies, and Development Timeline

enticing Garnet Silv http://ift.tt/2krO4K7

Using MvvmCross with Xamarin.Forms — Part 1

How to set up a Laravel Project Cloned from GitHub.

Dockerizing Rails Applications Part 2: Automation

Real-Time Bitcoin Price Streaming using Oracle Cloud Services

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Sujit Kamthe

Sujit Kamthe

Software Craftsman | Lead Consultant @ Thoughtworks

More from Medium

Let’s learn about function composition in Scala

Restricting sum type instance creation

Polymorphism in Scala

Implementing a Clean Architecture Application in Scala — Part 1

Our Clean Architecture