Fork me on GitHub

1 Mar 2009

Hibernate POGOs, Immutability and Encapsulation

I had a confusing experience this morning. I'm working on a simple web based game as a learning exercise.

I use a custom tag to determine if the currently logged in user is the active player (i.e. the player whose turn it is).

  <g:isNextPlayer>
<!-- display something only active player should see -->
</g:isNextPlayer>

The tag is used at various places in the page - displaying an "it's your turn" message, switching on bits of script, rendering buttons, etc. Suddenly I found the last couple of calls to the tag in a page weren't rendering the tag body.

The tag uses a transient property on the Game domain class that looks like this:

  Player getNextPlayer() {
return players.empty ? null : players[turn % players.size()]
}

The variable turn is simply an integer that gets incremented when a turn completes.

After poking around with debug logging I figured out that the implementation of getNextPlayer was fine and the value of turn wasn't being accidentally changed but the collection property players was getting re-ordered. What I'd done was introduce some code mid-way in the page that displays the players' names and scores - ordered by score.

  <g:each var="player" in="${gameInstance.players.sort { -it.score }}">
<tr>
<td>${player.user.username}</td>
<td>${player.score}</td>
</tr>
</g:each>

I had assumed when I did this that Collection.sort(Closure) would return a copy (which is what the docs state - not that I'd checked). However, it seems that is only the case if the collection it's called on is not a List. On List instances the method acts as a mutator and does an in-place sort.

  def c = [1,2,3] as Set
def s = c.sort { it }
assert !(c.is(s))

Remove the 'as Set' and the assertion fails.

Although I'd argue that Groovy's sort implementation doesn't exactly stick to the principal of least surprise that's not really what concerns me. In my domain it doesn't make sense for me to be able to mutate the players property of Game. Once a game is created the players are fixed, they can't leave, can't take turns out of order. The state of an individual Player can change - his score can change, for example, but not the collection property of Game. In a regular non-persistent class this sort of invariant would be enforced by overriding the getter and setter:

  private List players
List getPlayers() { players.asImmutable() }
void setPlayers(List players) {
this.players = []
this.players.addAll players
}

If my Game class was implemented that way I'd have got an UnsupportedOperationException from the sort call. If nothing else it would have saved me 5 minutes of "WTF?" and 10 minutes of trying to figure out if it was something to do with the second-level cache. Unfortunately, this technique doesn't quite work satisfactorily in a GORM domain class though. For a start declaring the property as private will result in a compilation failure - 'The field 'players' is declared multiple times.' Declaring the property without private gets you past the compilation phase but Grails 'addTo*' dynamic method isn't bound.

One of the things that's always nagged at the back of my mind when using GORM/Hibernate is the fact that all to often the domain classes end up resembling the anæmic domain model anti-pattern even to the extent of a design something like the cautionary tale of POTS. I like to put a certain amount of business logic in my domain classes and give them a reasonably rich API for managing their state without violating encapsulation but there's always an uncomfortable feeling that properties that really should be part of an object's private state are hanging out in the wind so that the persistence framework can access them.

1 comment:

JustinForder said...

Thanks for the Pots link - I hadn't seen it before, though like one of the commenters on that post, "I think I've met Pots and many of his family".

-- Justin Forder