Service

Given:

Let:

Service

import java.sql.Connection
import java.sql.PreparedStatement
import java.sql.Statement
import scala.concurrent.Future
import scala.concurrent.{ ExecutionContext => EC }
import scala.util.Failure
import scala.util.Success
import scala.util.Try

object Service {

  def transact[A]( s: Service[A]
                 , c: Connection
                 )(implicit ec: EC): Future[Try[A]] =
    c synchronized {
      val ea =
        s.run(c) map {
          case s@Success(_) =>
            c.commit()
            s
          case f@Failure(_) =>
            c.rollback()
            f
        }
      ea
    }

  def apply[A](a: A)(implicit ec: EC): Service[A] =
    Service(_ => Future(Success(a)))

  def apply[A](k: Connection => A)(implicit ec: EC): Service[A] =
    Service(c => Future(Try(k(c))))

  def insert( q: String
            , k: PreparedStatement => Unit
            )(implicit ec: EC): Service[Option[Int]] =
    Service { c =>
      Future {
        Try {
          val s = c.prepareStatement(q, Statement.RETURN_GENERATED_KEYS)
          k(s)
          s.executeUpdate()
          var id: Option[Int] = None
          val r = s.getGeneratedKeys()
          if (r.next()) {
            id = Option(r.getInt(1))
          }
          r.close()
          s.close()
          id
        }
      }
    }

  def update(u: String)(implicit ec: EC): Service[Unit] =
    apply { c =>
      val s = c.createStatement()
      s.executeUpdate(u)
      s.close()
    }

  def update(q: String, k: PreparedStatement => Unit)(implicit ec: EC): Service[Unit] =
    apply { c =>
      val s = c.prepareStatement(q)
      k(s)
      s.executeUpdate()
      s.close()
    }

  def query[A](q: String, k: PreparedStatement => A)(implicit ec: EC): Service[A] =
    apply { c =>
      val s = c.prepareStatement(q)
      val a = k(s)
      s.close()
      a
    }
}

case class Service[A](run: Connection => Future[Try[A]]) {

  def map[B](f: A => B)(implicit ec: EC): Service[B] =
    Service { c => run(c) map { _ map f } }

  def mapFailure(f: Throwable => Throwable)(implicit ec: EC): Service[A] =
    Service { c =>
      run(c) map {
        case Failure(t) => Failure(f(t))
        case s@Success(_) => s
      }
    }

  def flatMap[B](f: A => Service[B])(implicit ec: EC): Service[B] =
    Service { c =>
      run(c) flatMap {
        case Failure(t) => Future(Failure(t))
        case Success(a) => f(a).run(c)
      }
    }

  def >>[B](x: Service[B])(implicit ec: EC): Service[B] =
    flatMap { _ => x }

}