How to reduce boilerplate with Kleisli

72 Views Asked by At

I follow the design of the book Functional and Reactive Domain Modeling

And for some service methods, it only delegates work to the repository layer. Is there a way to reduce this boilerplate :

trait FeedbackServiceImpl extends FeedbackService {
  override def saveTFE(feedback: TripFeedbackEvent) =
    Kleisli[Future, Context, Either[String, Id]] { ctx => ctx.feedbackRepo.save(feedback) }

  override def saveLFE(feedback: LibraryFeedbackEvent) =
    Kleisli[Future, Context, Either[String, Id]] { ctx => ctx.feedbackRepo.save(feedback) }

  override def findByUser(userId: Id) =
    Kleisli[Future, Context, Seq[FeedbackEvent]] { ctx => ctx.feedbackRepo.findByUser(userId) }

  override def all =
    Kleisli[Future, Context, Seq[FeedbackEvent]] { ctx => ctx.feedbackRepo.all }

  override def findByTip(tipId: Id) =
    Kleisli[Future, Context, Seq[FeedbackEvent]] { ctx => ctx.feedbackRepo.findByTip(tipId) }

}
2

There are 2 best solutions below

0
On BEST ANSWER

We can create a combinator :

private def kleisli[M[_], A](f: FeedbackRepository => M[A]) = Kleisli.kleisli(f).local[Context](_.feedbackRepo)

Hence we gain 2 things :

  • avoid declaring the type by helping the type inference mechanism
  • avoid calling ctx.feedbackRepo by using local

So we can use :

trait Feedbacks {
  def saveTFE(feedback: TripFeedbackEvent) = kleisli(_.save(feedback)) 
  def saveLFE(feedback: LibraryFeedbackEvent) = kleisli(_.save(feedback))
  def findByUser(userId: Id) = kleisli(_.findByUser(userId))
  ...
}
0
On

Can you just define a function that does all the boilerplate ? Something like:

def repo2Kleisli[T](f: Repo => Future[T]): Kleisli[Future, Context, T]

You could possibly even make it implicit and reduce your code to something like:

 override def saveTFE(feedback: TripFeedbackEvent) = (repo: Repo) => repo.save(feedback)