case class domain modelling - best practice

1,067 views
Skip to first unread message

Ramon Buckland

unread,
Sep 18, 2012, 5:37:45 PM9/18/12
to scala...@googlegroups.com
Hi All, 

I am  an old Java hack and have embarked on an ambitious task of writing some of my old apps in Scala.

To throw all sorts of bits into the mix, I am using Event Sourcing, CQRS and a rich DDD model.
But it is my model where I am completely getting stuck. 

I completely get that I should use case classes for the domain models. Efficiently using copy() to launch a new instance etc.
But when it comes to modelling some complex domain models, I come unstuck each time with case class inheritance.

Some have suggested I look an Type Classes / ad-hoc polymorphism and I can see how that really works to add "functionality" to a domain model.

What I am struggling with is how you efficiently model the domain members. 
What I would like to do is subtype a case class, adding a new member .. now I know I can't do that (case class inheritance) 
What I am really struggling to see is how "should" I create a Rich (160+ classes) Domain Model ? Some leaf domain classes are 6 or more with many similar traits. 
Under the case class model, I have to repeat all my fields, which doesn't feel right. 
If I don't have them as fields / def's on traits, I miss the case class magic 

Does anyone have a good example of a case class based domain model they are willing to share ? 

case class Address(street:String,town:String)

trait Party {
 def id:Long
 def version:Long
 def name:String
 def address:Address
}

object Party {
  def nextId = IdGenerator.next(classOf[Party])
}

case class Person(id:Long,version:Long,name:String,address:Address) extends Party
case class Member(id:Long,version:Long,name:String,address:Address,membershipNo:String) extends Person(....) // <<-- can't to this of course


Ramon Buckland



Michael Schmitz

unread,
Sep 18, 2012, 5:41:24 PM9/18/12
to Ramon Buckland, scala...@googlegroups.com
Can you keep person abstract and have case classes Member and NonMember?

Peace. Michael

Raoul Duke

unread,
Sep 18, 2012, 5:46:04 PM9/18/12
to scala-user
On Tue, Sep 18, 2012 at 2:41 PM, Michael Schmitz
<mic...@schmitztech.com> wrote:
> Can you keep person abstract and have case classes Member and NonMember?

alternatively, i wonder can/how does one do
delegation/forwarding/has-a in scala, yet not die from boilerplate?

Nils Kilden-Pedersen

unread,
Sep 18, 2012, 5:52:31 PM9/18/12
to Ramon Buckland, scala...@googlegroups.com
On Tue, Sep 18, 2012 at 4:37 PM, Ramon Buckland <ra...@thebuckland.com> wrote:
Does anyone have a good example of a case class based domain model they are willing to share ? 

I'm doing CQRS with ES, so the entity classes are only used to hold the necessary state and are for internal consumption only. I have only a single case of a VO with inheritance and it's modeled it like this:

case class Foo(a: A, b: B)

trait Bar extends Foo {
  def bar = //
maybe something with a or b
}
trait Baz extends Foo {
  def baz = // maybe something with a or b
}

val bar: Bar = new Foo with Bar



Ramon Buckland

unread,
Sep 18, 2012, 5:55:34 PM9/18/12
to scala...@googlegroups.com
Michael: Yes that is definitely what I would typically do.
Raould: It is the boilerplate I am die-ing from consistently .The repetition of case class fields across all "types" just smells.

Where I am struggling would be in the third tier down so ..

abstract class Foo
   case class FooSpecial extends Foo
      case class FooSpecialExtra extends FooSpecial <-- but I can't do that .. :-(  which means I need to "mixin" or something the "Extra" and then my head breaks.

Alec Zorab

unread,
Sep 18, 2012, 6:53:17 PM9/18/12
to Ramon Buckland, scala...@googlegroups.com
Without wanting to sound overly stupid, why do they need to be case classes?

Nils Kilden-Pedersen

unread,
Sep 18, 2012, 6:59:16 PM9/18/12
to Alec Zorab, Ramon Buckland, scala...@googlegroups.com
On Tue, Sep 18, 2012 at 5:53 PM, Alec Zorab <alec...@googlemail.com> wrote:
Without wanting to sound overly stupid, why do they need to be case classes?

For me, it's the copy method, which makes copying immutable nested structures easier.
 

Ramon Buckland

unread,
Sep 18, 2012, 7:04:22 PM9/18/12
to scala...@googlegroups.com
I think I asked myself the same question 10 minutes before. 

So convenience mostly. I am getting the copy method for free.  Where is copy defined  (genprod.scala ? ) ? 

  def setFirstAndLastName(firstname: String, lastname: String): DomainValidation[Person] =
    if (Option(firstname).getOrElse("").isEmpty() || Option(lastname).getOrElse("").isEmpty()) DomainError("Need to supply a first and lastname").fail
    else copy(version = version + 1, name = null, firstname = firstname, lastname = lastname).success

