Skip to content

Instantly share code, notes, and snippets.

@michaeldiamant
Created January 10, 2016 19:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save michaeldiamant/7efbac9a9735a74abd96 to your computer and use it in GitHub Desktop.
Save michaeldiamant/7efbac9a9735a74abd96 to your computer and use it in GitHub Desktop.
Use dependent types to represent a relationship between a command and its possible failures and successful outcomes (i.e. events). This representation encodes the relationship into the data types rather than a service-like interface.
object E1 {
// Encoding a 1:1 command to event example
sealed trait Event
case class E1(i: Int) extends Event
case class E2() extends Event
sealed trait Intent[Z <: Event] {
type Out = Z
}
case class C1(id: Int) extends Intent[E1]
case class C2(id: Int) extends Intent[E1]
case class C3(id: Int) extends Intent[E2]
def handle[Z <: Event](i: Intent[Z]): i.Out = i match {
case C1(id) => E1(id)
case C2(id) => E1(id)
case C3(id) => E2()
}
val r1: E1 = handle(C1(5))
val r2: E2 = handle(C3(4))
}
object E2 {
// Encoding 1:1 relationship with added capability of expressing multiple
// rejects for a particular intent by adding specific sealed trait.
// The shortcoming here is that a reject common to all intents cannot be
// shared. The reject would need to be duplicated for all reject traits.
sealed trait ApiReject
sealed trait C1ApiReject extends ApiReject
case class Duplicated(i: Int) extends C1ApiReject
case class InvalidName(i: Int) extends C1ApiReject
sealed trait C2ApiReject extends ApiReject
case class NotFound(i: Int) extends C2ApiReject
sealed trait Intent[R <: ApiReject] {
type Reject = R
}
case class C1(i: Int) extends Intent[C1ApiReject]
case class C2(i: Int) extends Intent[C2ApiReject]
def handle[R <: ApiReject](c: Intent[R]): List[c.Reject] = c match {
case C1(x) => Duplicated(x) :: InvalidName(x) :: Nil
case C2(x) => NotFound(x) :: Nil
}
val r1: List[ApiReject] = handle(C1(1))
val r2: List[C1ApiReject] = handle(C1(1))
// val r2: List[C2ApiReject] = handle(C1(1)) // fails
}
object E3 {
// Advancing E2 to support sharing a reject across multiple intents while
// still allowing each intent (i.e. command) to define a set of possible
// rejects as a coproduct
import shapeless._
sealed trait ApiReject
case class Duplicated(i: Int) extends ApiReject
case class InvalidName(i: Int) extends ApiReject
case class NotFound(i: Int) extends ApiReject
type C1ApiReject = Duplicated :+: InvalidName :+: CNil
type C2ApiReject = NotFound :+: CNil
// Will never be used because it does not define a Rejectable instance
type C3ApiReject = String :+: CNil
sealed trait Rejectable[A]
implicit object C1Rejectable extends Rejectable[C1ApiReject]
implicit object C2Rejectable extends Rejectable[C2ApiReject]
sealed trait Intent[R <: Coproduct] {
type Reject = R
type Foo = Rejectable[R]
}
case class C1(i: Int) extends Intent[C1ApiReject]
case class C2(i: Int) extends Intent[C2ApiReject]
case class C3(i: Int) extends Intent[C3ApiReject]
def handle[R <: Coproduct : Rejectable](c: Intent[R]): c.Reject = c match {
case C1(x) => Coproduct[C1ApiReject].apply(Duplicated(x))
case C2(x) => Coproduct[C2ApiReject](NotFound(x))
case C3(x) => Coproduct[C3ApiReject]("")
}
object f extends Poly1 {
implicit val caseD = at[Duplicated](e => e.i)
implicit val caseX = at[InvalidName](e => e.i)
}
// val r1: List[ApiReject] = handle(C1(1))
val r2: C1ApiReject = handle(C1(1))
val res: Int = r2.fold(f)
// val r3: List[C1ApiReject] = handle(C3(1))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment