SlideShare a Scribd company logo
1 of 83
Download to read offline
Purely Functional Play Framework
Application
2018/03/17 ScalaMatsuri 2018
Naoki Aoyama
whoami
Naoki Aoyama
Twitter: @AoiroAoino
GitHub: @aoiroaoino
Working at
Septeni Original,Inc.
膨大なデータ収集から社内ワークフロー効率化までを行い、
広告主様の広告効果を高める広告運用最適化ツール
様々なメディアに対する広告運用やレポート、
クリエイティブ最適化を支援
Scalaを採用し、ドメイン駆動設計(DDD)で開発
グループ会社のコミックスマート社が手掛ける
連載型新作マンガ配信サービス
アプリは900万 DL を超え、オリジナルマンガも100作以上配信。
2017年12月にはアプリ内にアニメ視聴機能を実装し、
オリジナルアニメの配信を開始。
Scala + DDD ほか、 Android 版も Scala で開発。※ iOS 版は Swift
セプテーニ・オリジナルでは
いつでも Scala をやってみたい方を募集しております!
Scala や DDD 未経験でも問題ありません!
入社の研修カリキュラムを通して
Scala と DDD のトレーニングを積むことが出来ます。
</head>
<body>
Let’s get started!
Agenda
Introduction
- Play Framework with Google Guice
- Purely functional separation of program & execution
What’s Tagless Final style?
- Overview
- It’s your neighbor
Tagless Final style practice in Play Framework
- Purely, so useful!
- But, No Silver Bullet
Conclusion
Agenda
Introduction
- Play Framework with Google Guice
- Purely functional separation of program & execution
What’s Tagless Final style?
- Overview
- It’s your neighbor
Tagless Final style practice in Play Framework
- Purely, so useful!
- But, No Silver Bullet
Conclusion
Play Framework with Google Guice
- Play v2.4.0 より Google Guice を用いた DI の仕組みが取り
入れられた
- 大規模なアプリケーションには DI は欠かせない
- 実際、慣れれば便利。※慣れれば
Google Guice is introduce to Play Framework from
v2.4.0. It’s useful once you get used.
Sample Application on Play Framework
- Play Framework v2.6.12
- Scala 2.12.4
Sample Application on Play Framework
package controllers
import javax.inject._
import play.api.mvc._
import models.Greeting
@Singleton
class HomeController @Inject()(cc: ControllerComponents,
@Named("en") greeting: Greeting)
extends AbstractController(cc) {
def index() = Action { implicit request: Request[AnyContent] =>
Ok(views.html.index())
}
def message(name: String) = Action { Ok(greeting.message(name)) }
}
Sample Application on Play Framework
package models
trait Greeting {
def message(name: String): String
}
class EnglishGreeting extends Greeting {
override def message(name: String) = s"Hello, $name"
}
class JapaneseGreeting extends Greeting {
override def message(name: String) = s"こんにちは、$name"
}
Sample Application on Play Framework
import com.google.inject.AbstractModule
import com.google.inject.name.Names
import models._
class Module extends AbstractModule {
override def configure(): Unit = {
bind(classOf[Greeting])
.annotatedWith(Names.named("en"))
.to(classOf[EnglishGreeting])
bind(classOf[Greeting])
.annotatedWith(Names.named("jp"))
.to(classOf[JapaneseGreeting])
}
}
Play Framework with Google Guice
Google Guice is a runtime DI library
- 実行時に依存性を解決する
- Play で主に使われるのは Constructor Injection
Constructor Injection is mostly used in Play
Framework.
全て Google Guice でやる必要はない
そもそもプログラムとインタープリタの分離がしたい。
インターフェースと実装を分けたい、後で入れ替えたい。
そして以下のような性質を持っていると嬉しい
- Compile Time DI
- Composability
- Testability
We want to separate program & execution. Compile
time DI, composability and testability are desired.
全て Google Guice でやる必要はない
純粋関数型プログラミング言語由来のツールを使っておけば全て
解決するんでしょ?
- Reader Monad
- Free Monad
- etc.
I know! Purely functional programming solves all the
things.
Agenda
Introduction
- Play Framework with Google Guice
- Purely functional separation of program & execution
What’s Tagless Final style?
- Overview
- It’s your neighbor
Tagless Final style practice in Play Framework
- Purely, so useful!
- But, No Silver Bullet
Conclusion
Separation of program & execution - Reader Monad
trait UserRepository {
def store(user: User)(implicit session: DBSession): Try[Unit]
def resolveByName(name: String)(implicit session: DBSession): Try[Option[User]]
def resolveAllByNames(name: String*)
(implicit session: DBSession): Try[List[User]]
}
class UserRepositoryOnJDBC extends UserRepository {
def store(user: User)(implicit dbSession: DBSession) = ???
def resolveByName(name: String)(implicit dbSession: DBSession) = ???
def resolveAllByNames(name: String*)(implicit dbSession: DBSession) = ???
}
Separation of program & execution - Reader Monad
trait UserRepositoryModule {
def userRepository: UserRepository
}
trait MessageRepositoryModule {
def messageRepository: MessageRepository
}
Separation of program & execution - Reader Monad
type Prog[A] = Reader[UserRepositoryModule with MessageRepositoryModule, A]
def getReplyTargetUsers(messageId: Long): Prog[Try[List[User]]] =
Reader { module =>
for {
messageOpt <- module.messageRepository.resolveBy(messageId)
names = messageOpt.fold(List.empty[String])(_.replyTargets)
users <- module.userRepository.resolveAllByNames(names: _*)
} yield users
}
Separation of program & execution - Reader Monad
val module = new UserRepositoryModule with MessageRepositoryModule {
override def userRepository = new UserRepositoryOnJDBC()
override def messageRepository = new MessageRepositoryOnJDBC()
}
MessageAPI.getReplyTargetUsers(1).run(module)
Separation of program & execution - Reader Monad
- Reader Monad では依存が複数になると扱いにくい。
型が冗長になる
- 引数に一つしか取れないので、Context 組み立てと分解の手
間が発生する
It’s hard to handle with reader monad when it has
multiple dependencies.
Separation of program & execution - Free Monad
type UserRepositoryFree[A] = Free[UserRepositoryOp, A]
sealed trait UserRepositoryOp[A]
object UserRepositoryOp {
case class Store(user: User) extends UserRepositoryOp[Unit]
case class ResolveByName(name: String) extends UserRepositoryOp[Option[User]]
def store(user: User): UserRepositoryFree[Unit] =
Free.liftF[UserRepositoryOp, Unit](Store(user))
def resolveByName(name: String): UserRepositoryFree[Option[User]] =
Free.liftF[UserRepositoryOp, Option[User]](ResolveByName(name))
}
Separation of program & execution - Free Monad
object UserRepository {
def store(user: User): UserRepositoryFree[Unit] =
UserRepositoryOp.store(user)
def resolveByName(name: String): UserRepositoryFree[Option[User]] =
UserRepositoryOp.resolveByName(name)
}
Separation of program & execution - Free Monad
object UserRepositoryOnJDBCInterp {
def futureInterp(session: DBSession)
(implicit ec: ExecutionContext): UserRepositoryOp ~> Future =
new (UserRepositoryOp ~> Future) {
def apply[A](op: UserRepositoryOp[A]): Future[A] =
op match {
case UserRepositoryOp.Store(user) =>
Future(...)
case UserRepositoryOp.ResolveByName(name) =>
Future(...)
}
}
}
Separation of program & execution - Free Monad
val dbSession = new DBSession()
UserRepository.resolveByName("John Doe")
.foldMap(futureInterp(dbSession))
Separation of program & execution - Free Monad
Free Monad は
- ボイラープレートが多い
でもコード自動生成に頼るのは微妙
- 実装時に何を処理の最小単位 (≒ 1つの ADT)とするか見極
めが難しい
- 異なる ADT の合成には一苦労
Free Monad requires too many boilerplates,
and composing multiple ADTs is difficult.
_人人人人人人人人人人_
> Tagless final style <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
Agenda
Introduction
- Play Framework with Google Guice
- Purely functional separation of program & execution
What’s Tagless Final style?
- Overview
- It’s your neighbor
Tagless Final style practice in Play Framework
- Purely, so useful!
- But, No Silver Bullet
Conclusion
What’s Tagless Final style
言語内 DSL (Embedded DSL) を作成する手法の一つ
Free Monad と比較して少ない記述量でプログラムと実装の分
離、DSL の合成が実現できる。
One method of creating EDSL. Tagless final has less
boilerplate.
Introduction to Tagless Final
trait UserRepository[F[_]] {
def store(user: User): F[Unit]
def resolveByName(name: String): F[Option[User]]
}
Introduction to Tagless Final
object UserRepository {
implicit val tryUserRepository: UserRepository[Try] =
new UserRepository[Try] {
def store(user: User): Try[Unit] = ???
def resolveByName(name: String): Try[Option[User]] = ???
}
}
Introduction to Tagless Final
def prog[F[_]](userName: String)
(implicit userRepo: UserRepository[F]): F[Option[User]] =
userRepo.resolveByName(userName)
prog[Try]("John")
// scala.util.Try[Option[User]] = Success(None)
また宇宙からやってきた難しい概念なんでしょう...?
Where are you from?
Isn’t it purely functional Alien?
Agenda
Introduction
- Play Framework with Google Guice
- Purely functional separation of program & execution
What’s Tagless Final style?
- Overview
- It’s your neighbor
Tagless Final style practice in Play Framework
- Purely, so useful!
- But, No Silver Bullet
Conclusion
よくある UserRepository インターフェースと実装例
trait UserRepository[Ctx] {
def store(user: User)(implicit ctx: Ctx): Future[Unit]
def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]]
}
// impl
class UserRepositoryOnJDBC()(implicit ec: ExecutionContext)
extends UserRepository[DBSession] {
def store(user: User)(implicit ctx: DBSession): Future[Unit] =
???
def resolveByName(name: String)(implicit ctx: DBSession): Future[Option[User]] =
???
}
Problem 1:
各メソッドが Context を暗黙の値として受け取る
- Context を一つだけとることを前提としている
- 暗黙的な値が複数必要だった場合にはそれらをまとめるコン
テナを用意する必要がある
- メソッド内では受け取った値から自分に必要なものを取り出す
作業が発生する
Each method takes a context as an implicit
parameter.
Problem 1:
各メソッドが Context を暗黙の値として受け取る
class UserRepositoryCtx(ec: ExecutionContext, dbSession: DBSession)
// impl on database
class UserRepositoryOnJDBC() extends UserRepository[UserRepositoryCtx] {
def store(user: User)(implicit ctx: UserRepositoryCtx): Future[Unit] = {
implicit val (ec, s) = (ctx.ec, ctx.dbSession)
??? // update
}
def resolveByName(name: String)
(implicit ctx: UserRepositoryCtx): Future[Option[User]] = {
implicit val (ec, s) = (ctx.ec, ctx.dbSession)
??? // select
}
}
Problem 1:
各メソッドが Context を暗黙の値として受け取る
case class DummyCtx()
// impl by memory
object UserRepositoryOnMemory extends UserRepository[DummyCtx]{
private val db = mutable.Map.empty[UserId, User]
def store(user: User)(implicit ctx: DummyCtx): Future[Unit] =
Future.successful(db.update(user.id, user))
def resolveByName(name: String)(implicit ctx: DummyCtx): Future[Option[User]] =
Future.successful(db.collectFirst {
case (_, u@User(_, n, _)) if n == name => u
})
}
Problem 2:
メソッドの実行結果をどう表現するか
いくつかのパターンが考えられる
- 呼び出し側に責任を押し付け失敗を全て例外で throw
- 例外を型で表現するために Try に包む
- 非同期処理もありうるから Future にしておく
- etc.
How to express the return value as a type.
Problem 2:
メソッドの実行結果をどう表現するか
// It’s simply! but throwable...
trait UserRepository[Ctx] {
def store(user: User)(implicit ctx: Ctx): Unit
def resolveByName(name: String)(implicit ctx: Ctx): Option[User]
}
// It’s not throwable! but blocking only...
trait UserRepository[Ctx] {
def store(user: User)(implicit ctx: Ctx): Try[Unit]
def resolveByName(name: String)(implicit ctx: Ctx): Try[Option[User]]
}
// It’s non blocking! but needs ExecutionContext in Ctx...
trait UserRepository[Ctx] {
def store(user: User)(implicit ctx: Ctx): Future[Unit]
def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]]
}
つまり、どうすればいいのか
UserRepository インターフェース内に実装側の都合を一切持ち
込まないというのが重要
- Context という概念を UserRepository 内に持ち込まない
- Context を暗黙の値にとるような前提の実装をやめる
- メソッドの実行方法や失敗の表現など、まるっと含めて返り値
の型を抽象的に表現する
Do not bring implementation related things to
UserRepository interface.
Remove implicit parameter
trait UserRepository[Ctx] {
def store(user: User)(implicit ctx: Ctx): Future[Unit]
def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]]
}
Remove implicit parameter
trait UserRepository[Ctx] {
def store(user: User)(implicit ctx: Ctx): Future[Unit]
def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]]
}
// 1. remove implicit
trait UserRepository[Ctx] {
def store(user: User)(ctx: Ctx): Future[Unit]
def resolveByName(name: String)(ctx: Ctx): Future[Option[User]]
}
Remove implicit parameter
trait UserRepository[Ctx] {
def store(user: User)(implicit ctx: Ctx): Future[Unit]
def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]]
}
// 1. remove implicit
trait UserRepository[Ctx] {
def store(user: User)(ctx: Ctx): Future[Unit]
def resolveByName(name: String)(ctx: Ctx): Future[Option[User]]
}
// 2. modify return function
trait UserRepository[Ctx] {
def store(user: User): Ctx => Future[Unit]
def resolveByName(name: String): Ctx => Future[Option[User]]
}
Remove implicit parameter
trait UserRepository[Ctx] {
def store(user: User): Ctx => Future[Unit]
def resolveByName(name: String): Ctx => Future[Option[User]]
}
// easy to use implicit parameter within impl.
class UserRepositoryOnJDBC(implicit ec: ExecutionContext)
extends UserRepository[DBSession] {
def store(user: User): DBSession => Future[Unit] = {
implicit dbSession =>
???
}
def resolveByName(name: String): DBSession => Future[Option[User]] = {
implicit dbSession =>
???
}
}
Really need Context?
trait UserRepository[Ctx] {
def store(user: User): Ctx => Future[Unit]
def resolveByName(name: String): Ctx => Future[Option[User]]
}
Really need Context?
trait UserRepository[Ctx] {
def store(user: User): Ctx => Future[Unit]
def resolveByName(name: String): Ctx => Future[Option[User]]
}
// 1. create type alias named `Exec` and replace return type
trait UserRepository[Ctx] {
type Exec[R] = Ctx => Future[R]
def store(user: User): Exec[Unit]
def resolveByName(name: String): Exec[Option[User]]
}
Really need Context?
trait UserRepository[Ctx] {
def store(user: User): Ctx => Future[Unit]
def resolveByName(name: String): Ctx => Future[Option[User]]
}
// 1. create type alias named `Exec` and replace return type
trait UserRepository[Ctx] {
type Exec[R] = Ctx => Future[R]
def store(user: User): Exec[Unit]
def resolveByName(name: String): Exec[Option[User]]
}
// 2. add `F[_]` type parameter and remove `Exec`
trait UserRepository[F[_]] {
def store(user: User): F[Unit]
def resolveByName(name: String): F[Option[User]]
}
It’s more abstract interface
trait UserRepository[F[_]] {
def store(user: User): F[Unit]
def resolveByName(name: String): F[Option[User]]
}
Agenda
Introduction
- Play Framework with Google Guice
- Purely functional separation of program & execution
What’s Tagless Final style?
- Overview
- It’s your neighbor
Tagless Final style practice in Play Framework
- Purely, so useful!
- But, No Silver Bullet
Conclusion
UserRepository implementation
trait UserRepository[F[_]] {
def store(user: User): F[Unit]
def resolveByName(name: String): F[Option[User]]
}
UserRepository implementation
trait UserRepository[F[_]] {
def store(user: User): F[Unit]
def resolveByName(name: String): F[Option[User]]
}
type Query[A] = Reader[DBSession, A]
object UserRepositoryOnJDBC {
implicit def queryUserRepository: UserRepository[Query] =
new UserRepository[Query] {
def store(user: User): Query[Unit] =
Reader { implicit dbSession => ??? }
def resolveByName(name: String): Query[Option[User]] =
Reader { implicit dbSession => ??? }
}
}
UserRepository implementation
trait UserRepository[F[_]] {
def store(user: User): F[Unit]
def resolveByName(name: String): F[Option[User]]
}
type AsyncQuery[A] = ReaderT[Task, DBSession, A]
object UserRepositoryOnJDBC {
implicit def asyncQueryUserRepository: UserRepository[AsyncQuery] =
new UserRepository[AsyncQuery] {
def store(user: User): AsyncQuery[Unit] =
ReaderT { implicit dbSession => Task(???) }
def resolveByName(name: String): AsyncQuery[Option[User]] =
ReaderT { implicit dbSession => Task(???) }
}
}
UserRepository implementation
trait UserRepository[F[_]] {
def store(user: User): F[Unit]
def resolveByName(name: String): F[Option[User]]
}
type UserRepositoryFree[A] = Free[UserRepositoryOp, A]
object UserRepositoryOnJDBC {
implicit def freeUserRepository: UserRepository[UserRepositoryFree] =
new UserRepository[UserRepositoryFree] {
def store(user: User): UserRepositoryFree[Unit] =
UserRepositoryOp.store(user)
def resolveByName(name: String): UserRepositoryFree[Option[User]] =
UserRepositoryOp.resolveByName(name)
}
}
UserRepository implementation
trait UserRepository[F[_]] {
def store(user: User): F[Unit]
def resolveByName(name: String): F[Option[User]]
}
type ConnectionIO[A] = Free[ConnectionOp, A]
object UserRepositoryOnJDBC {
implicit def doobieUserRepository: UserRepository[ConnectionIO] =
new UserRepository[ConnectionIO] {
def store(user: User): ConnectionIO[Unit] =
sql"...".update.run.map(_ => ())
def resolveByName(name: String): ConnectionIO[Option[User]] =
sql"...".query[User].option
}
}
UserRepository implementation
object UserRepository {
def apply[F[_]](implicit F: UserRepository[F]): UserRepository[F] = F
}
val dbSession = new DBSession()
type Query[A] = Reader[DBSession, A]
UserRepository[Query].resolveByName("John Doe")
.run(dbSession)
type AsyncQuery[A] = ReaderT[Task, DBSession, A]
UserRepository[AsyncQuery].resolveByName("John Doe")
.run(dbSession).runAsync
type UserRepositoryFree[A] = Free[UserRepositoryOp, A]
UserRepository[UserRepositoryFree].resolveByName("John Doe")
.foldMap(futureInterp(dbSession))
UserRepository implementation
object UserRepository {
def apply[F[_]](implicit F: UserRepository[F]): UserRepository[F] = F
}
val xa = Transactor.fromDriverManager[Task](...) // for doobie
type ConnectionIO[A] = Free[ConnectionOp, A]
UserRepository[ConnectionIO].resolveByName("John Doe")
.transact(xa).runAsync
UserController implementation
object UserController {
def getUser[F[_]: Monad: UserRepository](id: Long): F[Result] =
UserRepository[F].resolveBy(id).map {
case Some(user) => Results.Ok(GetUserResponse.from(user).toJson)
case None => Results.NotFound
}
}
UserController implementation
class UserController @Inject()(cc: ControllerComponents, ec: ExecutionContext)
extends AbstractController(cc) {
// ...
import UserRepositoryOnJDBC._ // impls
def getUser(id: Long) = Action {
UserController.getUser[Query](id).run(dbSession)
}
def getUserAsync(id: Long) = Action.async {
UserController.getUser[AsyncQuery](id).run(dbSession).runAsync
}
}
UserController testing
class UserRepositoryMock extends UserRepository[Id] {
def store(user: User): Id[Unit] = ???
def resolveBy(id: Long): Id[Option[User]] = ???
def resolveByName(name: String): Id[Option[User]] = ???
}
UserController testing
class UserControllerSpec extends PlaySpec {
"UserController GET" should {
"get user from static controller" in {
implicit val mock = new UserRepositoryMock {
override def resolveBy(id: Long): Option[User] =
if (id == 100)
Some(User(100, "John"))
else
None
}
UserController.getUser[Id](100).header.status mustBe OK
UserController.getUser[Id](999).header.status mustBe NOT_FOUND
}
}
}
UserController testing
object UserController {
// ...
def storeAndGet[F[_]: Monad: UserRepository](name: String): F[Result] =
for {
_ <- UserRepository[F].store(User(Random.nextLong(), name))
user <- UserRepository[F].resolveByName(name)
} yield {
user match {
case Some(u) => Results.Ok(GetUserResponse.from(u).toJson)
case None => Results.InternalServerError
}
}
}
UserController testing
type DB = List[User]
class UserRepositoryStateMock extends UserRepository[State[DB, ?]] {
def store(user: User): State[DB, Unit] = ???
def resolveBy(id: Long): State[DB, Option[User]] = ???
def resolveByName(name: String): State[DB, Option[User]] = ???
}
UserController testing
class UserControllerSpec extends PlaySpec {
"UserController GET" should {
"store and get user from static controller" in {
implicit val mock = new UserRepositoryStateMock {
override def store(user: User): State[DB, Unit] =
State(db => (user :: db, ()))
override def resolveByName(name: String): State[DB, Option[User]] =
State.inspect(_.find(_.name == name))
}
UserController.storeAndGet[State[DB, ?]]("John")
.runEmptyA.value.header.status mustBe OK
}
}
Agenda
Introduction
- Play Framework with Google Guice
- Purely functional separation of program & execution
What’s Tagless Final style?
- Overview
- It’s your neighbor
Tagless Final style practice in Play Framework
- Purely, so useful!
- But, No Silver Bullet
Conclusion
F[_] が異なる場合は for 式で合成できない
object MessageController {
def getUserMessages[F[_]: Monad: MessageRepository: UserRepository](
userId: Long
): F[Result] =
for {
user <- UserRepository[F].resolveBy(userId)
messages <- MessageRepository[F].resolveAllByUserId(userId)
} yield {
user match {
case Some(u) => Results.Ok(GetMessagesResponse.from(u, messages).toJson)
case None => Results.NotFound
}
}
}
F[_] が異なる場合は for 式で合成できない
type AsyncQuery[A] = ReaderT[Task, DBSession, A]
type AsyncCommand[A] = ReaderT[Task, RedisConnection, A]
object MessageRepositoryOnJDBC {
// MessageRepository[AsyncQuery] is not implemented
implicit def redisConnectionMessageRepository: MessageRepository[AsyncCommand] =
new MessageRepository[AsyncCommand] {
def resolveBy(id: Long): AsyncCommand[Option[Message]] = ???
def store(message: Message): AsyncCommand[Unit] = ???
def resolveAllByUserId(userId: Long): AsyncCommand[List[Message]] = ???
}
}
F[_] が異なる場合は for 式で合成できない
[pfpfap] $ compile
[info] Compiling 1 Scala source to /Users/aoiroaoino/git/pfpf/bbs/target/scala-2.12/classes …
...
[error] /Users/aoiroaoino/git/pfpf/bbs/app/controllers/MessageController.scala:26:50:
could not find implicit value for evidence parameter of type
models.repository.MessageRepository[models.AsyncQuery]
[error] MessageController.getUserMessages[AsyncQuery](userId).run(dbSession).runAsync
[error] ^
F[_] が異なる場合は for 式で合成できない
いくつかの解決策がある
- Monad を積み上げる
- F[_] を具体的な型にしてから controller 合成する
- 自然変換を使って型を揃える
There are some solutions. Stacking Monads,
composing concrete types, Natural Transformation.
Stacking Monads
class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext)
extends AbstractController(cc) {
// ...
type SpecialAction[A] = ReaderT[ReaderT[Task, DBSession, ?], RedisConnection, A]
def getUserMessagesSM(userId: Long) = Action.async {
MessageController.getUserMessages[SpecialAction](userId)
.run(redisConnection) // RedisConnection => ReaderT[Task, DBSession, A]
.run(dbSession) // DBSession => Task[A]
.runAsync // CancelableFuture[A]
}
}
Composing concrete types
class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext)
extends AbstractController(cc) {
// ...
def getUserMessages(userId: Long) = Action.async {
(for {
user <- UserRepository[AsyncQuery].resolveBy(userId).run(dbSession)
messages <- MessageRepository[AsyncCommand].resolveAllByUserId(userId)
.run(redisConnection)
} yield {
user match {
case Some(u) => Results.Ok(GetMessagesResponse.from(u, messages).toJson)
case None => Results.NotFound
}
}).runAsync
}
}
Use natural transformation and mapK()
class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext)
extends AbstractController(cc) {
// ...
type AsyncQuery[A] = ReaderT[Task, DBSession, A]
type AsyncCommand[A] = ReaderT[Task, RedisConnection, A]
// common type of AsyncQuery and AsyncCommand
type Exec[A] = ReaderT[Task, (DBSession, RedisConnection), A]
def getUserMessages(userId: Long) = Action.async {
MessageController.getUserMessages[Exec](userId)
.run((dbSession, redisConnection)).runAsync
}
}
Use natural transformation and mapK()
trait MessageRepository[F[_]] {
def store(message: Message): F[Unit]
def resolveBy(id: Long): F[Option[Message]]
def resolveAllByUserId(userId: Long): F[List[Message]]
def mapK[G[_]](fk: F ~> G): MessageRepository[G] =
new MessageRepository[G] {
def store(message: Message): G[Unit] =
fk(self.store(message))
def resolveBy(id: Long): G[Option[Message]] =
fk(self.resolveBy(id))
def resolveAllByUserId(userId: Long): G[List[Message]] =
fk(self.resolveAllByUserId(userId))
}
}
Use natural transformation and mapK()
class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext)
extends AbstractController(cc) {
// ...
type Exec[A] = ReaderT[Task, (DBSession, RedisConnection), A]
val asyncQueryToExec: AsyncQuery ~> Exec =
new (AsyncQuery ~> Exec) {
def apply[A](aq: AsyncQuery[A]): Exec[A] =
ReaderT { case (dbSession, _) => aq.run(dbSessioin) }
}
val asyncCommandToExec: AsyncCommand ~> Exec =
new (AsyncCommand ~> Exec) {
def apply[A](ac: AsyncCommand[A]): Exec[A] =
ReaderT { case (_, redisConn) => ac.run(redisConn) }
}
}
Use natural transformation and mapK()
class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext)
extends AbstractController(cc) {
// ...
type Exec[A] = ReaderT[Task, (DBSession, RedisConnection), A]
val asyncQueryToExec: AsyncQuery ~> Exec = ...
val asyncCommandToExec: AsyncCommand ~> Exec = ...
implicit val execUserRepository: UserRepository[Exec] =
UserRepository[AsyncQuery].mapK(asyncQueryToExec)
implicit val execMessageRepository: MessageRepository[Exec] =
MessageRepository[AsyncCommand].mapK(asyncCommandToExec)
}
Use natural transformation and mapK()
class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext)
extends AbstractController(cc) {
// ...
type Exec[A] = ReaderT[Task, (DBSession, RedisConnection), A]
val asyncQueryToExec: AsyncQuery ~> Exec = ...
val asyncCommandToExec: AsyncCommand ~> Exec = ...
implicit val execUserRepository: UserRepository[Exec] = ...
implicit val execMessageRepository: MessageRepository[Exec] = ...
def getUserMessages(userId: Long) = Action.async {
MessageController.getUserMessages[Exec](userId)
.run((dbSession, redisConnection)).runAsync
}
}
Agenda
Introduction
- Play Framework with Google Guice
- Purely functional separation of program & execution
What’s Tagless Final style?
- Overview
- It’s your neighbor
Tagless Final style practice in Play Framework
- Purely, so useful!
- But, No Silver Bullet
Conclusion
Conclusion
Google Guice でアプリを全て組み立てる必要はない
- プログラムとインタープリタの分離をする方法はいくつもあ
る。
- Reader や Free もいいけど直接使うと不便さもある。
There are many ways to separate program &
interpreter.
Conclusion
Tagless Final 便利
- 手で書いても苦にならない程度のコード量
- 複数の DSL を比較的簡単に合成できる
- F[_] が異なる場合に一工夫必要。銀の弾丸ではない。
- Play Framework に限らずいろんな場面で使える!
Tagless final is useful because it has less boilerplate.

