Skip to content

Instantly share code, notes, and snippets.

@oxbowlakes
Last active August 29, 2015 14:18
Show Gist options
  • Save oxbowlakes/722913b9d05c6b233221 to your computer and use it in GitHub Desktop.
Save oxbowlakes/722913b9d05c6b233221 to your computer and use it in GitHub Desktop.
package gsa.domain.scala.dan
import scalaz.Leibniz.===
import scalaz._
import Scalaz._
import gsa.shared.scala._ //gets you Date, Instant types
/**
* We wish to use a DomainService to get a snapshot of our domain (e.g. Books and SubBusinessUnits).
* There are two drivers for this:
* - Domain entities from one snapshot are not "compatible" with those from another
* at the type level
* - We wish different domain services to contain different information in (for example) a Book
* and yet the usage to remain the same
*/
object BitemporalDomainExample extends App {
/* Users should not need to know about this type, nor its type parameters. Users will have
an instance of a specific DomainService, `d` and will use the type `d.SubBusinessUnit`
which has no type parameters
*/
trait SBU0[DS <: DomainService, SS <: DS#Snapshot0] extends DomainServiceAware[DS, SS] { self =>
def name: String
final def books: Set[snapshot.Book] = snapshot.booksIn(eq(self))
private def eq: self.type === snapshot.SubBusinessUnit = Leibniz.force[⊥, ⊤, self.type, snapshot.SubBusinessUnit]
}
/* Users should not need to know about this type, nor its type parameters. Users will have
an instance of a specific DomainService, `d` and will use the type `d.Book`
which has no type parameters
*/
trait Book0[PS <: DomainService, SS <: PS#Snapshot0] extends DomainServiceAware[PS, SS] {
def subBusinessUnit: snapshot.SubBusinessUnit
}
/* Users should not need to be concerned about this type */
abstract class DomainServiceAware[DS <: DomainService, SS <: DS#Snapshot0] {
final type D = DS
final type S = SS
val service: D
val snapshot: S
}
/* Users will use specific implementations of this interface to get their snapshots */
trait DomainService { self =>
type Snapshot <: Snapshot0
// this would get you an arbitrary snapshot
def someSnapshot(asOf: Instant, effective: Date): effect.IO[Snapshot]
abstract class Snapshot0 {
def books: Set[Book]
/* abstract type parameters to be implemented by implementors of this trait */
type Book <: Book0[self.type, Snapshot0.this.type]
type SubBusinessUnit <: SBU0[self.type, Snapshot0.this.type]
implicit def equalSBU: Equal[SubBusinessUnit] = ???
def booksIn(sbu: SubBusinessUnit): Set[Book] = books.filter(b => b.subBusinessUnit === sbu)
}
}
/* Example DomainService where books and SubBusinessUnits have a mnemonic. Different domain servcies may extend this to
other properties
*/
class ExampleDomainService1 extends DomainService {
type This = this.type
type Snapshot = Snapshot1
abstract class Snapshot1 extends Snapshot0 {
type SS = Snapshot1.this.type
type Book = Book1
type SubBusinessUnit = SBU1
case class Book1(name: String, subBusinessUnit: SubBusinessUnit) extends Book0[This, SS] { val service: D = ExampleDomainService1.this; val snapshot: S = Snapshot1.this }
case class SBU1(name: String, mnemonic: String) extends SBU0[This, SS] {
val service: D = ExampleDomainService1.this
val snapshot: S = Snapshot1.this
}
}
def someSnapshot(asOf: Instant, effective: Date)= scalaz.effect.IO {
new Snapshot1 {
// in reality you might derive the data here from a Database query
def books: Set[Book] = Set(Book1("foo", SBU1("sbu", "f")))
}
}
}
object Usage {
val ps: ExampleDomainService1 = new ExampleDomainService1
val snap: ps.Snapshot /* type ascription not necessary here, included for clarity */
= ps.someSnapshot(asOf = now, effective = yesterday()).unsafePerformIO()
val anSbu: snap.SubBusinessUnit /* type ascription not necessary here, included for clarity */
= snap.books.head.subBusinessUnit
println(anSbu.name)
// The mnemonic property is available at compile-time (though not declared on the base trait)
println(anSbu.mnemonic)
// this would have been retrieved some other way
// can either pass anSbu to the snapshot in order to get the books:
val `books in that SBU` = snap.booksIn(anSbu)
// or I can get the books using the accessor on anSbu directly:
val `books in that SBU by direct access` = anSbu.books
import snap._
anSbu.books.head.subBusinessUnit === anSbu
val snap2 = ps.someSnapshot(asOf = now, effective = yesterday())
// snap2.booksIn(anSbu)
// Error:(78, 19) type mismatch;
// found : gsa.domain.scala.dan.BitemporalDomainExample.Usage.anSbu.type (with underlying type _15.snapshot.SBU forSome { val _15: gsa.domain.scala.dan.BitemporalDomainExample.Usage.snap.Book })
// required: gsa.domain.scala.dan.BitemporalDomainExample.Usage.snap2.SBU
}
//run it
Usage
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment