我应该对从数据库检索到的数据的有效性投入多少信任?

时间:2017-11-06 15:49:42

标签: database scala architecture domain-driven-design trust

提出问题的其他方式是:"我是否应该保持来自数据库的数据类型简单和原始,就像我从我的REST端点那样问及#34;

想象一下我要将这个案例类作为一行存储在数据库中:

case class Product(id: UUID,name: String, price: BigInt)

它显然不是,也不应该是它所说的,因为nameprice的类型签名是谎言。

所以我们所做的是创建自定义数据类型,以更好地表示以下内容:(为简单起见,我们唯一关心的是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.valueprice: 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
  • 没有将域类型持续转换为简单类型,反之亦然:用于表示,存储,域层等;你只需要在系统中有一个表示来统治它们。

我看到的所有混乱的来源是数据库。如果数据来自用户,则很容易:您根本不相信它是有效的。您要求简单的数据类型将它们转换为具有验证的域类型,然后继续。但不是数据库。现代分层架构是否以某种明确或至少减轻的方式解决了这个问题?

1 个答案:

答案 0 :(得分:2)

  1. 保护数据库的完整性。正如您将保护对象内部状态的完整性一样。
  2. 信任数据库。检查并重新检查已经检查过的内容是没有意义的。
  3. 尽可能长时间使用域对象。等到最后一刻放弃它们(原始JDBC代码或在数据呈现之前)。
  4. 不要容忍损坏的数据。如果数据损坏,应用程序应该崩溃。否则,它可能会产生更多损坏的数据。
  5. 从DB检索时require调用的开销可以忽略不计。如果你真的认为这是一个问题,请提供2个构造函数,一个用于来自用户的数据(执行验证),另一个假定数据是好的(意图由数据库代码使用)。

    我喜欢异常,因为他们指出了一个错误(数据损坏,因为在途中验证不充分)。

    也就是说,我经常在代码中留下requires以帮助捕获更复杂验证中的错误(可能来自多个表的数据以某种无效方式组合)。系统仍然崩溃(应该如此),但我收到了更好的错误消息。