Last active
February 4, 2019 11:01
-
-
Save JoolsF/8301295d68b9346b0f8124470cf51beb to your computer and use it in GitHub Desktop.
EitherT - FutureEither example 1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import FutureEither.FutureEither | |
| import cats.data.EitherT | |
| import scala.concurrent.{ExecutionContext, Future} | |
| //ServiceError | |
| sealed trait ServiceError { | |
| val msg: String | |
| } | |
| case class NoSuchUserError(msg: String) extends ServiceError | |
| case class DataValidationError(msg: String) extends ServiceError | |
| case class ResourceAccessError(msg: String) extends ServiceError | |
| case class InsufficientFundsError(msg: String) extends ServiceError | |
| // FutureEither helper Singleton | |
| object FutureEither { | |
| //FutureEither types | |
| type ServiceResult[+A] = Either[ServiceError, A] | |
| type FutureEither[A] = EitherT[Future, ServiceError, A] | |
| def apply[A](f: => ServiceResult[A])(implicit ec: ExecutionContext): FutureEither[A] = | |
| apply(Future(f)) | |
| def apply[A](f: Future[ServiceResult[A]]): FutureEither[A] = | |
| EitherT(f) | |
| def apply[A](o: Option[A], error: ServiceError)(implicit ec: ExecutionContext): FutureEither[A] = o match { | |
| case Some(r) => apply(Right(r)) | |
| case None => apply(Left(error)) | |
| } | |
| def errored[A](error: ServiceError): FutureEither[A] = | |
| apply(Future.successful(Left(error))) | |
| def failed[A](ex: Throwable): FutureEither[A] = | |
| apply(Future.failed(ex)) | |
| def successful[A](result: A): FutureEither[A] = | |
| apply(Future.successful(Right(result))) | |
| def apply[A](o: Future[Option[A]], error: ServiceError)(implicit ec: ExecutionContext): FutureEither[A] = | |
| EitherT( | |
| o.map(_ match { | |
| case Some(v) => Right(v) | |
| case None => Left(error) | |
| }) | |
| ) | |
| } | |
| /* | |
| * Example use | |
| */ | |
| case class User(id: Long, firstName: String, lastName: String, balance: Long, isActive: Boolean) | |
| case class HttpResponse(code: Int, message: String) | |
| case class WithdrawlRequest(userId: Long, amount: Long) | |
| object UserDao { | |
| private implicit val executionContext = scala.concurrent.ExecutionContext.global | |
| val repo: Seq[User] = Seq( | |
| User(1L, "Julian", "Fenner", 1000L, true), | |
| User(2L, "John", "Doe", 0L, false) | |
| ) | |
| def getUser(id: Long)(implicit executionContext: ExecutionContext): Future[Option[User]] = | |
| Future(repo.find(_.id == id)) | |
| } | |
| object Service { | |
| import UserDao._ | |
| private def debitAccount(user: User, amount: Long)(implicit executionContext: ExecutionContext): FutureEither[User] = { | |
| if (amount > user.balance) { | |
| FutureEither.errored(InsufficientFundsError(s"User ${user.id} does not have enough money to fulfil this request")) | |
| } else { | |
| //TODO update repo | |
| FutureEither.successful(user.copy(balance = user.balance - amount)) | |
| } | |
| } | |
| def debitAccount(id: Long, amount: Long)(implicit executionContext: ExecutionContext): FutureEither[User] = | |
| for { | |
| user <- FutureEither(getUser(id), NoSuchUserError(s"user $id does not exist")) | |
| res <- debitAccount(user, amount) | |
| } yield res | |
| } | |
| object TestRouter1 { | |
| def withdrawMoney(id: Long, amount: Long)(implicit executionContext: ExecutionContext): Future[HttpResponse] = { | |
| Service.debitAccount(id.toLong, amount.toLong).value.flatMap(_ match { | |
| case Left(ex: InsufficientFundsError) => Future.successful(HttpResponse(409, ex.msg)) | |
| case Left(ex: ResourceAccessError) => Future.successful(HttpResponse(400, ex.msg)) | |
| case Left(ex: NoSuchUserError) => Future.successful(HttpResponse(404, ex.msg)) | |
| case Right(user) => Future.successful(HttpResponse(200, s"User ${user.id} has successfully withdrawn $amount - remaining balance is ${user.balance}")) | |
| } | |
| ).recover { case ex => HttpResponse(500, "Oh no! Something went wrong on our side") } | |
| } | |
| } | |
| object FutureEitherTest extends App { | |
| import TestRouter1._ | |
| private implicit val executionContext = scala.concurrent.ExecutionContext.global | |
| withdrawMoney(3, 10).map(println) | |
| withdrawMoney(2, 10).map(println) | |
| withdrawMoney(1, 2000).map(println) | |
| withdrawMoney(1, 650).map(println) | |
| // HttpResponse(409,User 1 does not have enough money to fulfil this request) | |
| // HttpResponse(200,User 1 has successfully withdrawn 650 - remaining balance is 350) | |
| // HttpResponse(400,User 2 is inactive) | |
| // HttpResponse(404,User 3 does not exist) | |
| Thread.sleep(2000) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment