Please read this first if you are a TroopTrack user
My blog is read, not only by coders like me, but also by a pretty decent number of TroopTrack users. I just put them through a fairly horrible major release, after which I swore I would never do an X.0 release again, so I need to start this post with an assurance. Please don’t interpret this post as a sign of things to come regarding TroopTrack. This is merely a hypothetical discussion to help me understand a concept being advocated by a fellow programmer whom I respect but whose ideas have troubled and frustrated me for the last year or so. There will NEVER be another major TroopTrack release. All changes will be incremental as previously promised.
Throwing the gauntlet down with Uncle Bob
This post is a follow on to an earlier post which was a response to a post by Uncle Bob Martin, author of Clean Code and other noteworthy books about software development. There were some tweets involved as well, but to make things easier and get you caught up if you haven’t been following along, just read these posts first:
- Bob Martin advocates for a use-case-centric architecture in “No DB”
- I ask for a working example in “Dear Uncle Bob: Please, please show me the code”
- Bob kindly gives me one in “Aldos Response“
Perhaps I Got It More than I Knew
About a year ago I embarked on a major re-design of TroopTrack. One of the changes I made was to group functionality in the five major uses cases of my software. This grouping is evident in the UI:
And in the code:
I show here the controllers folder, but if you looked in the views folder you would see something very similar – I’ve namespaced my application based on the major use cases of my system. Like FitNesse, there is plenty of cruft in there and, since it’s a rails app, things are pretty much glued to HTTP.
This may seem like a relatively small change, but it has had a dramatic improvement on TroopTrack overall. For one thing, TroopTrack’s “first glance” grade improves significantly. It’s a lot easier to see what TroopTrack does than pre-namespacing, when everything was just lumped in one sloppy bucket. For another thing, this improves separation of concerns at a higher level and lets me think about the same bits of data differently in different contexts.
Let’s Be Honest Here
Namespacing TroopTrack this way is nice, but let’s not call it a use-case-centric architecture. It’s not. It’s simply more use-case centric than it was. Whoop-dee-doo.
One of the frustrations I encountered in TroopTrack 3.0 was that I couldn’t namespace models. The “user” I deal with in Communicate is rather different than the “user” I deal with in Manage. Perhaps it would be more accurate to say that I wish they were different, in spite of the fact that underneath it all is a database structure that they share. You see, in Communicate I really only care about a few things related to the user:
- Their name (not the parts of it)
- Their email address
- Mailing lists they belong to
- Their privileges related to editing web sites, uploading documents, sending messages, and sharing links
In Manage, I have a much larger set of stuff I am interested in:
- First name
- Middle name
- Last name
- Household they belong to
- When they joined scouts
- What scouting unit they belong to
- Their avatar image
- Medical information about them
- All their privileges
- Their access level
It bothered me that I couldn’t namespace my models the same way I namespaced my views and controllers. Why can’t I have a different set of validations, accessible attributes, methods, etc for a different high level use case?
Note: this is a rhetorical question. In rails, you can in fact accomplish at least some of these things by adding logic to your validations and adding :as => namespace to your accessible attributes. But it’s not what I would call clean and, since all this stuff would wind up being in the same model file as every other namespace it would murkify intent in significant ways.
I’m not 100% sure what the controllers here would look like. It might be nice to un-marry them from HTTP, but the pragmatic part of me is not totally sold on that. I might just call them HTTP controllers or something, just so there is semantic congruence between what they are called and what they are.
I like the convention for naming views in a way that shows their action, format, and the language used to implement them, like so:
It would be important to be able to disconnect the views and controllers from HTTP if needed, so that I could continue to use them with other protocols. The pragmatist in me says “leave that step until you need it, just leave room for it for now”, and I agree with him at the mo. It would also be important to put some thought into keeping the views from being too married to models, but I think this might have more to do with the models than the views.
Neither of these things are a HUGE departure from things we’ve seen before. In fact, you could probably take different parts of Rails and bend them to your will to support an approach like this.
The problem is, you could go this far and still end up with an architecture that is fundamentally data-centric. In order to be truly use-case centric, you need to have models that reflect the use case as much as your views and controllers do.
The Models are the Big Deal
For the most part, models tend to be 1:1 matches to the way the data is stored. A model’s class usually represents a table and an instance is a row. The model you build just adds behavior on top of that data. This is convenient, especially in a framework like rails where active record adds some magic for all sorts of stuff, but the 1:1 mating of a model to a table is limiting.
Ultimately, it might be valuable to break both this 1:1 mapping and the tight database coupling to allow for a model to be “storable” in multiple ways. I can think of cool uses for this including an often-requested feature that TroopTrack currently doesn’t have – the ability for a scouting group to get/restore a csv or sql or json backup of all their data (TroopTrack is a multi-tenant application).
That’s all well and good, but I think there is a simpler advancement that is needed that is also more important. I mentioned it earlier in my example about how I care about different aspects of users depending on whether I am in the manage use case or the communicate use case. I think it boils down to something simple.
Models need to be a reflection of the use case rather than a reflection of the columns on a table plus a generalized set of behaviors.
At this point I can hear Bob Martin cackling ruthlessly. It’s what he’s been saying all along. Sometimes, you just gotta see some code to believe it.