Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Scala - Context propagation

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

Scala - Context propagation

Here are slides of my talk, I gave at scala user group meetup in Kyiv, about the overview of the various techniques of context propagation in Scala. Starting from adding one more parameter to each method and ending with ReaderT in combination with Tagless Final.

Avatar for Yaroslav Hryniuk

Yaroslav Hryniuk

September 26, 2018
Tweet

Other Decks in Programming

Transcript

  1. Goals & Conditions • define approach for (business-)context propagation a.

    context is fixed b. context is immutable • decouple context from business logic as much as possible
  2. Test application case class RequestContext(requestId: String) case class Car(carId: String,

    brand: String, model: String) case class Company(companyId: String, name: String, rentedCars: List[Car]) //business logic trait Api { def findFirstRentedCar(companyId: String): Try[Car] } //access to context trait Repository { def findCardById(carId: String): Try[Car] def findCompanyById(companyId: String): Try[Company] }
  3. Additional parameter - API implementation class Api(repository: Repository) { def

    findFirstRentedCar(companyId: String, context: RequestContext): Try[Car] = { for { company <- repository.findCompanyById(companyId, context) car <- company.rentedCars.headOption match { case Some(carId) => repository.findCardById(carId, context) case None => Failure(new RuntimeException("no rented cars")) } } yield car } }
  4. Additional parameter - Repository implementation class Repository { def findCardById(carId:

    String, context: RequestContext): Try[Car] = { println(context.requestId) ??? } def findCompanyById(companyId: String, context: RequestContext): Try[Company] = { println(context.requestId) ??? } }
  5. Additional parameter - Pros and Cons Pros 1. Zero learning

    curve 2. Safe 3. Performant Cons 1. Verbose 2. Context coupled to business
  6. i. Additional parameter - API implementation //business logic class Api(repository:

    Repository) { def findFirstRentedCar(companyId: String)(implicit context: RequestContext): Try[Car] = { for { company <- repository.findCompanyById(companyId) car <- company.rentedCars.headOption match { case Some(carId) => repository.findCardById(carId) case None => Failure(new RuntimeException("no rented cars")) } } yield car } }
  7. i. Additional parameter-Repository implementation class Repository { def findCardById(carId: String)(implicit

    context: RequestContext): Try[Car] = { println(context.requestId) ??? } def findCompanyById(companyId: String)(implicit context: RequestContext): Try[Company] = { println(context.requestId) ??? } }
  8. i. Additional Parameter - Usage object Main extends App{ implicit

    val context = RequestContext("asd") val repository = new Repository() val api = new Api(repository) api.findFirstRentedCar("company-id-1") }
  9. i. Additional parameter - Pros and Cons Pros 1. >Zero

    learning curve 2. Safe 3. Performant Cons 1. Verbose 2. <Context coupled to business
  10. ThreadLocal - API implementation class Api(repository: Repository) { def findFirstRentedCar(companyId:

    String): Try[Car] = { for { company <- repository.findCompanyById(companyId) car <- company.rentedCars.headOption match { case Some(carId) => repository.findCardById(carId) case None => Failure(new RuntimeException("no car rented")) } } yield car } }
  11. ThreadLocal - Repository implementation class Repository(threadLocal: ThreadLocal[RequestContext]) { def findCardById(carId:

    String): Try[Car] = { println(threadLocal.get().requestId) ??? } def findCompanyById(companyId: String): Try[Company] = { println(threadLocal.get().requestId) ??? } }
  12. ThreadLocal - Usage object Main extends App{ val value =

    new ThreadLocal[RequestContext]() val repository = new Repository(value) val api = new Api(repository) value.set(RequestContext("ctx-1")) api.findFirstRentedCar("asdasd") }
  13. ThreadLocal - Pros and Cons Pros 1. Flat learning curve

    2. Non verbose 3. Business decoupled from context 4. Performant Cons 1. Not safe 2. Can’t be used in non-blocking app
  14. ThreadLocal - Future class Api(repository: Repository)(implicit ec:ExecutionContext) { def findFirstRentedCar(companyId:

    String): Future[Car] = { for { company <- repository.findCompanyById(companyId) car <- company.rentedCars.headOption match { case Some(carId) => repository.findCardById(carId) case None => Future.failed(new RuntimeException("no car rented")) } } yield car } }
  15. Recap Additional parameter - API implementation class Api(repository: Repository) {

    def findFirstRentedCar(companyId: String, context: RequestContext): Try[Car] = { for { company <- repository.findCompanyById(companyId, context) car <- company.rentedCars.headOption match { case Some(carId) => repository.findCardById(carId, context) case None => Failure(new RuntimeException("no rented cars")) } } yield car } }
  16. Additional parameter -> Lambda (currying) def findFirstRentedCar(companyId: String, context: RequestContext):

    Try[Car] def findFirstRentedCar(companyId: String): RequestContext => Try[Car]
  17. Additional parameter -> Lambda (currying) class Api(repository: Repository) { def

    findFirstRentedCar(companyId: String): RequestContext => Try[Car] = { context:RequestContext => for { company <- repository.findCompanyById(companyId)(context) car <- company.rentedCars.headOption match { case Some(carId) => repository.findCardById(carId)(context) case None => Failure(new RuntimeException("no rented car")) } } yield car } }
  18. Additional parameter -> Lambda (currying) class Repository { def findCardById(carId:

    String): RequestContext => Try[Car] = { context: RequestContext => println(context.requestId) ??? } def findCompanyById(companyId: String): RequestContext => Try[Company] = { context: RequestContext => println(context.requestId) ??? } }
  19. ReaderT - API implementation class Api(repository: Repository) { def findFirstRentedCar(companyId:

    String): ReaderT[Try, RequestContext, Car] = { for { company <- repository.findCompanyById(companyId) car <- company.rentedCars.headOption match { case Some(carId) => repository.findCardById(carId) case None => ReaderT { (context: RequestContext) => Failure[Car](new RuntimeException("no car rented")): Try[Car] } } } yield car } }
  20. ReaderT - Repository implementation class Repository { def findCardById(carId: String):

    ReaderT[Try, RequestContext, Car] = ReaderT { context: RequestContext => println(context.requestId) ??? } def findCompanyById(companyId: String): ReaderT[Try, RequestContext, Company] = ReaderT { context: RequestContext => println(context.requestId) ??? } }
  21. ReaderT - usage object Main extends App { val context

    = RequestContext("request-id-1") val repository = new Repository() val api = new Api(repository) api.findFirstRentedCar("company-id-1").run(context) }
  22. ReaderT - Pros and Cons Pros 1. Safe Cons 1.

    Verbose 2. Steep learning curve 3. <Context coupled to business
  23. Tagless-final application case class Car(carId: String, brand: String, model: String)

    case class Company(companyId: String, name: String, rentedCars: List[Car]) trait Api[F[_]] { def findFirstRentedCar(companyId: String): F[Car] } trait Repository[F[_]] { def findCardById(carId: String): F[Car] def findCompanyById(companyId: String): F[Company] }
  24. Tagless-final - API implementation class Api[F[_]](repository: Repository[F])(implicit ME: MonadException[F]) {

    def findFirstRentedCar(companyId: String): F[Car] = { for { company <- repository.findCompanyById(companyId) car <- company.rentedCars.headOption match { case Some(rentedCarId) => repository.findCardById(rentedCarId) case None => ME.raiseError(new RuntimeException("no car rented")) } } yield car } }
  25. Tagless-final - Repository implementation class RepositoryImpl extends Repository[MonadStack] { def

    findCardById(carId: String): ReaderT[Try, RequestContext, Car] = ReaderT { context: RequestContext => println(context.requestId) ??? } def findCompanyById(companyId: String): ReaderT[Try, RequestContext, Company] = ReaderT { context: RequestContext => println(context.requestId) ??? } }
  26. Tagless-final - usage object Main extends App { val context

    = RequestContext("request-id-1") type MonadStack[A] = ReaderT[Try, RequestContext, A] type MonadException[F[_]] = MonadError[F, Throwable] val repository: Repository[MonadStack] = new RepositoryImpl() val api = new Api[MonadStack](repository) api.findFirstRentedCar("company-id-1") // ReaderT[Try, RequestContext, Car] .run(context) // Try[Car] }
  27. Tagless-final + ReaderT - Pros and Cons Pros 1. Safe

    2. Non verbose 3. Business decoupled from context 4. Bonus - can be used with any monad Cons 1. Steep learning curve 2. Performant?
  28. Q&A