Back in April of this year I took over the development of a client's Grails application. The app is simple:

  1. Business ideas flow into it
  2. The ideas are displayed for people to vote on.
  3. The top 3 voted ideas during a given week are deemed the winners. (Still working on this one)

Initially, once the weekly competition completed, the winning ideas became inaccessible because the view was designed to render only the ideas for the current competition. Yeah, talk about a brain fart.

My solution was to simply keep track of all winning ideas using a new domain model. Besides, the previous and current competition stats were kept in two properties of the same domain class. It was a putrid situation in need of venting.

Great!, I thought. I know exactly what to do. And so I proceeded with a new model similar to this:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Competition implements Serializable {
    Date startDate
    Idea idea
    Integer votes

    static mapping {
        id: composite: ['startDate', 'idea']
    }

    /*
     * The hashCode() and equals() dance.
     */
}

Yeah... the problem should have been obvious to me, but it wasn't. Since each competition is identified by its start date, and each idea can be in a competition only once, I figured this was a logical solution.

And in fact, it works just fine. Recording a vote in a competition is simple:

1
2
3
4
5
def example = new Competition(idea: idea, startDate: startDate)
def competition = Competition.get(example) ?: competition 

competition.votes += 1 // or -= 1 for a down-vote
competition.save()

An existing Competition is used if possible, otherwise a new instance is created. No problem. Except... that a competition is supposed to be able to have multiple ideas. And while the Competition model does work, it's design doesn't reflect its purpose. If my design was by intention for performance or anoother technical reason, I would be content. But the fact remains that I blatantly overlooked the concept of a competition and instead fixated on the implementation. I'm not even going to discuss what I did with the services. I'd clear the room.

The code-smell remained elusive until the day I began working on a new feature and needed a list of the competitions; meaning a list of competition dates.

1
2
3
4
5
def competitionDates = Competition.withCriteria {
    projections {
        distinct('startDate')
    } 
}

I had to use DISTINCT.

My days supporting horrid Microsoft Access databases taught me that having to use DISTINCT can be a sign of a... ignorantly de-normalized database. (I resisted the urge to use a more descriptive four-letter word)

Redemption

If you're like me, software architecture just rocks your socks off. When a particular problem enters my mind, there's no shutting it up until I solve it. It doesn't care that I go hungry, or that my eyes itch, or that I really need to visit the toilet. It demands my full attention; Emmanuel be damned.

OK, maybe I exaggerated a bit. In this case the solution is rather simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Competition {
    Date startDate

    static hasMany = [ideas: CompetitionIdea]

    static mapping {
        id generator: 'assigned', name: 'startDate'
    }
}

class CompetitionIdea {
    Idea idea
    Integer votes

    static belongsTo = [competition: Competition]

    static constraints = {
        idea unique: 'competition'
    }
}

Now a competition can indeed have multiple ideas.

Aaaahhhhh. Now I can relieve myself in peace

Lesson learned

I don't recall my thought the process when developing the Competition domain model. But because I'm a SQL aficionado, I can only assume that my thinking was too data-centric, too early. By focusing on how to store the data I didn't give enough attention to the concepts the data represents. That's something for me to watch out for in the future.

Speaking of software architecture, have you read any of The Architecture of Open Source Applications books? As of now there are three books and a fourth is on the way. Written by the developers themselves, the books cover the design of various open source applications. They make for a good read.