package nick
import java.time.Instant
import java.util.concurrent.atomic.AtomicInteger
import scala.util.{Failure, Random, Success, Try}
case class Dto(name: String)
case class Entity(
var id: Int,
var name: String,
var createdAt: Instant
)
// like true Dao sometimes fails
class Dao {
val random = new Random()
var currentId = new AtomicInteger(0)
def store(e: Entity): Unit = {
if (random.nextInt(10) < 1) {
throw new Exception("Database error")
} else {
e.id = currentId.incrementAndGet()
}
}
}
class MyService {
val dao = new Dao()
// this method requires to mock dao to be tested
def createMetadataBefore(dto: Dto): Dto = {
val e = convertD2E(dto)
e.createdAt = Instant.now()
dao.store(e)
convertE2D(e)
}
// split the effectful method into 2 pure functions
// one is before store call
def createMetadata1(dto: Dto, storeFun: Entity ⇒ Try[Entity]): Try[Entity] = {
val e = convertD2E(dto)
e.createdAt = Instant.now()
storeFun(e)
}
// one is after store call
def createMetadata2(e: Entity): Dto = {
convertE2D(e)
}
def createMetadataAfter(dto: Dto): Dto = {
val storeFun: Entity ⇒ Try[Entity] = e ⇒ {
try {
val e1 = e.copy() // to not spoil the argument
dao.store(e1)
Success(e1)
} catch {
case x: Exception ⇒
Failure(x)
}
}
val result = createMetadata1(dto, storeFun)
result match {
case Success(e) ⇒ createMetadata2(e)
case Failure(ex) ⇒ throw ex
}
}
def convertE2D(e: Entity): Dto = Dto(e.name)
def convertD2E(d: Dto): Entity = Entity(0, d.name, Instant.MIN)
}
object MyServiceTest extends App {
val service = new MyService
def test(): Unit = {
val now = Instant.now()
val storingFun: Entity ⇒ Try[Entity] =
e ⇒ Success(e.copy(id = 1, createdAt = now))
val dto = Dto("Hello!")
val r1 = service.createMetadata1(dto, storingFun)
assert(r1 == Success(Entity(1, "Hello!", now)))
val r2 = service.createMetadata2(r1.get)
assert(r2 == Dto("Hello!"))
}
test()
}
Comments
0 B
|👍
/👎
0 B
|👍
/👎