Skip to content

Instantly share code, notes, and snippets.

@Tvaroh
Last active April 21, 2017 22:07
Show Gist options
  • Save Tvaroh/e6df88cd34bd08014eb704ed43cb3fe7 to your computer and use it in GitHub Desktop.
Save Tvaroh/e6df88cd34bd08014eb704ed43cb3fe7 to your computer and use it in GitHub Desktop.
package com.rms.miu.common.db.slick
import java.time.{Duration, LocalDateTime}
import cats.{Monad, ~>}
object simple {
object common {
object db {
object impl {
import cats.free.Free
import slick.dbio.DBIO
type FreeDBIO[T] = Free[DBIO, T]
}
}
}
object model {
type UserId = String
type Password = String
case class Settings(userId: UserId, passwordValidityPeriod: Duration)
case class User(id: UserId, password: Password, passwordLastChangedAt: LocalDateTime) {
def isPasswordExpired(passwordValidityPeriod: Duration): Boolean =
passwordLastChangedAt isBefore LocalDateTime.now().minus(passwordValidityPeriod)
}
}
import model._
object db {
object api {
trait SettingsRepository[F[_]] {
def findSettings(userId: UserId): F[Option[Settings]]
}
trait UserRepository[F[_]] {
def findUser(userId: UserId): F[Option[User]]
}
}
object impl {
import common.db.impl._
import db.api._
import cats.free.Free
import slick.dbio.DBIO
class FreeDBIOSettingsRepository extends SettingsRepository[FreeDBIO] {
override def findSettings(userId: UserId): FreeDBIO[Option[Settings]] =
Free.liftF(settings(userId))
}
// or
class DBIOSettingsRepository extends SettingsRepository[DBIO] {
override def findSettings(userId: UserId): DBIO[Option[Settings]] =
settings(userId)
}
class FreeDBIOUserRepository extends UserRepository[FreeDBIO] {
override def findUser(userId: UserId): FreeDBIO[Option[User]] =
Free.liftF(user(userId))
}
// or
class DBIOUserRepository extends UserRepository[DBIO] {
override def findUser(userId: UserId): DBIO[Option[User]] =
user(userId)
}
private def settings(userId: UserId): DBIO[Option[Settings]] =
DBIO.successful(Some(Settings(userId, Duration.ofDays(90))))
private def user(userId: UserId): DBIO[Option[User]] =
DBIO.successful(Some(User(userId, "bigbang", LocalDateTime.now() minusDays 100)))
}
}
object service {
object api {
trait SecurityService[F[_]] {
def notifyIfPasswordExpired(userId: UserId): F[Option[Boolean]]
}
}
object impl {
import db.api._
import service.api._
import cats.data.{EitherT, OptionT}
import cats.implicits._
class GenericSecurityService[F[_]: Monad, DbEffect[_]: Monad](settingsRepository: SettingsRepository[DbEffect],
userRepository: UserRepository[DbEffect],
fromDb: DbEffect ~> F)
extends SecurityService[F] {
override def notifyIfPasswordExpired(userId: UserId): F[Option[Boolean]] =
OptionT {
fromDb {
(settingsRepository.findSettings(userId) |@| userRepository.findUser(userId))
.map { (settingsOpt, userOpt) =>
(settingsOpt |@| userOpt).map { (settings, user) =>
user.isPasswordExpired(settings.passwordValidityPeriod)
}
}
}
}
.flatMap { expired =>
EitherT.cond[F](expired, sendNotification(userId), ().pure[F]).toOption.map(_ => expired)
}
.value
private def sendNotification(userId: UserId): F[Unit] = {
println(s"Sending expired password notification to $userId")
().pure[F]
}
}
}
}
object test {
import slick.dbio.{DBIO, FlatMapAction, SuccessAction}
import scala.concurrent.Future
object fakeDBIOToFuture extends (DBIO ~> Future) {
override def apply[A](fa: DBIO[A]): Future[A] = Future.successful(extract(fa))
private def extract[T](dbio: DBIO[T]): T = dbio match {
case SuccessAction(value) => value
case FlatMapAction(base, f, _) => extract(f(extract(base)))
case _ => throw new NotImplementedError
}
}
}
object wiring {
import common.db.impl._
import db.api._, db.impl._
import service.api._, service.impl._
import slick.dbio.DBIO
object dbiomonad {
import cats.implicits._
import com.rms.miu.slickcats.DBIOInstances._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
private val settingsRepository: SettingsRepository[DBIO] = new DBIOSettingsRepository
private val userRepository: UserRepository[DBIO] = new DBIOUserRepository
val securityService: SecurityService[Future] =
new GenericSecurityService[Future, DBIO](settingsRepository, userRepository, test.fakeDBIOToFuture)
}
object freemonad {
import cats.implicits._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
private val settingsRepository: SettingsRepository[FreeDBIO] = new FreeDBIOSettingsRepository
private val userRepository: UserRepository[FreeDBIO] = new FreeDBIOUserRepository
object freeDbioToFuture extends (FreeDBIO ~> Future) {
override def apply[A](fa: FreeDBIO[A]): Future[A] = fa.foldMap(test.fakeDBIOToFuture)
}
val securityService: SecurityService[Future] =
new GenericSecurityService[Future, FreeDBIO](settingsRepository, userRepository, freeDbioToFuture)
}
}
}
object Main extends App {
import scala.concurrent.Await
import scala.concurrent.duration.Duration
println {
"Monad[DBIO]: " + Await.result(simple.wiring.dbiomonad.securityService.notifyIfPasswordExpired("scooper"), Duration.Inf)
}
println {
"Free[DBIO, ?]: " + Await.result(simple.wiring.freemonad.securityService.notifyIfPasswordExpired("scooper"), Duration.Inf)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment