I've got a web application with an architecture somewhat like a front-controller MVC design. I get HTTP requests, route them, run them through a filter layer, dispatch them to my domain classes which then when necessary access an intelligent data service through data layer classes. The domain classes then return a standard payload to presentation classes which know how to generate the HTTP/web page output.
The application has 3 major cross-cutting concerns: audit logging, instrumentation/metrics, and localization. The first 2 need to be available to almost every class in the composition root, domain, data and presentation categories. The latter, in just a handful of classes, though they are both in my domain and in my presentation layers.
My app uses a dependency injection container. Adding the 3 above services as dependencies causes code smells: too many parameters in the constructors, and a dependency each class does not really need to do its own responsibilty but yet must be able to access.
I've done a lot of reading (Mark Seemann, thanks for your extensive writing on related topics!) trying to find an optimal solution for my situation. I can imagine several possibilities:
- Use an aspect oriented programming library which will introspect my code and allow me to add arbitrary point cuts (join points) and wire up my services as advices at those points. This seems elegant. I do worry about adding more complexity, dependencies and performance costs (I push execution limits on some pages that are very data heavy).
- Use an abstract factory pattern of some variation to allow leaf classes to get their needed "outside" dependencies. This would require some difficult refactoring, as the core structure is in third party libraries I'd rather not customize and don't lend themselves to easy decoration.
- Create Decorators for the classes which need to log, record metrics or localize. This is rather what the aspect library would be doing automatically, isn't it? However, it might address performance concerns.
- Use an Ambient Context object. My reading says this is a bad choice.
- Inject with the DIC and live with the code smell. ;-)
I do want to retain my ability to unit test everything and use mocks/stubs.
Are there other possibilities I've overlooked? (Redesign the entire app is not an option, by the way.)