Domain-Driven Reports, Part 1
Most of the previous projects at The Company consisted of an external data source of some kind for which an Access front-end is developed to spit out various useful and non-trivial, pretty, colorized, formatted reports in Snapshot format. Since there was no side trip into object land, there was no impedance mismatch to worry about.
The Project, however, lives in object land. And you guessed it: stakeholders wanted the same kind of pretty, colorized, formatted reports as a feature.
It is a clash of the paradigms: domain-driven design works with objects. Reporting engines, like Access, live in the world of rows.
But this isn’t your ordinary impedance mismatch. The rows that make up a report themselves are not entities or even the hybrid value object entities that the Dynamic Enumeration pattern uses. They are formatted summaries representing a certain way to view a slice of the entities and value objects.
In our example for The Project, let’s assume we want a report that will, given a user and a list of rooms where equipment is stored, will give you a list, broken down by room, of how much of each type of equipment there is, with a sum of equipment in each room. Here is a relatively plain and unpretty-yet-formatted example:
Searching the Web for preferred solutions, you’ll run into the preference towards a separate reporting database from none other than Martin Fowler, and the man himself, Eric Evans. Unfortunately, The Project was constrained by getting only a single SQL Server database.
Let’s think about this a step at a time. Clearly, to fit this into DDD, we start with a Report Service because we need to take several domain objects as input. As output, we know we’re returning a report in Snapshot format.
I think this is where the problem gets incredibly sticky. In order to be a Service, we’ll need to return domain objects as well. Is a Snapshot report a domain object?
Forget that question and think about this: usually a Service returns a new domain object or objects that do not yet reside in persistence. The example Evans uses is a Routing Service that creates an Itinerary object (approx 30 minutes into the presentation) for a shipping company given a source maritime port and a destination maritime port.
We could do the same for reports: create the report in memory by defining some kind of Report object that contains the data for the formatted report.
- Problem: But at the end of the day, a .NET object is meaningless to a reporting engine like Access. We’ll have to flatten this object into rows, where it originally began, and put it in a table somewhere so Access can use it for a report.
- Problem: Not only that, but there is no “new” logic or data being added like there is in the Routing Service in the Evans example. The data for the report is there already. All the Report Service is doing is massaging it and doing a few summary calculations.
- Problem: Not only that, but it makes little sense to code the logic of the report ourselves. It is much easier and less error-prone to delegate that to the reporting engine (Access). And in order to do that, data has to be in a table of some kind, either linked in Access or directly in Access.
- Problem: Not only that, but it makes less performance sense to bring data into memory, instantiate them as domain objects, fiddle with them with our own algorithms, and then flatten them back into a table of some kind so the reporting engine can pick it up and spit out a report. For The Project, the SQL Server was located over a WAN (virtual LAN really) in a different state. The performance implications were clear.
You get the picture, I hope. The problem of fitting formatted reports into domain-driven design is not your typical domain-level Service. It is sticky. It is so sticky that I had been struggling with it since it was introduced to me last summer in my architecture and design course until I came up with four different solutions to this problem in the course of working on The Project and on my senior design project. The next three posts will enumerate each of these, with examples.