Time for me to evaluate the progress on the original goals of our project. In januari, I started on a new project and set myself/the project a few goals. These goals were explicitly written down
in a previous post. We've now finished phase 1 of the project, and I took the time to see what's left of my new year's resolutions.
What's the point?
I believe that you can only improve and learn new things when you deliberately set yourself explicit goals. Publishing these goals for all to see has been a major incentive for me to follow them up.
These are the goals I set for the project:
Overall
- readable, intent revealing software
- DRY, YAGNI, BOYSCOUT et all...
All in all, I think we've achieved this. What's even better, the team members started criticizing existing 'smelly' code with these principles in mind. I believe I planted a few good ideas that everyone will take to future projects
Entities
- Aggregate roots have an explicit interface
- No ID (I'm not sure if I can pull this one off with NHibernate)
- No (public) setters except for things like name, description, ...
- State based tests
- Testdata: take a look at NBuilder
No ID for entities was a challenge. At first all went well. Later on, NHibernate started complaining during specific queries that certain entities didn't contain an Id property. We didn't go into detail as to why that was. I intend to investigate this further, because I really liked the no-Id approach. We didn't get around to it yet.
No setter in properties caused a disturbance in the team. This meant that we needed a method each time an entity had to change state. I believe this effort has paid off, because our objects encapsulate their data and behavior much better this way (go look up the 'law' of Demeter for more info). Of course, as always, 'No setters' becomes 'Almost no setters'.
And we didn't get around to using NBuilder for test data. I'm not sure why, but we didn't. In the meantime, I met kilfour from
QuickNet. I think QuickNet might be a good candidate to include in our test
Services
- almost no logic
- infrastructure: queries, mapping, ...
- hosted in spring.net
- Behaviour based tests
Services in this case were WCF services. Check on all four goals. Lucky for me 'almost no logic' contained 'almost'. It's always good to set goals, but if the goal is too expensive when there is an easier alternative that doesn't '
smell', I apply the
KISS principle.
Controllers
- no logic
- only view concerns
- choice of view to render
- navigation (redirect)
- forward call to service
- Behavior based tests
- hosted in spring.net
Only a part of our application has a UI. The main part is web services, with a relatively small administration web site, built on
asp.net mvc 2. Check on all goals here too. I might add that each view got it's own viewmodel. Thanks to
automapper for making this so easy.
Repository
- only handles aggregate roots
- only the following methods: add/remove/query
Yep, only 1 repository, not 1 per aggregate root or (god forbid) 1 per entity. This repository contains only three methods:
void Add<T>(T t);
void Remove<T>(T t);
IQueryResult<T> Query(IQuery<T> query);
Add and Remove only accept IAggregateRoot entities.
Query() accepts only query objects.
Queries
- Separate query objects that get passed in the 'query' method of repository
- Preference in this order: LinqToNH, Criteria, HQL. Each query object can use any querying alternative from NHibernate
- Integration tested
Having separate query objects is something I'll try to apply on all upcoming projects. This way, we can have individual objects for each query that can be tested in isolation.
Security
- RhinoSecurity?
Nope, not needed at the moment. Security was not that big a concern... yet. This is phase 1 after all. That means phase 2 is coming up.
WCF
- Service hosting in Spring
Works OOB with spring.net.
Testing
- Heavy emphasis on readability
- Intent (test) Driven Development
- Fitnesse first (red), then unit test(R=>G=>R), integration test (R=>G=>R), Fitnesse green.
Check on all three goals. FitNesse first was a challenge, but it added a clarity to what we really wanted to accomplish in each story. In my opinion, FitNesse is a time saver when you're not sure how a feature should behave. It removes any ambiguity in the requirements before the implementation.
My original goal was to have the Product Owner write at least one FitNesse test per story to guide the team. This didn't work because the PO's workload didn't allow this. As a team, we decided to continue doing FitNesse up front.
Summary
First of all, we had a happy customer. In my opinion, this is what matters most. The customer was happy, because this design allowed us to have a flexible system which can easily adapt to changing requirements.
The team was happy too. We only had a small team of 4 people. Two of them had never heard of TDD, IoC, ORM, MVC, ... After a few weeks both became fans of this non-MSDN way of working. Retrospectives rule!