views:

133

answers:

2

In my web application authorized user has at least 4 "facets": http session related data, persistent data, facebook data, runtime business data.

I've decided to go with case class composition instead of traits for at least two reasons:

  • traits mixing can cause name clashes
  • i want the free case class goodies like pattern matching and copy method

I'd like to know experienced scalaists opinions on this subject. It looks like traits and/or cake pattern should be suitable for such tasks but as i've mentioned above there are problems... Its obvious that not only i want to implement it fast and easy but also to understand it in depth for using in future.

So does my decision have any flaws and misunderstanding or is it right? Related code looks like this:


case class FacebookUserInfo(name: String, friends: List[Long])
case class HttpUserInfo(sessionId: String, lastInteractionTime: Long, reconnect: Boolean)
case class RuntimeQuizUserInfo(recentScore: Int)
trait UserState {
  def db: User
  def http: HttpUserInfo
}

case class ConnectingUser(db: User, http: HttpUserInfo) extends UserState
case class DisconnectedUser(db: User, http: HttpUserInfo, facebook: Option[FacebookUserInfo]) extends UserState
case class AuthorizedUser(db: User, http: HttpUserInfo, facebook: FacebookUserInfo,
                          quiz: RuntimeQuizUserInfo) extends UserState
+2  A: 

I think the answer is easy: Go with inheritance, as long as everything really "belongs" to your object, as long as everything is in the same "problem domain".

The intention of the cake pattern is to factor out parts of the object that are somehow required, but are not really part of it, e.g. a strategy, a decoration, a configuration, a context etc. Logging would be a typical example. Generally we're talking about situations you don't want to "hard-wire" things, e.g. cases you would consider to use a DI framework (like Guice or Spring) in Java. See http://jonasboner.com/2008/10/06/real-world-scala-dependency-injection-di.html for a good example.

A question that often helps to decide what to do is: "How could I test the object behavior?". If you find it difficult to set up a proper test environment, chances are that you should decouple things, and that means DI, which can be often realized conveniently with the cake pattern.

Landei
The 'Cake pattern' term is usually used in context of services, not data, i added it to my question mostly because of the 'name clash' problem which is common to all kinds of trait mixing.
Oleg Galako
And also i don't fully understand what you mean by 'go with inheritance'? The question is what is better: trait mixing (two level model) or composition. I don't like inheritance (many level model) because it makes things heavy. Light weight interfaces are always preferable for me since they make all contexts simpler (no extra data to watch for).
Oleg Galako
+1  A: 

The third option is to use implicit converters aka "pimp my library," which probably is not necessary since you have the control of the code.

It all depends on how opaque (or transparent) you want to be about certain aspect of your object. You can pretend its a plain old case class to the rest of the world, but internally make it do extra work by using implicits. The use of case class to hold data is appropriate but I also feel that it's awkward to represent the same object using three classes (ConnectingUser, DisconnectedUser, AuthenticatedUser) depending on her state of authentication.

For the UserState, you could provide an extractor so it behaves like a case class:

object UserState {
  def unapply(state: UserState) = Some(state.db, state.http)
}

this can be used in a match state as follows:

val user = ConnectingUser(User(), HttpUserInfo("foo", 0, false))
user match {
  case UserState(db, http) => println(http)
}
eed3si9n
Thank you for your answer. I'm currently going on with the case classes (since there is also a problem with modifying obects created with traits mixing, i mean the way copy method of case classes does, since you have to use reflection or implicitly define construction method for each combination of traits). So in my case implicit conversion to match certain interfaces can be useful.
Oleg Galako