I ran into a curious situation today. The database I’m working with primarily has lots of big, horizontal entities, which is fairly common for any schema that’s been around for several years. It’s main consumer is a series of WinForms applications that rely heavily on grids for displaying data. Since most of the grids don’t need to show every column of every entity they are displaying, there are several versions of entity projection classes in different namespaces of the existing framework. I’ve found these frustrating, since they often don’t have everything that I need for the behavior I’m attempting to produce. Yet these classes exist in the data access layer, along with the actual entity objects.
I was working on a set of forms for searching and displaying information on various entities, and when I started a new form, I realized that the initial search display was exactly the same as a form I had just created. Obviously (much to my chagrin, more on that another time), it was time for a user control. I grafted the controls into my new user control, started working on refactoring the code, and then sat back, stumped.
In my opinion, a user control should really only be created if it is going to be re-used by different containers that require the same behavior for part of their purpose. This particular control had a DataGridView in it that worked with an entity projection class. The entity projection itself was in a separate namespace in the same project, since, at the time I created it, I believed that it’s sole purpose was going to be for use on the original form. Now that I was looking at a user control, the projection became theoretically necessary on a larger scope than the original assembly, along with the control itself.
Hence my stumpage. Where do I put the projection and the corresponding query? Hell if I want to be the guy violating DRY.
I considered the data access layer, but only for a few minutes. The query for the entity along with the projection itself, though they might be re-used at some point, were behavior driven elements; part of the business logic.
Then, I thought about creating a business logic assembly that would house both the user control and the logic. This was more appealing, at first, because it seemed to keep things that should be separate separated. But then I remembered the existing framework, and how these entity projection classes and the stored procedures used to load them kept getting changed, massaged, and bloated to represent 42 different things. Not at all what I wanted.
The truth of the matter is that the user control has a single responsibility: to search for an entity in a certain way and present the results in a certain way. Might other applications down the road need to do this same thing? I don’t know right now, and right now it doesn’t matter. What matters is that the code that is required for this control doesn’t belong anywhere else.
So, I’m moving the projected entity class, the compiled query that projects it, and all of the calling code into the user control itself, which will then simply need a data context to work with. Did I do the right thing in the long run? Maybe not. But even if this control needs to be used by another application in the future, it will be a simple, wholesale move to a separate project, rather than another application architecture decision.
Projections are business logic. Attempting to maintain several sets of projections for use by different projects is a no-win proposition. The entities themselves change over time, hence the use of projections in the first place. Maybe two applications will need very similar entity projections, but attempting to predict this leads to over-abstraction and accidental complexity.