Persistence Ignorance and Associations
One issue that seems to come up regularly in the team I'm on is how to design associations between persistence ignorant domain classes, or more particularly how to do it without falling into bad design practices.
Let me provide an example of the sort of problems that come up. Let’s say we have an abstract Company class which is the base class for two concrete classes:
- Agency - Contains a collection of Clients.
- AgencyNetwork - A collection of agencies that work together.
A Client is associated with one Agency and an Agency has 1+ Clients. An AgencyNetwork is associated with 1+ Agency objects, with each Agency in 0 or 1 AgencyNetworks.
The original solution is much like this (I've simplified it to show in a class diagram):
The reason the associations are like this is that we're managing the relationship from the "one end" as its simpler. Now here is what I don't like about this design:
- AgencyNetwork is currently only used by one of our application so having our key Client class indirectly coupled to it seems like a very bad idea. I'd prefer our core classes, which the majority of our applications will use, to be coupled to as few non-core classes as possible.
- I'd prefer if our design made it clear what the real world associations were, I don't think this design does.
- I don't like the idea that the way you find out if an Client is in an Agency is like this: if(Client.Agency != null).
- As I see it in a lot of the time you'll want to use an Client without knowing about a Agency/AgencyNetwork but if your working with a Agency it's likely that the first thing you'll want to do is manage the collection of Clients.
This is my alternative soluton:
Although I think its much better this solution it is not perfect either (things never are).
In object oriented terms I prefer it as Client isn't coupled to the other two. I also think that when you get an AgencyNetwork the first thing you'll want to do is look at the list of companies in it. In addition I believe that the associations are far more natural, they reflect the real world associations far better.
The disadvantage of the second solution is that managing the relationship becomes more complex. With the first solution you could do this:
// change Clients Agency
client.Agency = newAgency;
// if the Agency is in a AgencyNetwork take it out of it
client.Agency.AgencyNetwork = null;
Personally I think this is incredibly ugly and does not reveal the intention very clearly but it is short.
With the second solution its more complex, not least as the Agency now handles the association so you have to do this (note we’re using the repository pattern here):
AgencyRepository agencyRepository = new AgencyRepository();
Agency oldAgency = agencyRepository.GetForClient(Client);
oldAgency.Remove(Client);
newAgency.Add(Client);
Although it involves more code I think this is much clearer, however where do we put this code. We cannot put this in the domain assembly as we don't want our persistence ignorant domain layer to access repositories. We'd therefore need to do it in the domain coordination layer:
public static void ChangeClientsAgency(Client client, Agency newAgency)
{
... as shown above
}
The question is thus which solution do we prefer:
a) Solution 1 - Have the one end (which will often be one of our key/core classes) manage the relationship and so enforce things that way (resulting in a lot of coupling).
b) Solution 2 - Decouple the classes, add the associations that we think make most sense and then have to explicitly manage the relationship.
How about using interfaces to expose the intent and hide those implementation details behind it?
ReplyDeleteFor example:
IClient c = GetClientById(id);
IAgency a = GetAgencyById(id);
if (!c.WorksWith(a))
c.JoinAgency(a);
I think what your saying here is that the Client knows about its agency but only as an IAgency?
ReplyDeleteThat could definitely be an improvement but even that level of coupling may be undesirable. Lets say the Client class already exists and indeed is being used by other applications. Its a nice cut down class and it suits our needs nicely. In that case adding a reference from Client to Agency is creating a dependency that I probably want to avoid. In particular it's quite likely that Client may be in a different assembly and I don't want users of Client to now have to pull in the assembly that contains Agency.
In general I'm not entirely sure interfaces can hide this sort of implementation, one way or another you want to map the relationship between the two classes. Having said that I think people can be two quick to grab the obvious solution, which is to have the one end handle it, when actually that approach can result in some clunky designs.
Thats my take on it anyway.
Oh and I really like your podcasts, only found them a few weeks ago but they seem great and you seem to have the deepest voice in IT! :)