Case class 禁止继承,但是如何从库中建模依赖?

Case class inheritance is prohibited, but how to model dependency from library?

我有三个不同的 Scala 项目,其中一个仅包含在其他两个 Scala 项目中使用的模型(以下称为库)。这三个项目之一是一个 Play 网络应用程序,我想将库中的模型保存在 MongoDB 中(代码驻留在 Play 应用程序中)。

所以在我的库中我有以下模型:

case class MyUser(id: UUID = UUID.randomUUID(), timestamp: Instant = Instant.now, name: String)

要在我的 Play 应用程序中保留 MyUser 的实例,我通常会使用此模型:

case class MyUser(var _id: Option[BSONObjectID] = None,
                   id: UUID,
                   timestamp: Instant,
                   name: String,
                   var created: Option[DateTime] = None,
                   var updated: Option[DateTime] = None
                  ) extends Temporal

现在我正在寻找一种方法将我的库 MyUser 变形为我的 Play 应用程序中相应的 class,所以我想我会做这样的事情:

case class MyUser(var _id: Option[BSONObjectID] = None,
                   var created: Option[DateTime] = None,
                   var updated: Option[DateTime] = None
                  ) extends lib.MyUser with Temporal

但是,现在我得到以下错误:

case-to-case inheritance is prohibited. To overcome this limitation, use extractors to pattern match on non-leaf nodes.

我想过这样做:

case class MyUser(var _id: Option[BSONObjectID] = None,
                   user: lib.MyUser,
                   var created: Option[DateTime] = None,
                   var updated: Option[DateTime] = None
                  ) extends Temporal

我怎样才能以正确的方式设计它?

首先,错误告诉你的是你不应该为扩展的 class 使用 case class。它所说的关于提取器的事情基本上是,如果需要在父 class 上进行模式匹配(这是 case classcase 的来源,简单的模式匹配),你总是可以为它编写自定义提取器。

但是,这不适用于您的情况,因为您无法更改您的库公开 case classes 的事实。

另一种解决方案是考虑到您的两个 classes 之间没有继承关系。实际上,一个是用户的表示,而另一个是包含用户的存储单元的表示。所以看起来你提出的解决方案毕竟有一些意义。

不过,无论是在序列化对象(在 Mongo 中)还是在 JVM class 中,都必须添加一个字段似乎很麻烦,您需要始终在其中执行 mongoUser.userMyUser...

得到 lib.MyUser

一个优雅的(恕我直言)解决方案是保留这个字段 user: lib.MyUser,但是让你的序列化把这个字段压平到 BSON 对象的顶层,这样一个序列化的 MyUser 看起来喜欢

{
  _id: ObjectId(_),
  id: 123456789abc-1234-1234-1234,
  ...
}

就好像你继承了lib.MyUser的领域一样。

反序列化

现在,只要您想从 Mongo 中获取 lib.MyUser,仅供阅读,您可以直接以这种格式反序列化它,忽略添加的字段。但是,如果您需要对其进行一些更新,则必须将其反序列化为 MyUser,以便能够更新此特定的 BSON 文档。

scala 用法

当您将对象反序列化为 MyUser(例如,为了更新)时,您可能仍然希望轻松访问 lib.MyUser 中公开的所有字段,而无需访问该字段user 每次。这可以通过隐式转换来完成。

泛化

顺便说一下,您可以对任何您想要序列化的对象以通用方式执行此操作...

总结一下

case class MongoRepr[T](_id: Option[BSONObjectId]
                         value: T,
                         created: Option[DateTime],                             
                         updated: Option[DateTime])

object MongoRepr {
  //implement flattened (de)serialization, this depends on your
  // mongo driver and what you're using for serialization (JSON/BSON)
  def handler[T](implicit handlerT: BSONHandler[T]): BSONHandler[MongoRepr[T]] = ???

 implicit def toValue[T](repr: MongoRepr[T]): T = repr.value
}