我有一个用Scala编写的服务,它使用scalaz.Reader
进行DI和测试。
在测试中定义了op
函数,以组成服务的函数。
import scala.util.{Failure, Success, Try}
import scalaz.{Reader => ScalazReader}
trait AccountServiceScalazReader[Account, Amount, Balance] {
def open(no: String, name: String, openingDate: Option[Date]): ScalazReader[AccountRepository, Try[Account]]
def close(no: String, closeDate: Option[Date]): ScalazReader[AccountRepository, Try[Account]]
def debit(no: String, amount: Amount): ScalazReader[AccountRepository, Try[Account]]
def credit(no: String, amount: Amount): ScalazReader[AccountRepository, Try[Account]]
def balance(no: String): ScalazReader[AccountRepository, Try[Balance]]
}
object AccountServiceScalazReader extends AccountServiceScalazReader[Account, Amount, Balance] {
def open(no: String, name: String, openingDate: Option[Date]) = ScalazReader { (repo: AccountRepository) =>
repo.query(no) match {
case Success(Some(a)) => Failure(new Exception(s"Already existing account with no $no"))
case Success(None) =>
if (no.isEmpty || name.isEmpty) Failure(new Exception(s"Account no or name cannot be blank") )
else if (openingDate.getOrElse(today) before today) Failure(new Exception(s"Cannot open account in the past"))
else repo.store(Account(no, name, openingDate.getOrElse(today)))
case Failure(ex) => Failure(new Exception(s"Failed to open account $no: $name", ex))
}
}
def close(no: String, closeDate: Option[Date]) = ScalazReader { (repo: AccountRepository) =>
repo.query(no) match {
case Success(Some(a)) =>
if (closeDate.getOrElse(today) before a.dateOfOpening)
Failure(new Exception(s"Close date $closeDate cannot be before opening date ${a.dateOfOpening}"))
else repo.store(a.copy(dateOfClosing = closeDate))
case Success(None) => Failure(new Exception(s"Account not found with $no"))
case Failure(ex) => Failure(new Exception(s"Fail in closing account $no", ex))
}
}
def debit(no: String, amount: Amount) = ScalazReader { (repo: AccountRepository) =>
repo.query(no) match {
case Success(Some(a)) =>
if (a.balance.amount < amount) Failure(new Exception("Insufficient balance"))
else repo.store(a.copy(balance = Balance(a.balance.amount - amount)))
case Success(None) => Failure(new Exception(s"Account not found with $no"))
case Failure(ex) => Failure(new Exception(s"Fail in debit from $no amount $amount", ex))
}
}
def credit(no: String, amount: Amount) = ScalazReader { (repo: AccountRepository) =>
repo.query(no) match {
case Success(Some(a)) => repo.store(a.copy(balance = Balance(a.balance.amount + amount)))
case Success(None) => Failure(new Exception(s"Account not found with $no"))
case Failure(ex) => Failure(new Exception(s"Fail in credit to $no amount $amount", ex))
}
}
def balance(no: String) = ScalazReader((repo: AccountRepository) => repo.balance(no))
}
测试:
import org.junit.{Assert, Test}
import scala.util.Try
class AccountServiceScalazReaderTest {
import com.savdev.fp.monad.di.reader.AccountServiceScalazReader._
def op(no: String):scalaz.Reader[AccountRepository, Try[Balance]]
= for {
_ <- credit(no, BigDecimal(100))
_ <- credit(no, BigDecimal(300))
_ <- debit(no, BigDecimal(160))
b <- balance(no)
} yield b
@Test def testOpComposition: Unit = {
val newOp = for {
_ <- open("a-123", "Alex", Option.empty)
b <- op("a-123")
} yield b
val balance = newOp run (new TestAccountRepository)
Assert.assertTrue(balance.isSuccess)
Assert.assertEquals(Balance(240), balance.get)
println(balance)
}
@Test def testOpCompositionNotExistingAccount: Unit = {
val balance = op("a-123") run (new TestAccountRepository)
Assert.assertTrue(balance.isFailure)
Assert.assertEquals("No account exists with no a-123", balance.failed.get.getMessage)
println(balance)
}
}
现在我正在尝试从 Java 代码编写相同的测试。我什至无法定义op
函数的签名:
import scala.util.Try;
import scalaz.Reader;
public class AccountServiceScalazReaderFromJavaTest {
scalaz.Reader<AccountRepository, Try<Balance>> op(String no) {
return null;
}
}
我现在得到:
AccountServiceScalazReaderFromJavaTest.java:[12,9] cannot find symbol
symbol: class Reader
location: package scalaz
我错过了什么?如何从 Java 代码实现op
?
Scala 有类型别名,这在 Java 中是不存在的。Reader 不是一个类,它是一个带有伴随对象的别名:
type Reader[E, A] = ReaderT[Id, E, A]
object Reader extends scala.Serializable {
def apply[E, A](f: E => A): Reader[E, A] = Kleisli[Id, E, A](f)
}
type ReaderT[F[_], E, A] = Kleisli[F, E, A]
val ReaderT = Kleisli
所以我想它会更接近
Kleisli<?, AccountRepository, Try<Balance>> op(String no) {
return null;
}
虽然 Java 没有 HKT,所以我想在javac
接受Id[_]
之前可能会有一些反复试验(特别是因为Id[_]
也是一个类型别名,而不是一个类(。