提出问题的其他方式是:"我是否应该保持来自数据库的数据类型简单和原始,就像我从我的REST端点那样问及#34;
想象一下我要将这个案例类作为一行存储在数据库中:
case class Product(id: UUID,name: String, price: BigInt)
它显然不是,也不应该是它所说的,因为name
和price
的类型签名是谎言。
所以我们所做的是创建自定义数据类型,以更好地表示以下内容:(为简单起见,我们唯一关心的是price
数据类型)
case class Price(value: BigInt) {
require(value > BigInt(0))
}
object Price {
def validate(amount: BigInt): Either[String,Price] =
Try(Price(amount)).toOption.toRight("invalid.price")
}
//As a result my Product class is now:
case class Product(id: UUID,name: String,price: Price)
现在,对产品数据进行用户输入的过程如下所示:
//this class would be parsed from i.e a form:
case class ProductInputData(name: String, price: BigInt)
def create(input: ProductInputData) = {
for {
validPrice <- Price.validate(input.price)
} yield productsRepo.insert(
Product(id = UUID.randomUUID,name = input.name,price = ???)
)
}
查看三重问号(???
)。从整个应用程序架构的角度来看,这是我的主要关注点;如果我能够将列Price
存储在数据库中(例如slick
支持这些自定义数据类型),那么这意味着我可以选择将价格存储为price : BigInt = validPrice.value
或price: Price = validPrice
。
我在这两个决定中看到了很多利弊,我无法决定。 以下是我看到支持每个选择的论据:
将数据存储为简单数据库类型(即BigInt
),因为:
性能:x > 0
创建Price
的简单断言是微不足道的,但想象一下您想要验证自定义Email
类型复杂的正则表达式。这对于检索馆藏是有害的
对抗腐败的容忍度:如果BigInt
被插入为负值,每当您的应用程序尝试简单地读取该列并投掷时,它就会在您的脸上爆炸它出现在用户界面上。然而,如果它被检索然后涉及一些域层处理(例如购买),则会引起问题。
将数据存储为域丰富类型(即Price
),因为:
//two terrible variations of a calculateDiscount method:
//this version simply trusts that price is already valid and came from db:
def calculateDiscount(price: BigInt): BigInt = {
//apply some positive coefficient to price and hopefully get a positive
//number from it and if it's not positive because price is not positive then
//it'll explode in your face.
}
//this version is even worse. It does retain function totality and purity
//but the unforgivable culture it encourages is the kind of defensive and
//pranoid programming that causes every developer to write some guard
//expressions performing duplicated validation All over!
def calculateDiscount(price: BigInt): Option[BigInt] = {
if (price <= BigInt(0))
None
else
Some{
//Do safe processing
}
}
//ideally you want it to look like this:
def calculateDiscount(price: Price): Price
我看到的所有混乱的来源是数据库。如果数据来自用户,则很容易:您根本不相信它是有效的。您要求简单的数据类型将它们转换为具有验证的域类型,然后继续。但不是数据库。现代分层架构是否以某种明确或至少减轻的方式解决了这个问题?
答案 0 :(得分:2)
从DB检索时require
调用的开销可以忽略不计。如果你真的认为这是一个问题,请提供2个构造函数,一个用于来自用户的数据(执行验证),另一个假定数据是好的(意图由数据库代码使用)。
我喜欢异常,因为他们指出了一个错误(数据损坏,因为在途中验证不充分)。
也就是说,我经常在代码中留下requires
以帮助捕获更复杂验证中的错误(可能来自多个表的数据以某种无效方式组合)。系统仍然崩溃(应该如此),但我收到了更好的错误消息。