Last active
August 29, 2015 14:18
-
-
Save oxbowlakes/722913b9d05c6b233221 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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