Hey, I Think I’ve Invented a New Pattern
Ok maybe not totally new, I am sure that its just a projection of something I once noticed subcionscioiusly in a F# project. But in the C# space, I’ve yet to see something quite like it.
The problem is that programs are complicated. I don’t tend to help the issue. In a flash I don’t hesitate to layer on additional complexity with domain models, frameworks, and complex infrastructures. Entities, sevices, repositories and aggregate roots – they’re the tools that we reach for all too often. That is not to say that Domain Driven Design is a poor paradigm – I’m a big fan – it’s just not always necessary.
Take for example a typical scenario. You are given an integer which uniquely identifies a customer and are asked to generate a report of all their orders, export it to a pdf file and return as a Stream. My first impulse is to start with the customer and order objects, identify the bounded context, figure out the aggregate roots, start writing data access mapping code, and so on. If I’m lucky, I only do this for a few hours before hearing the voice of an incorporeal Ted Neward yelling at me to STOP. I was not asked to build a domain model, I do not need to keep track of state, there is no validation, or (much) business logic. The problems that DDD is intended to solve are simply not present here. I need to do one thing and one thing only, map an integer to a Stream!
And yes, I do realize that this is exactly what the CQRS guys have been harping on for years.
My Solution:
Each of my last few projects has contained a very simple interface.
public IMapper<INPUT_TYPE, OUTPUT_TYPE> {
OUTPUT_TYPE Map(INPUT_TYPE obj);
}
Get it? Put in an integer, get back a Stream, what could be simpler? Ok, maybe that’s too simple. Any change at all would require modifying the implementation. This is all that icky procedural programming stuff. What about reusability, encapsulation, separation of concerns, and our other OO goodies? Fortunately, we don’t have to give these up. All we have to do is introduce a couple of obvious seams. We no longer transform an integer directly to a Stream. Instead we:
- Take the id and map it to a DataSet that contains the report data.
- Map a DataSet to the appropriate fully built Crystal Reports ReportDocument.
- Map a ReportDocument to a MemoryStream of a pdf file.
IMapper<int, DataSet> _dataProvider;
IMapper<DataSet, ReportDocument> _reportGenerator;
IMapper<ReportDocument, Stream> _pdfReportExporter;
public Stream GetCustomerReport(int customerId) {
return _pdfReportExporter.Map(
_reportGenerator.Map(
_dataProvider.Map(customerId)));
}
Pretty nice huh? Well not that nice. If you’re a naming perfectionist like I this should make you shiver. When it comes to maintainability, good intention-revealing namespace, class, and method names are worth a million unit tests and n-tier architectures. Forget requirements docs and self-documenting code, proper naming is the number one tool for communicating our intent.
I lecture my team on this constantly and yet I allow myself the use of the the term “Map”?! A generic term that could indicate anything except (unless you’re a cartographer) a concept from your ubiquitous langauge. I should be ashamed.
We want to keep our generic IMapper interface though, it’s so slick, allows us to write minimal code and you you could easily imagine it coming in handy. For example, perhaps as the domain itself becomes more complicated, it becomes useful to break our IMapper<int, DataSet> down further.
1. The integer is get mapped to a Customer entity.
2. This gets mapped to a ReportSpecification.
3. The ReportSpecification is evaluated and mapped to a StoredProcedureInvocation
4. The procedure is executed and finally returns our DataSet.
You could imagine all sorts of neat little tricks you could play with our IoC container – we could have it automatically link transforms together or subsitute out implemenations. Simply by knowing that all these components transform one object to another we could configure automatic caching, deferred execution proxies, and so on. All things that are made far easier because all transformations are achieved via IMapper.Map.
What we really need is a way to introduce aliases for the Map() method. Preferably ones that depend on the input and output type. So why sit there scratching our heads? C# is a flexible enough language, let’s just let’s do that thing that I just said.
public CustomerOrderReportGenerator : IMapper<DataSet, ReportDocument> {
public ReportDocument Map(DataSet data) {
// implementation
}
}
public partial static class AliasExtensions {
public static ReportDocument GenerateReportFrom(this IMapper<ReportDocument, DataSet> mapper, DataSet ds) {
return mapper.Map(ds); }
}
And now we have aliases:
_pdfReportExporter.Export(
_reportGenerator.GenerateReportFrom(
_dataProvider.GetOrderDataFor(customerId)));
The poor readability of this bothers me. The LISPers among us might not mind reading backwards through a stack of nested parentheses but as a fan of fluent interfaces I’d like to see my code read as close to natural english as possible.
Same trick, now featuring double dispatch!
public partial static class AliasExtensions {
public static ReportDocument ComposeIntoReportUsing(this DataSet ds, IMapper<ReportDocument, DataSet> mapper) {
return mapper.Map(ds); }
}
Allows us to do:
return _dataProvider
.GetOrderDataFor(customerId)
.ComposeIntoReportUsing(_reportGenerator)
.ToStreamUsing(_pdfReportExporter);
And there you have it. Nice, fluent, and composable.
What do you guys think? Aliased Maps a good name?
Write a comment: