Skip to content

Commit 0139412

Browse files
committed
Add Either interpreter
1 parent 88855e0 commit 0139412

File tree

15 files changed

+254
-49
lines changed

15 files changed

+254
-49
lines changed

build.sbt

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ lazy val root = (project in file("."))
1010
name := "fujitask-eff",
1111
publishArtifact := false,
1212
publish := {},
13-
publishLocal := {},
13+
publishLocal := {}
1414
)
1515
.settings(publishSettings)
1616
.aggregate(fujitaskEff, example)
@@ -104,4 +104,4 @@ val tagName = Def.setting {
104104
val tagOrHash = Def.setting {
105105
if (isSnapshot.value) sys.process.Process("git rev-parse HEAD").lineStream_!.head
106106
else tagName.value
107-
}
107+
}

example/src/main/scala/Main.scala

+27-9
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import java.util.concurrent.TimeUnit
2-
32
import com.google.inject.Guice
43
import config.di.DefaultModule
4+
import domain.entity.UserId
5+
import domain.exception.UserException
6+
import domain.exception.UserException.NoSuchUserException
7+
import domain.usecase.UpdateUserNameUseCase
58
import fujitask.eff.Fujitask
69
import infra.db.Database
710
import infra.ec.ExecutionContextProvider
8-
import kits.eff.Reader
11+
import kits.eff.{Exc, Opt, Reader}
912
import org.slf4j.{Logger, LoggerFactory}
1013
import repository.UserRepository
1114
import scala.concurrent.duration.Duration
@@ -33,16 +36,14 @@ object Main {
3336
val userRepository: UserRepository = injector.getInstance(classOf[UserRepository])
3437

3538
val eff1 = for {
36-
user1 <- userRepository.read(1L)
3739
_ <- userRepository.create("test")
38-
user2 <- userRepository.read(1L)
40+
user2 <- userRepository.read(UserId(1L))
3941
} yield {
40-
logger.info(s"user1 is $user1")
4142
logger.info(s"user2 is $user2")
4243
}
4344

4445
val eff2 = for {
45-
user3 <- userRepository.read(1L)
46+
user3 <- userRepository.read(UserId(1L))
4647
} yield {
4748
logger.info(s"user3 is $user3")
4849
}
@@ -53,15 +54,32 @@ object Main {
5354
user4 <- userRepository.read(user.id)
5455
} yield {
5556
logger.info(s"user4 is $user4")
57+
user
58+
}
59+
val updateUserNameUseCase = injector.getInstance(classOf[UpdateUserNameUseCase])
60+
61+
val eff4 = for {
62+
user <- userRepository.create("piyopiyo")
63+
_ = logger.info(s"$user name is piyopiyo.")
64+
updatedUser <- updateUserNameUseCase.updateUserName(
65+
user.id, "hogehoge!!!!!!!!!!!!!!!"
66+
)
67+
a <- Exc.raise[UserException](NoSuchUserException("aaa"))
68+
} yield {
69+
logger.info(s"user5 is $updatedUser")
5670
}
5771

5872
values {
5973
for {
60-
_ <- Fujitask.run(eff1)
61-
_ <- Fujitask.run(eff2)
62-
_ <- Fujitask.run(Reader.run("piyo")(eff3))
74+
_ <- Fujitask.run(Opt.run(eff1))
75+
_ <- Fujitask.run(Opt.run(eff2))
76+
_ <- Fujitask.run(Opt.run(Reader.run("piyo")(eff3)))
77+
_ = logger.info(s"There are ${Database.getAllUsers} users (1)")
78+
_ <- Fujitask.runWithEither(eff4)
6379
} yield ()
6480
}
81+
82+
logger.info(s"There are ${Database.getAllUsers} users")
6583
}
6684

6785
def main(args: Array[String]): Unit = {

example/src/main/scala/config/di/DefaultModule.scala

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package config.di
22

33
import com.google.inject.AbstractModule
4+
import domain.usecase.UpdateUserNameUseCase
5+
import domain.usecase.impl.UpdateUserNameUseCaseImpl
46
import infra.ec.ExecutionContextProvider
57
import repository.UserRepository
68
import repository.impl.jdbc.UserRepositoryImpl
@@ -11,5 +13,7 @@ class DefaultModule extends AbstractModule {
1113
bind(classOf[ExecutionContext]).toProvider(classOf[ExecutionContextProvider])
1214

1315
bind(classOf[UserRepository]).to(classOf[UserRepositoryImpl])
16+
17+
bind(classOf[UpdateUserNameUseCase]).to(classOf[UpdateUserNameUseCaseImpl])
1418
}
1519
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
package domain.entity
22

3-
case class User(id: Long, name: String)
3+
case class User(id: UserId, name: String)
4+
5+
case class UserId(value: Long)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package domain.exception
2+
3+
sealed trait UserException extends Throwable
4+
5+
object UserException {
6+
case class NoSuchUserException(
7+
message: String = null,
8+
cause: Throwable = null
9+
) extends UserException
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package domain.usecase
2+
3+
import domain.entity.{User, UserId}
4+
import domain.exception.UserException
5+
import kits.eff.{Eff, Exc}
6+
import repository.ReadWriteTransaction
7+
8+
trait UpdateUserNameUseCase {
9+
def updateUserName(
10+
id: UserId,
11+
name: String
12+
): Eff[ReadWriteTransaction with Exc[UserException], User]
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package domain.usecase.impl
2+
3+
import domain.entity.{User, UserId}
4+
import domain.exception.UserException
5+
import domain.exception.UserException.NoSuchUserException
6+
import domain.usecase.UpdateUserNameUseCase
7+
import javax.inject.Inject
8+
import kits.eff.{Eff, Exc, Opt}
9+
import repository.{ReadWriteTransaction, UserRepository}
10+
11+
class UpdateUserNameUseCaseImpl @Inject()(
12+
userRepository: UserRepository
13+
) extends UpdateUserNameUseCase {
14+
def updateUserName(
15+
id: UserId,
16+
name: String
17+
): Eff[ReadWriteTransaction with Exc[UserException], User] = {
18+
val opt = for {
19+
user <- userRepository.read(id)
20+
updatedUser = user.copy(name = name)
21+
_ <- userRepository.update(updatedUser)
22+
} yield updatedUser
23+
24+
Opt.run(opt) flatMap {
25+
case Some(updateUser) =>
26+
Exc.lift(Right(updateUser))
27+
case None =>
28+
Exc.lift(Left(NoSuchUserException("No such a user!")))
29+
}
30+
}
31+
}

example/src/main/scala/infra/db/Database.scala

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package infra.db
22

3+
import domain.entity.{User, UserId}
34
import scalikejdbc._
45
import scalikejdbc.config.DBs
56

@@ -19,6 +20,10 @@ object Database {
1920
select count(1) from `user`
2021
"""
2122

23+
private val allUserIds = sql"""
24+
select * from `user`
25+
"""
26+
2227
def setUp(): Unit = DBs.setupAll()
2328

2429
def close(): Unit = DBs.closeAll()
@@ -40,4 +45,10 @@ object Database {
4045
count.map(_.int(1)).single.apply()
4146
}).map(_ == 0).get
4247
}
48+
49+
def getAllUsers: Seq[User] = {
50+
DB.readOnly { implicit s =>
51+
allUserIds.map(u => User(UserId(u.long(1)), u.string(2))).list.apply()
52+
}
53+
}
4354
}
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package repository
22

3-
import domain.entity.User
4-
import kits.eff.Eff
3+
import domain.entity.{User, UserId}
4+
import kits.eff.{Eff, Opt}
55

66
trait UserRepository {
77
def create(name: String): Eff[ReadWriteTransaction, User]
88

9-
def read(id: Long): Eff[ReadTransaction, Option[User]]
9+
def read(id: UserId): Eff[ReadTransaction with Opt, User]
1010

1111
def update(user: User): Eff[ReadWriteTransaction, Unit]
1212

13-
def delete(id: Long): Eff[ReadWriteTransaction, Unit]
13+
def delete(id: UserId): Eff[ReadWriteTransaction, Unit]
1414
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package repository.impl.jdbc
22

3-
import domain.entity.User
3+
import domain.entity.{User, UserId}
44
import fujitask.eff.Fujitask
5-
import kits.eff.Eff
5+
import kits.eff.{Eff, Opt}
66
import repository.{ReadTransaction, ReadWriteTransaction, UserRepository}
77
import scalikejdbc.DBSession
88
import scalikejdbc._
@@ -14,30 +14,32 @@ class UserRepositoryImpl extends UserRepository {
1414

1515
val sql = sql"""insert into user (name) values ($name)"""
1616
val id = sql.updateAndReturnGeneratedKey.apply()
17-
User(id, name)
17+
User(UserId(id), name)
1818
}
1919

20-
def read(id: Long): Eff[ReadTransaction, Option[User]] =
21-
Fujitask.ask map { (i: ScalikeJDBCReadTransaction) =>
20+
def read(id: UserId): Eff[ReadTransaction with Opt, User] =
21+
Fujitask.ask flatMap { (i: ScalikeJDBCReadTransaction) =>
2222
implicit val session: DBSession = i.ctx
2323

24-
val sql = sql"""select * from user where id = $id"""
25-
sql.map(rs => User(rs.long("id"), rs.string("name"))).single.apply()
24+
val sql = sql"""select * from user where id = ${id.value}"""
25+
Opt.lift(
26+
sql.map(rs => User(UserId(rs.long("id")), rs.string("name"))).single.apply()
27+
)
2628
}
2729

2830
def update(user: User): Eff[ReadWriteTransaction, Unit] =
2931
Fujitask.ask map { (i: ScalikeJDBCWriteTransaction) =>
3032
implicit val session: DBSession = i.ctx
3133

32-
val sql = sql"""update user set name = ${user.name} where id = ${user.id}"""
34+
val sql = sql"""update user set name = ${user.name} where id = ${user.id.value}"""
3335
sql.update.apply()
3436
}
3537

36-
def delete(id: Long): Eff[ReadWriteTransaction, Unit] =
38+
def delete(id: UserId): Eff[ReadWriteTransaction, Unit] =
3739
Fujitask.ask map { (i: ScalikeJDBCWriteTransaction) =>
3840
implicit val session: DBSession = i.ctx
3941

40-
val sql = sql"""delete user where id = $id"""
42+
val sql = sql"""delete user where id = ${id.value}"""
4143
sql.update.apply()
4244
}
4345
}

example/src/main/scala/repository/impl/jdbc/package.scala

+10-5
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import fujitask.eff.FujitaskRunner
44
import org.slf4j.{Logger, LoggerFactory}
55
import scalikejdbc.DB
66
import repository.{ReadTransaction, ReadWriteTransaction}
7-
87
import scala.concurrent.{ExecutionContext, Future}
8+
import scala.util.{Failure, Success}
99

1010
package object jdbc {
1111
lazy val logger: Logger = LoggerFactory.getLogger(this.getClass)
@@ -28,10 +28,15 @@ package object jdbc {
2828
new FujitaskRunner[I] {
2929
def apply[A](task: I => Future[A]): Future[A] = {
3030
logger.info("ReadWriteRunner begin --------->")
31-
val future = DB.futureLocalTx(session => task(new ScalikeJDBCWriteTransaction(session)))
32-
future.onComplete(_ =>
33-
logger.info("<--------- ReadWriteRunner end")
34-
)
31+
val future = DB.futureLocalTx { session =>
32+
task(new ScalikeJDBCWriteTransaction(session))
33+
}
34+
future.onComplete {
35+
case Success(_) =>
36+
logger.info("<--------- ReadWriteRunner end")
37+
case Failure(e) =>
38+
logger.warn(s"failed!: ${e.getMessage}")
39+
}
3540
future
3641
}
3742
}

example/src/test/scala/repository/impl/jdbc/UserRepositoryImplSpec.scala

+39-12
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
package repository.impl.jdbc
22

33
import java.util.concurrent.TimeUnit
4-
54
import com.google.inject.Guice
65
import config.di.DefaultModule
7-
import domain.entity.User
6+
import domain.entity.{User, UserId}
87
import fujitask.eff.Fujitask
98
import infra.db.Database
10-
import kits.eff.Reader
9+
import kits.eff.{Exc, Opt, Reader}
1110
import org.scalatest.{DiagrammedAssertions, FlatSpec}
1211
import repository.{ReadTransaction, ReadWriteTransaction, UserRepository}
13-
1412
import scala.concurrent.ExecutionContext.Implicits.global
1513
import org.scalatest._
16-
1714
import scala.concurrent.duration.Duration
1815
import scala.concurrent.{Await, Future}
1916
import scala.util.Try
@@ -48,16 +45,16 @@ class UserRepositoryImplSpec
4845
"UserRepository" should "create/read/update/delete a user in ReadWrite transaction successfully" in new SetUp {
4946
val actual = for {
5047
user1 <- sut.create("test")
51-
user2Opt <- sut.read(user1.id)
48+
user2Opt <- Opt.run(sut.read(user1.id))
5249
_ <- sut.update(user1.copy(name = "changed"))
53-
user3Opt <- sut.read(user1.id)
50+
user3Opt <- Opt.run(sut.read(user1.id))
5451
_ <- sut.delete(user1.id)
55-
user4Opt <- sut.read(user1.id)
52+
user4Opt <- Opt.run(sut.read(user1.id))
5653
i <- Fujitask.ask[ReadWriteTransaction, ScalikeJDBCWriteTransaction]
5754
} yield {
58-
assert(user1 == User(1L, "test"))
59-
assert(user2Opt.contains(User(1L, "test")))
60-
assert(user3Opt.contains(User(1L, "changed")))
55+
assert(user1 == User(UserId(1L), "test"))
56+
assert(user2Opt.contains(User(UserId(1L), "test")))
57+
assert(user3Opt.contains(User(UserId(1L), "changed")))
6158
assert(user4Opt.isEmpty)
6259
assert(i.isInstanceOf[ScalikeJDBCWriteTransaction])
6360
}
@@ -67,7 +64,7 @@ class UserRepositoryImplSpec
6764

6865
it should "read a user in Read only transaction successfully" in new SetUp {
6966
val actual = for {
70-
_ <- sut.read(1L)
67+
_ <- Opt.run(sut.read(UserId(1L)))
7168
i <- Fujitask.ask[ReadTransaction, ScalikeJDBCReadTransaction]
7269
} yield {
7370
assert(i.isInstanceOf[ScalikeJDBCReadTransaction])
@@ -101,4 +98,34 @@ class UserRepositoryImplSpec
10198
assert(Try(values(Fujitask.run(actual))).isFailure)
10299
assert(Database.isEmpty)
103100
}
101+
102+
it should "rollback if Either.Left and using `runWithEither`" in new SetUp {
103+
assert(Database.isEmpty)
104+
105+
val eff = for {
106+
_ <- sut.create("test")
107+
_ <- Exc.raise(new RuntimeException())
108+
} yield ()
109+
110+
val actual = Try(values(Fujitask.runWithEither(eff)))
111+
112+
assert(actual.isSuccess)
113+
assert(actual.get.isLeft)
114+
assert(Database.isEmpty)
115+
}
116+
117+
it should "NOT rollback if Either.Left but using `run`" in new SetUp {
118+
assert(Database.isEmpty)
119+
120+
val eff = for {
121+
_ <- sut.create("test")
122+
_ <- Exc.raise(new RuntimeException())
123+
} yield ()
124+
125+
val actual = Try(values(Fujitask.run(Exc.run(eff))))
126+
127+
assert(actual.isSuccess)
128+
assert(actual.get.isLeft)
129+
assert(!Database.isEmpty)
130+
}
104131
}

0 commit comments

Comments
 (0)