More Related Content

What's hot

Introduction to Objective - C
Introduction to Objective - CIntroduction to Objective - C
Introduction to Objective - CJussi Pohjolainen
 
Uncommon Design Patterns
Uncommon Design PatternsUncommon Design Patterns
Uncommon Design PatternsStefano Fago
 
FP 201 - Unit4 Part 2
FP 201 - Unit4 Part 2FP 201 - Unit4 Part 2
FP 201 - Unit4 Part 2rohassanie
 
C++11 smart pointer
C++11 smart pointerC++11 smart pointer
C++11 smart pointerLei Yu
 
Lecture 3, c++(complete reference,herbet sheidt)chapter-13
Lecture 3, c++(complete reference,herbet sheidt)chapter-13Lecture 3, c++(complete reference,herbet sheidt)chapter-13
Lecture 3, c++(complete reference,herbet sheidt)chapter-13Abu Saleh
 
STL ALGORITHMS
STL ALGORITHMSSTL ALGORITHMS
STL ALGORITHMSfawzmasood
 
Let's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java APILet's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java APIMario Fusco
 
C++11 Idioms @ Silicon Valley Code Camp 2012
C++11 Idioms @ Silicon Valley Code Camp 2012 C++11 Idioms @ Silicon Valley Code Camp 2012
C++11 Idioms @ Silicon Valley Code Camp 2012 Sumant Tambe
 
Objective-C Crash Course for Web Developers
Objective-C Crash Course for Web DevelopersObjective-C Crash Course for Web Developers
Objective-C Crash Course for Web DevelopersJoris Verbogt
 
Testing for share
Testing for share Testing for share
Testing for share Rajeev Mehta
 
JavaScript Growing Up
JavaScript Growing UpJavaScript Growing Up
JavaScript Growing UpDavid Padbury
 
[C++] The Curiously Recurring Template Pattern: Static Polymorphsim and Expre...
[C++] The Curiously Recurring Template Pattern: Static Polymorphsim and Expre...[C++] The Curiously Recurring Template Pattern: Static Polymorphsim and Expre...
[C++] The Curiously Recurring Template Pattern: Static Polymorphsim and Expre...Francesco Casalegno
 
Advanced Python, Part 2
Advanced Python, Part 2Advanced Python, Part 2
Advanced Python, Part 2Zaar Hai
 
Introduction to ad-3.4, an automatic differentiation library in Haskell
Introduction to ad-3.4, an automatic differentiation library in HaskellIntroduction to ad-3.4, an automatic differentiation library in Haskell
Introduction to ad-3.4, an automatic differentiation library in Haskellnebuta
 

What's hot (20)

Introduction to Objective - C
Introduction to Objective - CIntroduction to Objective - C
Introduction to Objective - C
 
