Skip to content

Instantly share code, notes, and snippets.

@programaker
Last active March 7, 2025 12:47
Show Gist options
  • Save programaker/09de2a15cfa587cdbe78fb724d33e6c2 to your computer and use it in GitHub Desktop.
Save programaker/09de2a15cfa587cdbe78fb724d33e6c2 to your computer and use it in GitHub Desktop.

Revisions

  1. programaker revised this gist Oct 21, 2021. 1 changed file with 3 additions and 16 deletions.
    19 changes: 3 additions & 16 deletions exportClause.scala
    Original file line number Diff line number Diff line change
    @@ -15,20 +15,12 @@ import io.circe.syntax.*

    import java.util.UUID

    // the pure domain concept
    // the pure domain concept/entity
    // the base data is declared here, and only here
    case class Frunfles(a: String, b: Int, c: Boolean)
    object Frunfles:
    given Encoder[Frunfles] = deriveEncoder

    // new entity to be persisted without id
    // no need to make the id optional
    // no need to duplicate Frunfles fields
    case class NewFrunfles(data: Frunfles):
    export data.* // super-powered delegation!
    object NewFrunfles:
    given Encoder[NewFrunfles] = Encoder.instance(_.data.asJson)

    // persisted entity containing an id
    // no need to make the id optional
    // no need to duplicate Frunfles fields
    @@ -40,20 +32,15 @@ object PersistedFrunfles:

    // type-safe operations
    // explictly stating which operations require an entity with id
    def insert(f: NewFrunfles): PersistedFrunfles = ???
    def insert(f: Frunfles): PersistedFrunfles = ???
    def update(f: PersistedFrunfles): PersistedFrunfles = ???
    def delete(f: PersistedFrunfles): Unit = ???

    // composition over inheritance, more FP-ish
    val f = Frunfles("Meh", 42, true)
    val nf = NewFrunfles(data = f)
    val pf = PersistedFrunfles(id = UUID.randomUUID().nn, data = f)

    // exported fields available without calling `.data`
    val a1 = nf.a
    val b1 = nf.b
    val c1 = nf.c
    ///
    // exported fields available without calling `.data`. Happy demeter!
    val id = pf.id
    val a2 = pf.a
    val b2 = pf.b
  2. programaker revised this gist Sep 14, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion exportClause.scala
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    /*
    * The good practices say we shouldn't use the same type for everything (domain, json, persistence, ...), but
    * most of the time those types are very similar - if not exactly identical - creating a lot of dupliation.
    * most of the time those types are very similar - if not exactly identical - creating a lot of duplication.
    *
    * Inheritance to the rescue? Nah... not very FP and even the OOP folks are aware that we should
    * prefer composition over it. But composition has it's own challenges (Law of Demeter for instance).
  3. programaker created this gist Sep 13, 2021.
    60 changes: 60 additions & 0 deletions exportClause.scala
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,60 @@
    /*
    * The good practices say we shouldn't use the same type for everything (domain, json, persistence, ...), but
    * most of the time those types are very similar - if not exactly identical - creating a lot of dupliation.
    *
    * Inheritance to the rescue? Nah... not very FP and even the OOP folks are aware that we should
    * prefer composition over it. But composition has it's own challenges (Law of Demeter for instance).
    *
    * Let's see how the new `Export Clauses` in Scala 3 can help with that:
    * https://docs.scala-lang.org/scala3/reference/other-new-features/export.html
    * */

    import io.circe.Encoder
    import io.circe.generic.semiauto.*
    import io.circe.syntax.*

    import java.util.UUID

    // the pure domain concept
    // the base data is declared here, and only here
    case class Frunfles(a: String, b: Int, c: Boolean)
    object Frunfles:
    given Encoder[Frunfles] = deriveEncoder

    // new entity to be persisted without id
    // no need to make the id optional
    // no need to duplicate Frunfles fields
    case class NewFrunfles(data: Frunfles):
    export data.* // super-powered delegation!
    object NewFrunfles:
    given Encoder[NewFrunfles] = Encoder.instance(_.data.asJson)

    // persisted entity containing an id
    // no need to make the id optional
    // no need to duplicate Frunfles fields
    case class PersistedFrunfles(id: UUID, data: Frunfles):
    export data.*
    object PersistedFrunfles:
    // easy to avoid exposing the id in Json
    given Encoder[PersistedFrunfles] = Encoder.instance(_.data.asJson)

    // type-safe operations
    // explictly stating which operations require an entity with id
    def insert(f: NewFrunfles): PersistedFrunfles = ???
    def update(f: PersistedFrunfles): PersistedFrunfles = ???
    def delete(f: PersistedFrunfles): Unit = ???

    // composition over inheritance, more FP-ish
    val f = Frunfles("Meh", 42, true)
    val nf = NewFrunfles(data = f)
    val pf = PersistedFrunfles(id = UUID.randomUUID().nn, data = f)

    // exported fields available without calling `.data`
    val a1 = nf.a
    val b1 = nf.b
    val c1 = nf.c
    ///
    val id = pf.id
    val a2 = pf.a
    val b2 = pf.b
    val c2 = pf.c