0

I'm building the following api for Users in play scala:

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

The issue is that when the client sends a request in order to store/create a new user the _id is not present.

One viable solution seemed to be:

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

However, it seemed a bit annoying to check for the option field not only for inserting the user, but for the other CRUD operations.

One suggestions that I got was to have two types:

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

or

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

I chose the latter implementation as it seemed appropiate. I created the proper Json Formats to deal with both cases insertion and the other operations:

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

However, now I faced an issue when Inserting, how to convert elegantly from UserWithoutId => UserwithId after the Id has been generated

So the next step was to create this method:

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

Is this some type of Monad that I need to implement? (Just learning category theory now)

I guess the easiest way is to just add an "initialized/empty" state where the case Class is required to always have an Id, and only when it's persisted it will have username. Something like val initalizedUser = User(BSONObjectId.generate(), "")

What is the best approach here? Are using Types would improve performance wise and make the domain more rich, or just adding a state will make it more approachable for future colleagues

What would be the implementation for map, is this something I should extract and have my own Monad implementation to be applied for all future collections?

Is it better to reach for a functional library to solve this issue or not yet?

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
raul782
  • 479
  • 1
  • 5
  • 15

1 Answers1

0

In my opinion using complex types seems unnecessary here: it won't improve performance and may confuse some colleagues who are not familiar with the concepts.

My favorite approach here is to have two case classes, one for user creation and one to represent the created entity:

case class CreateUser(username: String)

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

This has several advantages:

  • expresses the user intents
  • you can have different fields in both entities and more complex validation on the CreateUser case class
  • will be more future proof

The downsize is that it requires to write one more case class, but it's quite easy in scala

vdebergue
  • 2,354
  • 16
  • 19
  • I like your proposal but since I’m wondering if this would scale with 200 tables, porting an app and exploring if slick or mongo is the best choice – raul782 May 05 '18 at 20:05
  • I think even while this might not scale well, It's the best starting point. – raul782 May 09 '18 at 07:10