Generic implemention for common case classes


#1

Hi, I just started learning Scala and I’m trying to create specific ID types for each entity of my domain. This is what I have so far:

trait EntityID {
  val value: UUID
}

case class ClientID private (value: UUID) extends EntityID

case object ClientID {

  def createNew: ClientID = ClientID(UUID.randomUUID())

  def createFrom(str: String): Try[ClientID] = {
    for {
      id <- Try(UUID.fromString(str))
      clientId <- Try(ClientID(id))
    } yield clientId
  }
}

case class UserID private (value: UUID) extends EntityID
....

It works, but it’s not the ideal because I would have to copy and paste the whole logic for other implementations of EntityID.

I was wondering if there’s a way to reuse createNew and createFrom through all my types by using some kind of generics or whatever.

Any tips?

Thank you!


#2

Hello,

How about something like:

trait EntityID { val value: UUID}

trait EntityIdFactory[E <: EntityID] {

  • def createFrom(uuid: UUID): E def createNew: E =
    createFrom(UUID.randomUUID()) def createFrom(str: String): Try[E] = {
    for { id <- Try(UUID.fromString(str)) entityId <-
    Try(createFrom(id)) } yield entityId }*
    }

case class ClientID private (value: UUID) extends EntityIDcase object
ClientID extends EntityIdFactory[ClientID] {

  • def createFrom(uuid: UUID): ClientID = ClientID(uuid)}*

Best, Oliver


#3

That’s what I was looking for!

Thank you!


#4

In Scala, it’s better to create a single ID wrapper type with a phantom type variable and reuse that everywhere. Here’s what I mean:

import java.util.UUID
import scala.util.Try

/* We don't need pattern matching, so make it a normal class. But make
   the `toUUID` member field public so we can convert the ID back to its
   contained UUID at any time. */
class ID[Tag] private (val toUUID: UUID) extends AnyVal

object ID {
  def apply[Tag](): ID[Tag] = new ID(UUID.randomUUID())

  def apply[Tag](string: String): Try[ID[Tag]] =
    Try(UUID fromString string) map { uuid =>
      new ID(uuid)
    }
}

Usage (paste in REPL):

case class Person(id: ID[Person], name: String)
val bob = Person(ID(), "Bob")

Explanation:

You’ll notice that the type parameter Tag is actually never used in the type definition. This pattern is called a phantom type and is used to distinguish between values which have the same underlying type but a different ‘tag’ (the phantom type).

Now you can reuse the same ID[Tag] class for many different domain types and have them all be safe and distinct from each other at the type level, without repetition.

P.S., the pattern case class Foo private (something: Int) extends AnyVal does not work like you think it does: because Scala auto-generates a Foo.apply(something: Int): Foo method, users can still create instances outside of your control. That’s also another reason I used a normal class instead of a case class. In general it’s a good idea to use case classes carefully–they auto-generate a lot of things ( https://stackoverflow.com/a/30530447/20371 ) you may not need.


#5

Thank you for the tip @yawaramin! I’ll use this approach!