In Layered Architectures – Sculpting the Big Ball of Mud, I mentioned in passing the topics of dependency injection and inversion of control. These topics roll up to the larger consideration of managing dependencies, which, as a key architectural concern, deserves further attention here.
Dependencies are, for all practical purposes, unavoidable. This is a good thing, unless you are in favor of having to embed operating system functionality into your code just to be able to write “hello world”. As stated by Microsoft MVP Steve Smith, in Insidious Dependencies:
Let’s assume you’re working with a .NET application. Guess what? You’re dependent on the .NET Framework, a particular CLR version, and most likely the Windows platform unless you’ve tested your application on Mono. Are you working with a database? If so, then you likely have a dependency on your database platform, and depending on how flexibly you architect your application, you’ll either be tightly or loosely coupled to your database implementation.
In that same article, Steve identified other common dependencies:
- The file system (System.IO namespace)
- Email (System.Net.Mail namespace)
- Web Service and Service References
- Dates
- Configuration
The list above is also far from exhaustive. I’d add authentication, authorization, and caching as additional examples that leap to mind. Steve himself added logging (System.Diagnostics) in another post, Avoiding Dependencies. All of these represent basic application services that are useful (if not indispensable). In most cases, it makes little or no sense to try to reproduce the functionality when a ready made (and tested) alternative is available.
By the same token, dependencies represent a vulnerability. They are things you rely on, and in many cases you have no control over their evolution. For example, there have been changes from one version of the .Net framework to another in both email and configuration. Providers of third party components can introduce breaking changes or even go out of business, leaving you without support. Since we can’t live without dependencies, but living with them is problematic, then the answer is to manage them.
Managing dependencies can (and should) take place on both the macro and micro level. At the lowest level, Steve Smith recommends two common patterns: the Facade Pattern (where the dependency is wrapped within another class, which serves as the unchanging point of reference to consuming code, allowing the underlying implementation dependency to be changed in one location) and the Strategy Pattern (which combines the Facade pattern with a common interface, allowing for multiple implementations selected at runtime). The Strategy Pattern would be used to enable dependency injection, which is useful for plug-in functionality scenarios as well as for automated unit testing. There are limits, however, to how far this should be taken. In Avoid Entrenched Dependencies, Steve notes:
There are costs associated with abstracting dependencies. In the extreme, every “new” in your code is a dependency on a particular implementation, and thus could be replaced with an interface and injected in. In practice, the main things you want to worry about abstracting are the ones that are most likely to change, and over which you have the least control. Things that need to change in order to test your code count as things that are likely to change, and thus should always be abstracted if you plan on writing good unit tests! Otherwise, don’t go overboard. Wait until your dependence on a particular implementation causes pain before you start extracting interfaces and DI-ing them into all of your classes. All things in moderation.
Managing dependencies at the macro level ties in with layered architectures. Dependencies can be broken down into those that have an affinity to a particular layer and those that are more cross-cutting in usage. Those that can be segregated to a particular layer, should be. In Dependency Management – Anti-Patterns as Art from The Daily WTF, I highlighted three articles on that site describing self-inflicted dependency nightmares. While comic, they also serve to underscore an important point: some of the dependencies your code relies on are your code as well. A poorly organized system where concerns are inter-mingled quickly turns into a maintenance nightmare. Partitioning classes by responsibility and enforcing communication between layers in a controlled manner will lead to a more understandable and maintainable system.
Another macro level concern lies in determining what external dependencies to allow. Standard buy versus build considerations will apply here: time to develop, test, and maintain as opposed to license and maintenance fees, stability of the vendor, level of control over the component that’s required, etc. Type of license of can also be critically important. Open Source components can be great additions to a system, but make sure you understand your obligations under the license. If you have access to a legal adviser, their input will be valuable.
Eric N. Bush, in the { End Bracket } column of the August 2007 MSDN Magazine laid out the following guidelines for dependency management:
Authority Who makes what decisions? Does anyone have the final say? What is the escalation process?
Roles Clarify the expectations and determine who is responsible for what.
Goals/Value Define what success looks like. Drive for alignment. Enumerate and explain the risks.
Communication How and what will be communicated? Establish meeting schedules, e-mail protocols, and Web site access.
Accountability How will you track progress? What is the process for fixing mistakes?
Engineering System Create an issue database, source code control, build, setup, drop points, and quality measures.
The level of formality involved will vary depending on the size your organization, but the principles remain the same. Properly understood and managed dependencies contribute to the success of an application. Left to their own devices, however, your dependencies can manage you.
Like this:
Like Loading...