带有reactivemongo的Id的案例类应该是可选的或必需的

时间:2018-05-04 17:51:14

标签: scala playframework functional-programming scalaz reactivemongo

我正在为scala中的用户构建以下API:

case class User(_id: BSONObjectId, username: String)

问题在于,当客户端发送请求以存储/创建新用户时,_id不存在。

一个可行的解决方案似乎是:

case class User(_id: Option[BSONObjectId], username: String)

然而,检查选项字段似乎有点烦人,不仅是为了插入用户,还为了其他CRUD操作。

我得到的一个建议是有两种类型:

case class User(name: String) ; type UserWithId = (Id, User)

case class User[A](id: A, name: String); type UserWithId = User[BSONObjectId]
type UserWithoutId = User[Unit]

我选择了后一种实施方式,因为它看似合适。 我创建了适当的Json格式来处理插入案例和其他操作:

object User {
  lazy val userWithIdFormat: OFormat[UserWithId] = Json.format[UserWithId]
  lazy val userWithoutIdFormat: OFormat[UserWithoutId] = Json.format[UserWithoutId]
}

然而,现在我在插入时面临一个问题,如何从UserWithoutId =>优雅地转换;生成Id后的UserwithId

所以下一步是创建这个方法:

case class User[A](_id: A, username: String)
{
  def map[B](f: A => B): User[B] = ???
}

这是我需要实施的某种Monad吗? (现在只是学习类别理论)

我想最简单的方法就是添加"初始化/清空"状态,其中案例类必须始终具有Id,并且只有在它持久化时才会有用户名。 像val initalizedUser = User(BSONObjectId.generate(), "")

这样的东西

这里最好的方法是什么? 使用类型会明智地提高性能并使域更丰富,或者只是添加一个州将使其对未来的同事更加平易近人

地图的实现是什么,这是我应该提取的内容,并将我自己的Monad实现应用于所有未来的集合?

是否更好地找到功能库来解决这个问题呢?

2 个答案:

答案 0 :(得分:0)

在我看来,使用复杂类型似乎没有必要:它不会提高性能,可能会让一些不熟悉这些概念的同事感到困惑。

我最喜欢的方法是有两个案例类,一个用于创建用户,另一个用于表示创建的实体:

case class CreateUser(username: String)

case class User(id: BSONObjectId, username: String)

这有几个好处:

  • 表达用户意图
  • 您可以在两个实体中使用不同的字段,并在CreateUser案例类
  • 上进行更复杂的验证
  • 将来会更加充实

缩小尺寸是需要再写一个案例类,但在scala中它很容易

答案 1 :(得分:0)

如果您希望坚持使用单个User案例类,则可以在将传入的JSON字符串解析为_id后将add JsValue字段转换为JSON对象,但只是在反序列化到User之前:

case class User(_id: BSONObjectId, username: String)

implicit val userReads: Reads[User] = (
  (JsPath \ "_id").read[String].map(BSONObjectID) and
  (JsPath \ "username").read[String]
)(User.apply _)

val incomingJsonStr = """{"username": "John"}"""
val json = Json.parse(incomingJsonStr)
val jsonWithId = json.as[JsObject] + ("_id" -> JsString("12345"))
val user = jsonWithId.as[User]