I suspect copy will be associated tightly with the 22 tuples.

http://www.scala-lang.org/node/7910 .. this seems on topic.


Ramon Buckland


Alec Zorab

unread,
Sep 18, 2012, 7:13:31 PM9/18/12
to Ramon Buckland, scala...@googlegroups.com

As far as I know, copy is synthetically generated by the compiler. If you're using 2.10 you might be able to do a pretty similar thing using a macro.

Daniel Sobral

unread,
Sep 18, 2012, 9:02:13 PM9/18/12
to Ramon Buckland, scala...@googlegroups.com
Err, no. Here's copy:

class Person(val id:Long,val version:Long,val name:String,val
address:Address) extends Party {
def copy(newId: Long = id, newVersion: Long = version, newName:
String = name, newAddress: Address = address) = new Person(newId,
newVersion, newName, newAddress)
}

That said, here's what I recommend:

* Do not make any class a "case class" unless it's a leaf.
* Make non-leaf classes abstract (that means Person would be abstract,
since it's non-leaf) and possibly sealed.
* If a class is both leaf and node, revise your model.
--
Daniel C. Sobral

I travel to the future all the time.

Seth Tisue

unread,
Sep 18, 2012, 9:07:17 PM9/18/12
to scala-user
On Tue, Sep 18, 2012 at 9:02 PM, Daniel Sobral <dcso...@gmail.com> wrote:
> That said, here's what I recommend:
> * Do not make any class a "case class" unless it's a leaf.
> * Make non-leaf classes abstract (that means Person would be abstract,
> since it's non-leaf) and possibly sealed.
> * If a class is both leaf and node, revise your model.

Few words, much wisdom.

--
Seth Tisue | Northwestern University | http://tisue.net
lead developer, NetLogo: http://ccl.northwestern.edu/netlogo/

Ken Scambler

unread,
Sep 18, 2012, 11:12:43 PM9/18/12
to Ramon Buckland, scala...@googlegroups.com
When I'm doing this kind of thing, I'll sketch out my domain with case classes, and expand the case classes into full traits/classes as my needs become clearer and I need finer grained control.

For instance, case classes have a lot of canned semantics that you need to think about.  Equals/hashcode is based on the value of each member in the constructor; will this always be what you want?  Does equality for a Person mean the same person (ID), the same person at a point in time (ID/version), or the exact same values (all the fields)?  It needs to be considered carefully at least; equality semantics can be fraught with difficulty in domain models.

The canned Pattern Matching might not be helpful either.  You need to count places to know what's matching to what, which when you get to more than 3 members is error-prone and borderline unusable.  You can't overload a pattern matcher on your class name, either; if you want a pattern with less arguments, you'd need to use another name.

So case classes are not appropriate for every use case, and as your classes grow and their responsibilities become clearer, they can get in the way.  If the only thing you want is a copy method, then as Daniel pointed out, writing your own is really not a big deal; using a case class doesn't pay for itself just for that.

Stefan Hoeck

unread,
Sep 19, 2012, 12:21:27 AM9/19/12
to scala...@googlegroups.com
I had similar considerations to make when designing a domain model for our database application. Instead of inheritance, I used case class
composition together with type classes:

case class Party(id:Long,version:Long,name:String,address:Address)

trait AsParty[A] {
  def getParty (a: A): Party
  def setParty (a: A, p: Party): A

  def id (a: A): Long = getParty(a).id
  def version ...
}

case class Person (party: Party)

object Person {
  implicit val PersonAsParty = new Party[Person] {
    def getParty (p: Person) = p.party
    def setParty (pe: Person, pa: Party) = pe.copy(party = pa)
  }
}

Now, this will naturally lead to a deeper nesting of case classes due to favoring composition
over inheritance. If you want to update fields in deeply nested data structures, Lenses (such as
those found in scalaz) provide a neat, composable way to do that. You would change type class
Party like so:

trait Party[A] {
  def party: Lens[A, Party] =

  def id: Lens[A, Long] = party andThen Party.id
  def version: Lens[A,Long] = party andThen Party.version
}

And at the companion object of Party you add:

import scalaz.Lens

object Party {
  val id: Lens[Party,Long] = Lens(_.id, (a,b) => a.copy(id = b))
  val version: Lens[Party,Long] = Lens(_.version, (a,b) => a.copy(version = b))
  ...
}

If you are working with deeply nested case classes, Lenses are a must. There are several
talks on Lenses in scalaz and Tony Morris wrote an article about them which was presented
at ScalaDays 2012.

You can now in a similar way define type class AsPerson

//we CAN use inheritance for code reuse in type classes
trait AsPerson[A] extends AsParty[A] {
  def person: Lens[A,Person]
  def party: Lens[A,Party] = person andThen Person.party
}

case class Member (person: Person)

object Member {
  implicit val MemberAsPerson = new AsPerson[Member] {
    def person: Lens[Member,Person] = ...
  }
}

And so on. All party related logic goes into the AsParty type class, all person related
logic goes into the AsPerson type class. Since type classes are defined as traits, they
can inherit from each other (like AsPerson extends AsParty) for maximal code reuse.
Defining all those lenses leads to some boilerplate, but this might be easily addressed
using macros in Scala 2.10 (not sure about that, I have not tried any macro hackery so far).

Regards, Stefan

Stefan Hoeck

unread,
Sep 19, 2012, 12:29:31 AM9/19/12
to scala...@googlegroups.com
Sorry, that should have been AsParty not Party:

trait AsParty[A] {

  def party: Lens[A, Party] =

  def id: Lens[A, Long] = party andThen Party.id
  def version: Lens[A,Long] = party andThen Party.version
}

By the way, if you do not like the notion, that a Person has a Party (instead of is a Party) and a Member has a Person,
you can rename them (that's what I usually do) to PersonData, PartyData, and MemberData. But that's just a matter
of taste.

Alec Zorab

unread,
Sep 19, 2012, 2:32:40 AM9/19/12
to Daniel Sobral, Ramon Buckland, scala...@googlegroups.com
Yes, but no-where in the code is that expressly written down. That's
what I meant by generated.

Ramon Buckland

unread,
Sep 19, 2012, 3:43:16 AM9/19/12
to scala...@googlegroups.com
Alec, having pondered this for a few days; writing a macro to generate me a copy method for free will be good.

I will look into it and report my findings. It would be a case of not caring if i did not have so many domain objects. Having such a large number only leads to errors under maintenance when one manually defined copy is not updated.

I want the immutability ; it fits neatly with es/cqrs.

Has anyone ventured or have good examples of macro definitions ?
--
Sent from my Android phone with K-9 Mail. Please excuse my brevity.

Ken Scambler

unread,
Sep 19, 2012, 3:50:47 AM9/19/12
to Ramon Buckland, scala...@googlegroups.com
Has anyone ventured or have good examples of macro definitions ?

The current implementation of macros can't generate classes or methods.  It's planned for a future release.

Matthew Pocock

unread,
Sep 19, 2012, 6:43:11 AM9/19/12
to Stefan Hoeck, scala...@googlegroups.com
On 19 September 2012 05:29, Stefan Hoeck <efasc...@gmail.com> wrote:

By the way, if you do not like the notion, that a Person has a Party (instead of is a Party) and a Member has a Person,
you can rename them (that's what I usually do) to PersonData, PartyData, and MemberData. But that's just a matter
of taste.


It depends how you think about your domain modelling. One school of thought says that if it is the same thing, you should use the same instance. Another says that each context in which you wish to independently talk about something, you should have a distinct instance representing it specifically in that context. These 'in a context' things are called qua entities, and can be used to capture extra stuff about something that only pertains to it fulfilling a particular role or function, or acting in a specific capacity or context. You could argue that when a person is *acting as* a party, that there is stuff you say about them *as a party* that is not intrinsic only to that person and which has no meaning outside of that person *acting as* that party. They may be in several contracts, each one acting as a distinct party, and in each of those situations, that same person needs different information associated with that particular *acting as* a party.

So, to make the model feel more natural, instead of reading it as "Person has a Party", read it as "Person acting as a Party". In ontology modelling, these things are called 'qua entities', and you'd say 'Person qua Party'.

World English Dictionary
qua  (kweɪ, kwɑː) 
 
— prep
in the capacity of; by virtue of being
 
[C17: from Latin, ablative singular (feminine) of qui  who]
 
--
Dr Matthew Pocock
Integrative Bioinformatics Group, School of Computing Science, Newcastle University
skype: matthew.pocock
tel: (0191) 2566550

Stefan Hoeck

unread,
Sep 19, 2012, 6:58:51 AM9/19/12
to scala...@googlegroups.com, Stefan Hoeck
Type classes do exactly that: Describe how a Person acts as a Party. With 'Person has a Party' I was referring to composition vs. inheritance:

  case class Person(...) extends Party

describes (in the OO sense) how a Person IS also a Party while

  case class Person(party: Party)

looks more like a Person consisting of (aka having) a Party. The latter might seem a bit awkward, therefore the renaming to PersonData, PartyData. Then we can say, the data related to a Person consists of the data related to a Party, plus (possibly) some additional fields.

So, in your words: When using inheritance, this is the mechanism to describe 'Person qua Party', while when using composition we use type classes to achieve the same goal.

Regards, Stefan

Alec Zorab

unread,
Sep 19, 2012, 7:03:19 AM9/19/12
to Matthew Pocock, Stefan Hoeck, scala...@googlegroups.com
Thinking about it, what you want is pretty similar to Jason Zaugg's lens implementation in macrocosm [1]. I think you can extend calls to a hypothetical copy function so that they forward to a lens...

Or something, I haven't thought about it that much yet.

Reply all
Reply to author
Forward
0 new messages