Core concepts-javascript
Core concepts-javascriptCore concepts-javascript
Core concepts-javascript
 
The Future of C++
The Future of C++The Future of C++
The Future of C++
 
Uncommon Design Patterns
Uncommon Design PatternsUncommon Design Patterns
Uncommon Design Patterns
 
FP 201 - Unit4 Part 2
FP 201 - Unit4 Part 2FP 201 - Unit4 Part 2
FP 201 - Unit4 Part 2
 
Smart pointers
Smart pointersSmart pointers
Smart pointers
 
C++11 smart pointer
C++11 smart pointerC++11 smart pointer
C++11 smart pointer
 
Lecture 3, c++(complete reference,herbet sheidt)chapter-13
Lecture 3, c++(complete reference,herbet sheidt)chapter-13Lecture 3, c++(complete reference,herbet sheidt)chapter-13
Lecture 3, c++(complete reference,herbet sheidt)chapter-13
 
STL ALGORITHMS
STL ALGORITHMSSTL ALGORITHMS
STL ALGORITHMS
 
Fancy talk
Fancy talkFancy talk
Fancy talk
 
Let's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java APILet's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java API
 
C++11 Idioms @ Silicon Valley Code Camp 2012
C++11 Idioms @ Silicon Valley Code Camp 2012 C++11 Idioms @ Silicon Valley Code Camp 2012
C++11 Idioms @ Silicon Valley Code Camp 2012
 
Objective-C Crash Course for Web Developers
Objective-C Crash Course for Web DevelopersObjective-C Crash Course for Web Developers
Objective-C Crash Course for Web Developers
 
Testing for share
Testing for share Testing for share
Testing for share
 
JavaScript Growing Up
JavaScript Growing UpJavaScript Growing Up
JavaScript Growing Up
 
What's New in C++ 11?
What's New in C++ 11?What's New in C++ 11?
What's New in C++ 11?
 
[C++] The Curiously Recurring Template Pattern: Static Polymorphsim and Expre...
[C++] The Curiously Recurring Template Pattern: Static Polymorphsim and Expre...[C++] The Curiously Recurring Template Pattern: Static Polymorphsim and Expre...
[C++] The Curiously Recurring Template Pattern: Static Polymorphsim and Expre...
 
Advanced Python, Part 2
Advanced Python, Part 2Advanced Python, Part 2
Advanced Python, Part 2
 
C++11
C++11C++11
C++11
 
Introduction to ad-3.4, an automatic differentiation library in Haskell
Introduction to ad-3.4, an automatic differentiation library in HaskellIntroduction to ad-3.4, an automatic differentiation library in Haskell
Introduction to ad-3.4, an automatic differentiation library in Haskell
 

Similar to purely_functional_play_framework_application

ClojureScript - Making Front-End development Fun again - John Stevenson - Cod...
ClojureScript - Making Front-End development Fun again - John Stevenson - Cod...ClojureScript - Making Front-End development Fun again - John Stevenson - Cod...
ClojureScript - Making Front-End development Fun again - John Stevenson - Cod...Codemotion
 
PhpStorm: Symfony2 Plugin
PhpStorm: Symfony2 PluginPhpStorm: Symfony2 Plugin
PhpStorm: Symfony2 PluginHaehnchen
 
Introduction to Software Development
Introduction to Software DevelopmentIntroduction to Software Development
Introduction to Software DevelopmentZeeshan MIrza
 
Reactive datastore demo (2020 03-21)
Reactive datastore demo (2020 03-21)Reactive datastore demo (2020 03-21)
Reactive datastore demo (2020 03-21)YangJerng Hwa
 
Django Framework Overview forNon-Python Developers
Django Framework Overview forNon-Python DevelopersDjango Framework Overview forNon-Python Developers
Django Framework Overview forNon-Python DevelopersRosario Renga
 
Griffon: Re-imaging Desktop Java Technology
Griffon: Re-imaging Desktop Java TechnologyGriffon: Re-imaging Desktop Java Technology
Griffon: Re-imaging Desktop Java TechnologyJames Williams
 
OSGi DevCon 2009 Review
OSGi DevCon 2009 ReviewOSGi DevCon 2009 Review
OSGi DevCon 2009 Reviewnjbartlett
 
MobileConf 2021 Slides: Let's build macOS CLI Utilities using Swift
MobileConf 2021 Slides:  Let's build macOS CLI Utilities using SwiftMobileConf 2021 Slides:  Let's build macOS CLI Utilities using Swift
MobileConf 2021 Slides: Let's build macOS CLI Utilities using SwiftDiego Freniche Brito
 
Flutter vs Java Graphical User Interface Frameworks - text
Flutter vs Java Graphical User Interface Frameworks - textFlutter vs Java Graphical User Interface Frameworks - text
Flutter vs Java Graphical User Interface Frameworks - textToma Velev
 
Drupal 8 preview_slideshow
Drupal 8 preview_slideshowDrupal 8 preview_slideshow
Drupal 8 preview_slideshowTee Malapela
 
Going open source with small teams
Going open source with small teamsGoing open source with small teams
Going open source with small teamsJamie Thomas
 
Advantages of golang development services &amp; 10 most used go frameworks
Advantages of golang development services &amp; 10 most used go frameworksAdvantages of golang development services &amp; 10 most used go frameworks
Advantages of golang development services &amp; 10 most used go frameworksKaty Slemon
 
Exploring Google (Cloud) APIs with Python & JavaScript
Exploring Google (Cloud) APIs with Python & JavaScriptExploring Google (Cloud) APIs with Python & JavaScript
Exploring Google (Cloud) APIs with Python & JavaScriptwesley chun
 
WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...Fabio Franzini
 
Introduction to Gradio library in python.pptx
Introduction to Gradio library in python.pptxIntroduction to Gradio library in python.pptx
Introduction to Gradio library in python.pptxvahid67ebrahimian
 
Designing flexible apps deployable to App Engine, Cloud Functions, or Cloud Run
Designing flexible apps deployable to App Engine, Cloud Functions, or Cloud RunDesigning flexible apps deployable to App Engine, Cloud Functions, or Cloud Run
Designing flexible apps deployable to App Engine, Cloud Functions, or Cloud Runwesley chun
 
UX Prototyping: New Principles and Tools (HTP Grodno, 20.03.2018)
UX Prototyping: New Principles and Tools (HTP Grodno, 20.03.2018)UX Prototyping: New Principles and Tools (HTP Grodno, 20.03.2018)
UX Prototyping: New Principles and Tools (HTP Grodno, 20.03.2018)Ilya Zakharau
 

Similar to purely_functional_play_framework_application (20)

ClojureScript - Making Front-End development Fun again - John Stevenson - Cod...
ClojureScript - Making Front-End development Fun again - John Stevenson - Cod...ClojureScript - Making Front-End development Fun again - John Stevenson - Cod...
ClojureScript - Making Front-End development Fun again - John Stevenson - Cod...
 
PhpStorm: Symfony2 Plugin
PhpStorm: Symfony2 PluginPhpStorm: Symfony2 Plugin
PhpStorm: Symfony2 Plugin
 
Introduction to Software Development
Introduction to Software DevelopmentIntroduction to Software Development
Introduction to Software Development
 
Reactive datastore demo (2020 03-21)
Reactive datastore demo (2020 03-21)Reactive datastore demo (2020 03-21)
Reactive datastore demo (2020 03-21)
 
Django Framework Overview forNon-Python Developers
Django Framework Overview forNon-Python DevelopersDjango Framework Overview forNon-Python Developers
Django Framework Overview forNon-Python Developers
 
Griffon: Re-imaging Desktop Java Technology
Griffon: Re-imaging Desktop Java TechnologyGriffon: Re-imaging Desktop Java Technology
Griffon: Re-imaging Desktop Java Technology
 
OSGi DevCon 2009 Review
OSGi DevCon 2009 ReviewOSGi DevCon 2009 Review
OSGi DevCon 2009 Review
 
MobileConf 2021 Slides: Let's build macOS CLI Utilities using Swift
MobileConf 2021 Slides:  Let's build macOS CLI Utilities using SwiftMobileConf 2021 Slides:  Let's build macOS CLI Utilities using Swift
MobileConf 2021 Slides: Let's build macOS CLI Utilities using Swift
 
Flutter vs Java Graphical User Interface Frameworks - text
Flutter vs Java Graphical User Interface Frameworks - textFlutter vs Java Graphical User Interface Frameworks - text
Flutter vs Java Graphical User Interface Frameworks - text
 
Drupal 8 preview_slideshow
Drupal 8 preview_slideshowDrupal 8 preview_slideshow
Drupal 8 preview_slideshow
 
Going open source with small teams
Going open source with small teamsGoing open source with small teams
Going open source with small teams
 
Griffon Presentation
Griffon PresentationGriffon Presentation
Griffon Presentation
 
ANDROID FDP PPT
ANDROID FDP PPTANDROID FDP PPT
ANDROID FDP PPT
 
Advantages of golang development services &amp; 10 most used go frameworks
Advantages of golang development services &amp; 10 most used go frameworksAdvantages of golang development services &amp; 10 most used go frameworks
Advantages of golang development services &amp; 10 most used go frameworks
 
Exploring Google (Cloud) APIs with Python & JavaScript
Exploring Google (Cloud) APIs with Python & JavaScriptExploring Google (Cloud) APIs with Python & JavaScript
Exploring Google (Cloud) APIs with Python & JavaScript
 
WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...
 
Introduction to Gradio library in python.pptx
Introduction to Gradio library in python.pptxIntroduction to Gradio library in python.pptx
Introduction to Gradio library in python.pptx
 
Shuzworld Analysis
Shuzworld AnalysisShuzworld Analysis
Shuzworld Analysis
 
Designing flexible apps deployable to App Engine, Cloud Functions, or Cloud Run
Designing flexible apps deployable to App Engine, Cloud Functions, or Cloud RunDesigning flexible apps deployable to App Engine, Cloud Functions, or Cloud Run
Designing flexible apps deployable to App Engine, Cloud Functions, or Cloud Run
 
UX Prototyping: New Principles and Tools (HTP Grodno, 20.03.2018)
UX Prototyping: New Principles and Tools (HTP Grodno, 20.03.2018)UX Prototyping: New Principles and Tools (HTP Grodno, 20.03.2018)
UX Prototyping: New Principles and Tools (HTP Grodno, 20.03.2018)
 

Recently uploaded

UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxUI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxAndreas Kunz
 
Ahmed Motair CV April 2024 (Senior SW Developer)
Ahmed Motair CV April 2024 (Senior SW Developer)Ahmed Motair CV April 2024 (Senior SW Developer)
Ahmed Motair CV April 2024 (Senior SW Developer)Ahmed Mater
 
VK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web DevelopmentVK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web Developmentvyaparkranti
 
Implementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureImplementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureDinusha Kumarasiri
 
英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作qr0udbr0
 
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Natan Silnitsky
 
Cyber security and its impact on E commerce
Cyber security and its impact on E commerceCyber security and its impact on E commerce
Cyber security and its impact on E commercemanigoyal112
 
Salesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZSalesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZABSYZ Inc
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Velvetech LLC
 
Powering Real-Time Decisions with Continuous Data Streams
Powering Real-Time Decisions with Continuous Data StreamsPowering Real-Time Decisions with Continuous Data Streams
Powering Real-Time Decisions with Continuous Data StreamsSafe Software
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtimeandrehoraa
 
Sending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdfSending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdf31events.com
 
How to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationHow to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationBradBedford3
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...OnePlan Solutions
 
Introduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdfIntroduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdfFerryKemperman
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesŁukasz Chruściel
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...OnePlan Solutions
 
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfGOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfAlina Yurenko
 
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Cizo Technology Services
 
Xen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfXen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfStefano Stabellini
 

Recently uploaded (20)

UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptxUI5ers live - Custom Controls wrapping 3rd-party libs.pptx
UI5ers live - Custom Controls wrapping 3rd-party libs.pptx
 
Ahmed Motair CV April 2024 (Senior SW Developer)
Ahmed Motair CV April 2024 (Senior SW Developer)Ahmed Motair CV April 2024 (Senior SW Developer)
Ahmed Motair CV April 2024 (Senior SW Developer)
 
VK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web DevelopmentVK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web Development
 
Implementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureImplementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with Azure
 
英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作
 
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
 
Cyber security and its impact on E commerce
Cyber security and its impact on E commerceCyber security and its impact on E commerce
Cyber security and its impact on E commerce
 
Salesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZSalesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZ
 
Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...Software Project Health Check: Best Practices and Techniques for Your Product...
Software Project Health Check: Best Practices and Techniques for Your Product...
 
Powering Real-Time Decisions with Continuous Data Streams
Powering Real-Time Decisions with Continuous Data StreamsPowering Real-Time Decisions with Continuous Data Streams
Powering Real-Time Decisions with Continuous Data Streams
 
SpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at RuntimeSpotFlow: Tracking Method Calls and States at Runtime
SpotFlow: Tracking Method Calls and States at Runtime
 
Sending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdfSending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdf
 
How to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationHow to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion Application
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
 
Introduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdfIntroduction Computer Science - Software Design.pdf
Introduction Computer Science - Software Design.pdf
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New Features
 
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
Tech Tuesday - Mastering Time Management Unlock the Power of OnePlan's Timesh...
 
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfGOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
 
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
Global Identity Enrolment and Verification Pro Solution - Cizo Technology Ser...
 
Xen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdfXen Safety Embedded OSS Summit April 2024 v4.pdf
Xen Safety Embedded OSS Summit April 2024 v4.pdf
 

