Класс Case с Id с реактивным Mongo, должен быть необязательным или обязательным - PullRequest
0 голосов
/ 04 мая 2018

Я строю следующий API для пользователей в игре Scala:

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 => UserwithId после того, как Id был сгенерирован

Итак, следующим шагом было создание этого метода:

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

Это какой-то тип монады, который мне нужно реализовать? (Сейчас изучаю теорию категорий)

Я полагаю, что самый простой способ - это просто добавить "инициализированное / пустое" состояние, в котором класс Case должен всегда иметь Id, и только когда он сохраняется, он будет иметь имя пользователя. Что-то вроде val initalizedUser = User(BSONObjectId.generate(), "")

Какой здесь лучший подход? Использование типов улучшит производительность и сделает домен более насыщенным, или просто добавление состояния сделает его более доступным для будущих коллег

Какой должна быть реализация для map, это то, что я должен извлечь и иметь свою собственную реализацию Monad, которая будет применяться для всех будущих коллекций?

Лучше обратиться к функциональной библиотеке, чтобы решить эту проблему, или еще нет?

Ответы [ 2 ]

0 голосов
/ 06 мая 2018

Если вы хотите использовать один класс User, вы можете добавить поле _id к объекту JSON после того, как входящая строка JSON была проанализирована в JsValue, но перед десериализацией в 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]
0 голосов
/ 05 мая 2018

По моему мнению, использование сложных типов здесь не нужно: это не улучшит производительность и может запутать некоторых коллег, которые не знакомы с концепциями.

Мой любимый подход здесь состоит в том, чтобы иметь два класса case, один для создания пользователя и один для представления созданного объекта:

case class CreateUser(username: String)

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

Это имеет несколько преимуществ:

  • выражает намерения пользователя
  • вы можете иметь разные поля в обеих сущностях и более сложную проверку в CreateUser классе дела
  • будет более перспективным

Сокращение состоит в том, что требуется написать еще один класс дел, но в scala это довольно просто

...