Making illegal states unrepresntable


#1

Hi, I’ve just started studying Scala and I have some questions regarding how to construct domain models.

I have this case class:

case class Client (id: UUID, name: String, isActive: Boolean)

I’m want to follow the idea of making illegal states unrepresentable, so I created a phantom type for id and specific types for isActive. That was the result:

sealed trait ClientState
case object Active extends ClientState
case object Inactive extends ClientState

case class Client (id: ID[Client], name: String, isActive: ClientState)

This is much better. But let’s say the name cannot be null or greater than 50. The above implementation is still not enough. I don’t know what is the recommended way to solve this in Scala. So, I decided to do the same thing I would do in F#, which is by creating a String50 type. That was the result:

class String50 private (val value: String) extends AnyVal {
  override def toString: String = value
}

// USING SCALAZ FOR VALIDATION
object String50 {
  def apply(value: String): ValidationNel[String, String50] = {
    if (value == null || value.isEmpty) {
      "$property cannot be empty".failureNel[String50]
    } else if (value.length > 50) {
      "$property cannot be longer than 50".failureNel[String50]
    } else {
      new String50(value).successNel[String]
    }
  }
}

Then, I updated the Client with String50 and created a create function:

// NOTE THAT I ADDED "NAME2" JUST TO TEST THE APPLICATIVE PATTERN
case class Client (id: ID[Client], name: String50, name2: String50, isActive: ClientState)

object Client {
  import Scalaz._

  def validateName(name: String): ValidationNel[String, String50] = {
    // transform possible validation error
    // replace "$property" with "Name" ????
    String50(name)
  }

  def validateName2(name2: String): ValidationNel[String, String50] = {
    // transform possible validation error
    // replace "$property" with "Name 2" ????
    String50(name2)
  }

  def create(name: String, name2: String): ValidationNel[String, Client] = {
    (
      validateName(name) |@|
      validateName2(name2)
    ) { (n1, n2) =>
      Client(ID(), n1, n2, Active)
    }
  }
}

This is what I have so far.

Am I on the right track? Is there a nice way to format the error messages without pattern matching?

I also would be glad if someone gives me a link of the source-code of a DDD application in Scala. I just found some small incomplete examples on GitHub that didn’t help at all =/

Thank you!


#2

You have the right idea in general, but I’m not sure scalaz ValidationNel and applicative style are warranted at this point. You could for now stick to Either[ErrorMsg, String50] and the only thing you would give up is tracking a list of all errors (well, and potentially parallelising the operation … but I doubt you need to worry about that for now). You would instead just track the first error that happened. E.g.:

// No need to `override def toString...`
class String50 private (override val toString: String) extends AnyVal

object String50 {
  type ErrorMsg = String

  def apply(string: String): Either[ErrorMsg, String50] = Either.cond(
    string.length <= 50 && !string.isEmpty,
    new String50(string),
    "String is empty or longer than 50 characters")
}

...

def create(name1: String, name2: String): Either[String50.ErrorMsg, String50] = for {
  string50Name1 <- String50(name1)
  string50Name2 <- String50(name2)
} yield Client(ID(), string50Name1, string50Name2, Active)

I think you get the benefit of for-comprehension syntax being even easier to read than applicative-style symbols. If Scala ever adds support for applicative comprehensions, even better.


#3

Thank you! I’ll consider your tips in my real implementation.