purely_functional_play_framework_application

  • 1. Purely Functional Play Framework Application 2018/03/17 ScalaMatsuri 2018 Naoki Aoyama
  • 2. whoami Naoki Aoyama Twitter: @AoiroAoino GitHub: @aoiroaoino Working at Septeni Original,Inc.
  • 3.
  • 6. セプテーニ・オリジナルでは いつでも Scala をやってみたい方を募集しております! Scala や DDD 未経験でも問題ありません! 入社の研修カリキュラムを通して Scala と DDD のトレーニングを積むことが出来ます。
  • 8. Agenda Introduction - Play Framework with Google Guice - Purely functional separation of program & execution What’s Tagless Final style? - Overview - It’s your neighbor Tagless Final style practice in Play Framework - Purely, so useful! - But, No Silver Bullet Conclusion
  • 9. Agenda Introduction - Play Framework with Google Guice - Purely functional separation of program & execution What’s Tagless Final style? - Overview - It’s your neighbor Tagless Final style practice in Play Framework - Purely, so useful! - But, No Silver Bullet Conclusion
  • 10. Play Framework with Google Guice - Play v2.4.0 より Google Guice を用いた DI の仕組みが取り 入れられた - 大規模なアプリケーションには DI は欠かせない - 実際、慣れれば便利。※慣れれば Google Guice is introduce to Play Framework from v2.4.0. It’s useful once you get used.
  • 11. Sample Application on Play Framework - Play Framework v2.6.12 - Scala 2.12.4
  • 12. Sample Application on Play Framework package controllers import javax.inject._ import play.api.mvc._ import models.Greeting @Singleton class HomeController @Inject()(cc: ControllerComponents, @Named("en") greeting: Greeting) extends AbstractController(cc) { def index() = Action { implicit request: Request[AnyContent] => Ok(views.html.index()) } def message(name: String) = Action { Ok(greeting.message(name)) } }
  • 13. Sample Application on Play Framework package models trait Greeting { def message(name: String): String } class EnglishGreeting extends Greeting { override def message(name: String) = s"Hello, $name" } class JapaneseGreeting extends Greeting { override def message(name: String) = s"こんにちは、$name" }
  • 14. Sample Application on Play Framework import com.google.inject.AbstractModule import com.google.inject.name.Names import models._ class Module extends AbstractModule { override def configure(): Unit = { bind(classOf[Greeting]) .annotatedWith(Names.named("en")) .to(classOf[EnglishGreeting]) bind(classOf[Greeting]) .annotatedWith(Names.named("jp")) .to(classOf[JapaneseGreeting]) } }
  • 15.
  • 16. Play Framework with Google Guice Google Guice is a runtime DI library - 実行時に依存性を解決する - Play で主に使われるのは Constructor Injection Constructor Injection is mostly used in Play Framework.
  • 17.
  • 18. 全て Google Guice でやる必要はない そもそもプログラムとインタープリタの分離がしたい。 インターフェースと実装を分けたい、後で入れ替えたい。 そして以下のような性質を持っていると嬉しい - Compile Time DI - Composability - Testability We want to separate program & execution. Compile time DI, composability and testability are desired.
  • 19. 全て Google Guice でやる必要はない 純粋関数型プログラミング言語由来のツールを使っておけば全て 解決するんでしょ? - Reader Monad - Free Monad - etc. I know! Purely functional programming solves all the things.
  • 20. Agenda Introduction - Play Framework with Google Guice - Purely functional separation of program & execution What’s Tagless Final style? - Overview - It’s your neighbor Tagless Final style practice in Play Framework - Purely, so useful! - But, No Silver Bullet Conclusion
  • 21. Separation of program & execution - Reader Monad trait UserRepository { def store(user: User)(implicit session: DBSession): Try[Unit] def resolveByName(name: String)(implicit session: DBSession): Try[Option[User]] def resolveAllByNames(name: String*) (implicit session: DBSession): Try[List[User]] } class UserRepositoryOnJDBC extends UserRepository { def store(user: User)(implicit dbSession: DBSession) = ??? def resolveByName(name: String)(implicit dbSession: DBSession) = ??? def resolveAllByNames(name: String*)(implicit dbSession: DBSession) = ??? }
  • 22. Separation of program & execution - Reader Monad trait UserRepositoryModule { def userRepository: UserRepository } trait MessageRepositoryModule { def messageRepository: MessageRepository }
  • 23. Separation of program & execution - Reader Monad type Prog[A] = Reader[UserRepositoryModule with MessageRepositoryModule, A] def getReplyTargetUsers(messageId: Long): Prog[Try[List[User]]] = Reader { module => for { messageOpt <- module.messageRepository.resolveBy(messageId) names = messageOpt.fold(List.empty[String])(_.replyTargets) users <- module.userRepository.resolveAllByNames(names: _*) } yield users }
  • 24. Separation of program & execution - Reader Monad val module = new UserRepositoryModule with MessageRepositoryModule { override def userRepository = new UserRepositoryOnJDBC() override def messageRepository = new MessageRepositoryOnJDBC() } MessageAPI.getReplyTargetUsers(1).run(module)
  • 25. Separation of program & execution - Reader Monad - Reader Monad では依存が複数になると扱いにくい。 型が冗長になる - 引数に一つしか取れないので、Context 組み立てと分解の手 間が発生する It’s hard to handle with reader monad when it has multiple dependencies.
  • 26. Separation of program & execution - Free Monad type UserRepositoryFree[A] = Free[UserRepositoryOp, A] sealed trait UserRepositoryOp[A] object UserRepositoryOp { case class Store(user: User) extends UserRepositoryOp[Unit] case class ResolveByName(name: String) extends UserRepositoryOp[Option[User]] def store(user: User): UserRepositoryFree[Unit] = Free.liftF[UserRepositoryOp, Unit](Store(user)) def resolveByName(name: String): UserRepositoryFree[Option[User]] = Free.liftF[UserRepositoryOp, Option[User]](ResolveByName(name)) }
  • 27. Separation of program & execution - Free Monad object UserRepository { def store(user: User): UserRepositoryFree[Unit] = UserRepositoryOp.store(user) def resolveByName(name: String): UserRepositoryFree[Option[User]] = UserRepositoryOp.resolveByName(name) }
  • 28. Separation of program & execution - Free Monad object UserRepositoryOnJDBCInterp { def futureInterp(session: DBSession) (implicit ec: ExecutionContext): UserRepositoryOp ~> Future = new (UserRepositoryOp ~> Future) { def apply[A](op: UserRepositoryOp[A]): Future[A] = op match { case UserRepositoryOp.Store(user) => Future(...) case UserRepositoryOp.ResolveByName(name) => Future(...) } } }
  • 29. Separation of program & execution - Free Monad val dbSession = new DBSession() UserRepository.resolveByName("John Doe") .foldMap(futureInterp(dbSession))
  • 30. Separation of program & execution - Free Monad Free Monad は - ボイラープレートが多い でもコード自動生成に頼るのは微妙 - 実装時に何を処理の最小単位 (≒ 1つの ADT)とするか見極 めが難しい - 異なる ADT の合成には一苦労 Free Monad requires too many boilerplates, and composing multiple ADTs is difficult.
  • 32. Agenda Introduction - Play Framework with Google Guice - Purely functional separation of program & execution What’s Tagless Final style? - Overview - It’s your neighbor Tagless Final style practice in Play Framework - Purely, so useful! - But, No Silver Bullet Conclusion
  • 33. What’s Tagless Final style 言語内 DSL (Embedded DSL) を作成する手法の一つ Free Monad と比較して少ない記述量でプログラムと実装の分 離、DSL の合成が実現できる。 One method of creating EDSL. Tagless final has less boilerplate.
  • 34. Introduction to Tagless Final trait UserRepository[F[_]] { def store(user: User): F[Unit] def resolveByName(name: String): F[Option[User]] }
  • 35. Introduction to Tagless Final object UserRepository { implicit val tryUserRepository: UserRepository[Try] = new UserRepository[Try] { def store(user: User): Try[Unit] = ??? def resolveByName(name: String): Try[Option[User]] = ??? } }
  • 36. Introduction to Tagless Final def prog[F[_]](userName: String) (implicit userRepo: UserRepository[F]): F[Option[User]] = userRepo.resolveByName(userName) prog[Try]("John") // scala.util.Try[Option[User]] = Success(None)
  • 38. Agenda Introduction - Play Framework with Google Guice - Purely functional separation of program & execution What’s Tagless Final style? - Overview - It’s your neighbor Tagless Final style practice in Play Framework - Purely, so useful! - But, No Silver Bullet Conclusion
  • 39. よくある UserRepository インターフェースと実装例 trait UserRepository[Ctx] { def store(user: User)(implicit ctx: Ctx): Future[Unit] def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]] } // impl class UserRepositoryOnJDBC()(implicit ec: ExecutionContext) extends UserRepository[DBSession] { def store(user: User)(implicit ctx: DBSession): Future[Unit] = ??? def resolveByName(name: String)(implicit ctx: DBSession): Future[Option[User]] = ??? }
  • 40. Problem 1: 各メソッドが Context を暗黙の値として受け取る - Context を一つだけとることを前提としている - 暗黙的な値が複数必要だった場合にはそれらをまとめるコン テナを用意する必要がある - メソッド内では受け取った値から自分に必要なものを取り出す 作業が発生する Each method takes a context as an implicit parameter.
  • 41. Problem 1: 各メソッドが Context を暗黙の値として受け取る class UserRepositoryCtx(ec: ExecutionContext, dbSession: DBSession) // impl on database class UserRepositoryOnJDBC() extends UserRepository[UserRepositoryCtx] { def store(user: User)(implicit ctx: UserRepositoryCtx): Future[Unit] = { implicit val (ec, s) = (ctx.ec, ctx.dbSession) ??? // update } def resolveByName(name: String) (implicit ctx: UserRepositoryCtx): Future[Option[User]] = { implicit val (ec, s) = (ctx.ec, ctx.dbSession) ??? // select } }
  • 42. Problem 1: 各メソッドが Context を暗黙の値として受け取る case class DummyCtx() // impl by memory object UserRepositoryOnMemory extends UserRepository[DummyCtx]{ private val db = mutable.Map.empty[UserId, User] def store(user: User)(implicit ctx: DummyCtx): Future[Unit] = Future.successful(db.update(user.id, user)) def resolveByName(name: String)(implicit ctx: DummyCtx): Future[Option[User]] = Future.successful(db.collectFirst { case (_, u@User(_, n, _)) if n == name => u }) }
  • 43. Problem 2: メソッドの実行結果をどう表現するか いくつかのパターンが考えられる - 呼び出し側に責任を押し付け失敗を全て例外で throw - 例外を型で表現するために Try に包む - 非同期処理もありうるから Future にしておく - etc. How to express the return value as a type.
  • 44. Problem 2: メソッドの実行結果をどう表現するか // It’s simply! but throwable... trait UserRepository[Ctx] { def store(user: User)(implicit ctx: Ctx): Unit def resolveByName(name: String)(implicit ctx: Ctx): Option[User] } // It’s not throwable! but blocking only... trait UserRepository[Ctx] { def store(user: User)(implicit ctx: Ctx): Try[Unit] def resolveByName(name: String)(implicit ctx: Ctx): Try[Option[User]] } // It’s non blocking! but needs ExecutionContext in Ctx... trait UserRepository[Ctx] { def store(user: User)(implicit ctx: Ctx): Future[Unit] def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]] }
  • 45. つまり、どうすればいいのか UserRepository インターフェース内に実装側の都合を一切持ち 込まないというのが重要 - Context という概念を UserRepository 内に持ち込まない - Context を暗黙の値にとるような前提の実装をやめる - メソッドの実行方法や失敗の表現など、まるっと含めて返り値 の型を抽象的に表現する Do not bring implementation related things to UserRepository interface.
  • 46. Remove implicit parameter trait UserRepository[Ctx] { def store(user: User)(implicit ctx: Ctx): Future[Unit] def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]] }
  • 47. Remove implicit parameter trait UserRepository[Ctx] { def store(user: User)(implicit ctx: Ctx): Future[Unit] def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]] } // 1. remove implicit trait UserRepository[Ctx] { def store(user: User)(ctx: Ctx): Future[Unit] def resolveByName(name: String)(ctx: Ctx): Future[Option[User]] }
  • 48. Remove implicit parameter trait UserRepository[Ctx] { def store(user: User)(implicit ctx: Ctx): Future[Unit] def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]] } // 1. remove implicit trait UserRepository[Ctx] { def store(user: User)(ctx: Ctx): Future[Unit] def resolveByName(name: String)(ctx: Ctx): Future[Option[User]] } // 2. modify return function trait UserRepository[Ctx] { def store(user: User): Ctx => Future[Unit] def resolveByName(name: String): Ctx => Future[Option[User]] }
  • 49. Remove implicit parameter trait UserRepository[Ctx] { def store(user: User): Ctx => Future[Unit] def resolveByName(name: String): Ctx => Future[Option[User]] } // easy to use implicit parameter within impl. class UserRepositoryOnJDBC(implicit ec: ExecutionContext) extends UserRepository[DBSession] { def store(user: User): DBSession => Future[Unit] = { implicit dbSession => ??? } def resolveByName(name: String): DBSession => Future[Option[User]] = { implicit dbSession => ??? } }
  • 50. Really need Context? trait UserRepository[Ctx] { def store(user: User): Ctx => Future[Unit] def resolveByName(name: String): Ctx => Future[Option[User]] }
  • 51. Really need Context? trait UserRepository[Ctx] { def store(user: User): Ctx => Future[Unit] def resolveByName(name: String): Ctx => Future[Option[User]] } // 1. create type alias named `Exec` and replace return type trait UserRepository[Ctx] { type Exec[R] = Ctx => Future[R] def store(user: User): Exec[Unit] def resolveByName(name: String): Exec[Option[User]] }
  • 52. Really need Context? trait UserRepository[Ctx] { def store(user: User): Ctx => Future[Unit] def resolveByName(name: String): Ctx => Future[Option[User]] } // 1. create type alias named `Exec` and replace return type trait UserRepository[Ctx] { type Exec[R] = Ctx => Future[R] def store(user: User): Exec[Unit] def resolveByName(name: String): Exec[Option[User]] } // 2. add `F[_]` type parameter and remove `Exec` trait UserRepository[F[_]] { def store(user: User): F[Unit] def resolveByName(name: String): F[Option[User]] }
  • 53. It’s more abstract interface trait UserRepository[F[_]] { def store(user: User): F[Unit] def resolveByName(name: String): F[Option[User]] }
  • 54. Agenda Introduction - Play Framework with Google Guice - Purely functional separation of program & execution What’s Tagless Final style? - Overview - It’s your neighbor Tagless Final style practice in Play Framework - Purely, so useful! - But, No Silver Bullet Conclusion
  • 55. UserRepository implementation trait UserRepository[F[_]] { def store(user: User): F[Unit] def resolveByName(name: String): F[Option[User]] }
  • 56. UserRepository implementation trait UserRepository[F[_]] { def store(user: User): F[Unit] def resolveByName(name: String): F[Option[User]] } type Query[A] = Reader[DBSession, A] object UserRepositoryOnJDBC { implicit def queryUserRepository: UserRepository[Query] = new UserRepository[Query] { def store(user: User): Query[Unit] = Reader { implicit dbSession => ??? } def resolveByName(name: String): Query[Option[User]] = Reader { implicit dbSession => ??? } } }
  • 57. UserRepository implementation trait UserRepository[F[_]] { def store(user: User): F[Unit] def resolveByName(name: String): F[Option[User]] } type AsyncQuery[A] = ReaderT[Task, DBSession, A] object UserRepositoryOnJDBC { implicit def asyncQueryUserRepository: UserRepository[AsyncQuery] = new UserRepository[AsyncQuery] { def store(user: User): AsyncQuery[Unit] = ReaderT { implicit dbSession => Task(???) } def resolveByName(name: String): AsyncQuery[Option[User]] = ReaderT { implicit dbSession => Task(???) } } }
  • 58. UserRepository implementation trait UserRepository[F[_]] { def store(user: User): F[Unit] def resolveByName(name: String): F[Option[User]] } type UserRepositoryFree[A] = Free[UserRepositoryOp, A] object UserRepositoryOnJDBC { implicit def freeUserRepository: UserRepository[UserRepositoryFree] = new UserRepository[UserRepositoryFree] { def store(user: User): UserRepositoryFree[Unit] = UserRepositoryOp.store(user) def resolveByName(name: String): UserRepositoryFree[Option[User]] = UserRepositoryOp.resolveByName(name) } }
  • 59. UserRepository implementation trait UserRepository[F[_]] { def store(user: User): F[Unit] def resolveByName(name: String): F[Option[User]] } type ConnectionIO[A] = Free[ConnectionOp, A] object UserRepositoryOnJDBC { implicit def doobieUserRepository: UserRepository[ConnectionIO] = new UserRepository[ConnectionIO] { def store(user: User): ConnectionIO[Unit] = sql"...".update.run.map(_ => ()) def resolveByName(name: String): ConnectionIO[Option[User]] = sql"...".query[User].option } }
  • 60. UserRepository implementation object UserRepository { def apply[F[_]](implicit F: UserRepository[F]): UserRepository[F] = F } val dbSession = new DBSession() type Query[A] = Reader[DBSession, A] UserRepository[Query].resolveByName("John Doe") .run(dbSession) type AsyncQuery[A] = ReaderT[Task, DBSession, A] UserRepository[AsyncQuery].resolveByName("John Doe") .run(dbSession).runAsync type UserRepositoryFree[A] = Free[UserRepositoryOp, A] UserRepository[UserRepositoryFree].resolveByName("John Doe") .foldMap(futureInterp(dbSession))
  • 61. UserRepository implementation object UserRepository { def apply[F[_]](implicit F: UserRepository[F]): UserRepository[F] = F } val xa = Transactor.fromDriverManager[Task](...) // for doobie type ConnectionIO[A] = Free[ConnectionOp, A] UserRepository[ConnectionIO].resolveByName("John Doe") .transact(xa).runAsync
  • 62. UserController implementation object UserController { def getUser[F[_]: Monad: UserRepository](id: Long): F[Result] = UserRepository[F].resolveBy(id).map { case Some(user) => Results.Ok(GetUserResponse.from(user).toJson) case None => Results.NotFound } }
  • 63. UserController implementation class UserController @Inject()(cc: ControllerComponents, ec: ExecutionContext) extends AbstractController(cc) { // ... import UserRepositoryOnJDBC._ // impls def getUser(id: Long) = Action { UserController.getUser[Query](id).run(dbSession) } def getUserAsync(id: Long) = Action.async { UserController.getUser[AsyncQuery](id).run(dbSession).runAsync } }
  • 64. UserController testing class UserRepositoryMock extends UserRepository[Id] { def store(user: User): Id[Unit] = ??? def resolveBy(id: Long): Id[Option[User]] = ??? def resolveByName(name: String): Id[Option[User]] = ??? }
  • 65. UserController testing class UserControllerSpec extends PlaySpec { "UserController GET" should { "get user from static controller" in { implicit val mock = new UserRepositoryMock { override def resolveBy(id: Long): Option[User] = if (id == 100) Some(User(100, "John")) else None } UserController.getUser[Id](100).header.status mustBe OK UserController.getUser[Id](999).header.status mustBe NOT_FOUND } } }
  • 66. UserController testing object UserController { // ... def storeAndGet[F[_]: Monad: UserRepository](name: String): F[Result] = for { _ <- UserRepository[F].store(User(Random.nextLong(), name)) user <- UserRepository[F].resolveByName(name) } yield { user match { case Some(u) => Results.Ok(GetUserResponse.from(u).toJson) case None => Results.InternalServerError } } }
  • 67. UserController testing type DB = List[User] class UserRepositoryStateMock extends UserRepository[State[DB, ?]] { def store(user: User): State[DB, Unit] = ??? def resolveBy(id: Long): State[DB, Option[User]] = ??? def resolveByName(name: String): State[DB, Option[User]] = ??? }
  • 68. UserController testing class UserControllerSpec extends PlaySpec { "UserController GET" should { "store and get user from static controller" in { implicit val mock = new UserRepositoryStateMock { override def store(user: User): State[DB, Unit] = State(db => (user :: db, ())) override def resolveByName(name: String): State[DB, Option[User]] = State.inspect(_.find(_.name == name)) } UserController.storeAndGet[State[DB, ?]]("John") .runEmptyA.value.header.status mustBe OK } }
  • 69. Agenda Introduction - Play Framework with Google Guice - Purely functional separation of program & execution What’s Tagless Final style? - Overview - It’s your neighbor Tagless Final style practice in Play Framework - Purely, so useful! - But, No Silver Bullet Conclusion
  • 70. F[_] が異なる場合は for 式で合成できない object MessageController { def getUserMessages[F[_]: Monad: MessageRepository: UserRepository]( userId: Long ): F[Result] = for { user <- UserRepository[F].resolveBy(userId) messages <- MessageRepository[F].resolveAllByUserId(userId) } yield { user match { case Some(u) => Results.Ok(GetMessagesResponse.from(u, messages).toJson) case None => Results.NotFound } } }
  • 71. F[_] が異なる場合は for 式で合成できない type AsyncQuery[A] = ReaderT[Task, DBSession, A] type AsyncCommand[A] = ReaderT[Task, RedisConnection, A] object MessageRepositoryOnJDBC { // MessageRepository[AsyncQuery] is not implemented implicit def redisConnectionMessageRepository: MessageRepository[AsyncCommand] = new MessageRepository[AsyncCommand] { def resolveBy(id: Long): AsyncCommand[Option[Message]] = ??? def store(message: Message): AsyncCommand[Unit] = ??? def resolveAllByUserId(userId: Long): AsyncCommand[List[Message]] = ??? } }
  • 72. F[_] が異なる場合は for 式で合成できない [pfpfap] $ compile [info] Compiling 1 Scala source to /Users/aoiroaoino/git/pfpf/bbs/target/scala-2.12/classes … ... [error] /Users/aoiroaoino/git/pfpf/bbs/app/controllers/MessageController.scala:26:50: could not find implicit value for evidence parameter of type models.repository.MessageRepository[models.AsyncQuery] [error] MessageController.getUserMessages[AsyncQuery](userId).run(dbSession).runAsync [error] ^
  • 73. F[_] が異なる場合は for 式で合成できない いくつかの解決策がある - Monad を積み上げる - F[_] を具体的な型にしてから controller 合成する - 自然変換を使って型を揃える There are some solutions. Stacking Monads, composing concrete types, Natural Transformation.
  • 74. Stacking Monads class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext) extends AbstractController(cc) { // ... type SpecialAction[A] = ReaderT[ReaderT[Task, DBSession, ?], RedisConnection, A] def getUserMessagesSM(userId: Long) = Action.async { MessageController.getUserMessages[SpecialAction](userId) .run(redisConnection) // RedisConnection => ReaderT[Task, DBSession, A] .run(dbSession) // DBSession => Task[A] .runAsync // CancelableFuture[A] } }
  • 75. Composing concrete types class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext) extends AbstractController(cc) { // ... def getUserMessages(userId: Long) = Action.async { (for { user <- UserRepository[AsyncQuery].resolveBy(userId).run(dbSession) messages <- MessageRepository[AsyncCommand].resolveAllByUserId(userId) .run(redisConnection) } yield { user match { case Some(u) => Results.Ok(GetMessagesResponse.from(u, messages).toJson) case None => Results.NotFound } }).runAsync } }
  • 76. Use natural transformation and mapK() class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext) extends AbstractController(cc) { // ... type AsyncQuery[A] = ReaderT[Task, DBSession, A] type AsyncCommand[A] = ReaderT[Task, RedisConnection, A] // common type of AsyncQuery and AsyncCommand type Exec[A] = ReaderT[Task, (DBSession, RedisConnection), A] def getUserMessages(userId: Long) = Action.async { MessageController.getUserMessages[Exec](userId) .run((dbSession, redisConnection)).runAsync } }
  • 77. Use natural transformation and mapK() trait MessageRepository[F[_]] { def store(message: Message): F[Unit] def resolveBy(id: Long): F[Option[Message]] def resolveAllByUserId(userId: Long): F[List[Message]] def mapK[G[_]](fk: F ~> G): MessageRepository[G] = new MessageRepository[G] { def store(message: Message): G[Unit] = fk(self.store(message)) def resolveBy(id: Long): G[Option[Message]] = fk(self.resolveBy(id)) def resolveAllByUserId(userId: Long): G[List[Message]] = fk(self.resolveAllByUserId(userId)) } }
  • 78. Use natural transformation and mapK() class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext) extends AbstractController(cc) { // ... type Exec[A] = ReaderT[Task, (DBSession, RedisConnection), A] val asyncQueryToExec: AsyncQuery ~> Exec = new (AsyncQuery ~> Exec) { def apply[A](aq: AsyncQuery[A]): Exec[A] = ReaderT { case (dbSession, _) => aq.run(dbSessioin) } } val asyncCommandToExec: AsyncCommand ~> Exec = new (AsyncCommand ~> Exec) { def apply[A](ac: AsyncCommand[A]): Exec[A] = ReaderT { case (_, redisConn) => ac.run(redisConn) } } }
  • 79. Use natural transformation and mapK() class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext) extends AbstractController(cc) { // ... type Exec[A] = ReaderT[Task, (DBSession, RedisConnection), A] val asyncQueryToExec: AsyncQuery ~> Exec = ... val asyncCommandToExec: AsyncCommand ~> Exec = ... implicit val execUserRepository: UserRepository[Exec] = UserRepository[AsyncQuery].mapK(asyncQueryToExec) implicit val execMessageRepository: MessageRepository[Exec] = MessageRepository[AsyncCommand].mapK(asyncCommandToExec) }
  • 80. Use natural transformation and mapK() class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext) extends AbstractController(cc) { // ... type Exec[A] = ReaderT[Task, (DBSession, RedisConnection), A] val asyncQueryToExec: AsyncQuery ~> Exec = ... val asyncCommandToExec: AsyncCommand ~> Exec = ... implicit val execUserRepository: UserRepository[Exec] = ... implicit val execMessageRepository: MessageRepository[Exec] = ... def getUserMessages(userId: Long) = Action.async { MessageController.getUserMessages[Exec](userId) .run((dbSession, redisConnection)).runAsync } }
  • 81. Agenda Introduction - Play Framework with Google Guice - Purely functional separation of program & execution What’s Tagless Final style? - Overview - It’s your neighbor Tagless Final style practice in Play Framework - Purely, so useful! - But, No Silver Bullet Conclusion
  • 82. Conclusion Google Guice でアプリを全て組み立てる必要はない - プログラムとインタープリタの分離をする方法はいくつもあ る。 - Reader や Free もいいけど直接使うと不便さもある。 There are many ways to separate program & interpreter.
  • 83. Conclusion Tagless Final 便利 - 手で書いても苦にならない程度のコード量 - 複数の DSL を比較的簡単に合成できる - F[_] が異なる場合に一工夫必要。銀の弾丸ではない。 - Play Framework に限らずいろんな場面で使える! Tagless final is useful because it has less boilerplate.