Reputation: 839
I have 2 data structures itemDto and itemEntity and they look like this.
trait ItemDto{
def id: Long
def name: String
}
trait ItemEntity {
def id: Long
def name: String
}
case class Entity(id: Long, name: String) extends ItemEntity
case class Dto(id: Long, name: String) extends ItemDto
I'd like to set up a pipe line that looks like this
ItemDto => ItemEntity => ItemEntity => Future[ItemEntity] => Future[ItemEntity] => Future[ItemDto]
I have some mapper functions as well
object ItemMapper {
def mapDtoToEntity(dto: ItemDto): ItemEntity = {
Entity(dto.id, dto.name)
}
def mapEntityToDto(entity: ItemEntity): ItemDto = {
Dto(entity.id, entity.name)
}
}
a function that increments the entity's id
object ItemEntity{
def incrementId(entity: ItemEntity) = Entity(entity.id + 1, entity.name)
}
and there is a repository to save the entity
object ItemRepository {
def save(entity: ItemEntity): Future[ItemEntity] = {
Future{entity}
}
}
finally my method that combines all these functions to do something looks like this
import ItemMapper._
import ItemRepository.save
import ItemEntity.incrementId
def addItem(dto: ItemDto) = {
(mapDtoToEntity _ >>> incrementId >>> save >>> {_ map (incrementId _ >>> mapEntityToDto) })(dto)
}
After the save method is called in the chain, I have to break into another function. My question, is there a way to lift the value out of the future and put it back in so that my pipeline looks something like this?
(mapDtoToEntity _ >>> incrementId >>> save ?!? incrementId ?!? mapEntityToDto)(dto)
Where ?!?
is the hypothetical operator.
It could be from the scala library as well. It doesn't need to be from scalaz.
Upvotes: 0
Views: 116
Reputation: 9820
The most useful concept from scalaz (or cats) to compose functions which return some F[_]
(like Future
here) is the Keisli
arrow.
Kleisli
makes it easy to compose multiple A => F[B]
functions. Here we actually only have one ItemEntity => Future[ItemEntity]
function.
We can create a Kleisli[Future, ItemDto, ItemEntity]
from the first 3 functions and then map over it using the two last :
import scalaz._, Scalaz._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
val composition1: Kleisli[Future, ItemDto, ItemDto] =
Kleisli(mapDtoToEntity _ >>> incrementId >>> save)
.map(incrementId _ >>> mapEntityToDto)
The actual operation you want to do is save
, the rest is a transformation from Dto
to Entity
and back while incrementing the id (twice).
We can do the transformation and the incrementation before and after save
by calling dimap
(an operation we get from Profunctor
) on Kleisli(save)
:
val composition2: Kleisli[Future, ItemDto, ItemDto] =
Kleisli(save).dimap(
mapDtoToEntity _ >>> incrementId,
incrementId _ >>> mapEntityToDto)
Or by using dimap
twice :
val composition3: Kleisli[Future, ItemDto, ItemDto] =
Kleisli(save).dimap(incrementId, incrementId)
.dimap(mapDtoToEntity, mapEntityToDto)
Which all give the same result :
import scala.concurrent.Await
import scala.concurrent.duration._
val futureDto: Future[ItemDto] = composition3(Dto(1L, "test"))
Await.result(futureDto, 1.second)
// ItemDto = Dto(3,test)
With cats this would look more or less the same :
>>>
operator, so it would need to be replaced by andThen
.dimap
is curried, so we would write dimap(f)(g)
instead of dimap(f, g)
.Upvotes: 1