Software architecture tends to be a pretty hard game. Writing scripts and little toy apps is easy enough. You build something and then you run it, confirming it does what you want. But then the software grows in scope and complexity, and things get tough. And it’s only once things get tough and architects enter the fray that you really worry about something called a dependency graph.
At that point, the dependency graph really matters to anyone interested in architecture.
What is a Dependency Graph?
Let’s start with the basics. What is a dependency graph? It’s actually not really a code-specific term, though it applies frequently and suitably to building software.
In mathematical terms, a dependency graph is a directed graph, where directed edges connect the nodes and indicate a directional dependency. I’ll concede that I just typed a pretty dense definition there, so let’s take the edge off with an example. Please bear with my rudimentary-at-best drawing skills.
In this diagram, I’ve modeled the components of a house. The basement of the house, containing the foundation, depends on nothing. The first floor of the house, however, depends on the basement/foundation for stability. And the upstairs depends on that first floor and, indirectly, the basement. In this model here, the garage is a free-standing structure, depending on nothing and taking no dependencies, either.
I use this completely non-mathematical and non-programming model to demonstrate that the dependency graph is a standalone concept. It’s a fairly straightforward way to illustrate relationships. And more importantly, it’s a highly visual way to do so.
Dependency Graphs in the World of Programming
As you might imagine, this can relate to programming in a variety of ways. For instance, dependency graphs lend themselves naturally to package management, since it depends heavily on ordered trees of interdependence.
But I’d like to set that and other usage flavors aside and talk software architecture. Dependency graphs do an excellent job of depicting how elements in your codebase depend on one another. You can use them for methods, types, namespaces, or whole assemblies. And in any of these contexts, they provide an excellent window into the architecture and maintenance profile of your codebase.
When it comes to architecture, you most commonly see people reason about things at the project/assembly level. But let’s also bear in mind that you could apply it to other granularities of code as well.
Architecture Dependency Graphs in the Wild
So where do you usually see dependency graphs in actual software development groups? In what context does this modeling take place?
Well, you’ll often see them scrawled on whiteboards during meetings or perhaps represented in PowerPoint slide decks. And they usually won’t appear as simple node-edge, mathematical-style graphs. You’ll see them take more stately forms, often involving layers, complex workflows and “orchestration” of all of the moving parts.
But don’t take my word for it. Do a Google image search for “software architecture” and behold for yourself the impressive array of buses, collaboration tiers, workflow layers and such.
I plant my tongue a bit in cheek as I talk about this, but it’s truly understandable. As I said earlier, scaling software is really complicated, so we attempt to wrangle the complexity with fairly elaborate abstractions. And we do this by taking the simple dependency management construct, the dependency graph, and fleshing it out to an elaborate architectural blueprint. Then, ideally, developers take that blueprint, go forth, and make it reality.
The Feedback Loop Problem
This is how software processes have worked for decades. In its most extreme form, we call this a waterfall approach to software development. Business analysts create gigantic requirements documents, and then they sit with the architects, who proceed to design these imposing systems. Once they’ve completed their designs, the developers execute, theoretically turning these glorified dependency graphs into actual software.
But even shops that have adopted newer, more agile methods have some of this going on. They iterate much more frequently, and they ship to production with regular cadence. But they still create complex dependency schemes on whiteboards and then translate those as faithfully as possible into code that they deploy. They’ll describe these things using words like “microservices” and “distributed intelligence,” and they’ll illustrate them with graphs or flow diagrams, hoping the code matches.
Across the board, this approach to dependency management suffers from having an open loop. Whiteboard illustrations and Viso diagrams materialize, and people try to recreate them in code. How do they know if they recreated the vision faithfully? Well, simply put, they don’t. And I refer to this as the feedback loop problem. As an industry, we tend to draw these complex abstractions, attempt to create them, and then never check whether or not we succeeded.
Closing the Feedback Loop
I’ve written about this issue before, in a blog post called “Visualizing Your Real Software Architecture.” Our beautiful layered diagrams turn into amorphous blobs of architectural death. In the post I wrote, you’ll notice a picture that captures the problem. If you read this post from the Daily WTF, you’ll see another picture with an accompanying backstory.
Take a minute and laugh at those, but then realize something. In both cases, we have an actual picture of reality. That means that somebody has closed the feedback loop. But it also means that somebody closed it way, way too late.
Think about it. If they’d started with regular pictures of the architecture from day one, there’s no way the group would have let the actual graph diverge so pitifully from the pretty layer cake diagrams and flow charts. Instead, the team marches for months or years without closing the loop until, eventually, some skeptic (or hired consultant, as it were) comes along and generates a dependency graph for them.
It’s probably too late for those shops, but that doesn’t mean it’s too late for yours. You can close this loop and see what your dependency structure actually looks like. You can see which assemblies actually reference one another, which namespaces have an organized structure (or which have dependency cycles), and which types come together in clusters.
And when you get right down to it, this — actual dependency management — is the heart of software architecture, not creating diagrams and rattling off design patterns you intend to use.
Use Tools To See Your Dependency Graph
Not surprisingly, I use NDepend’s dependency graph feature, myself. It’s extremely simple to use, visually expressive, and interactive. Here’s an example dependency graph.
As you can see, we don’t have something that looks like a Visio-rendered work of art. But it’s not “the enterprise dependency,” either. This is an actual look at a relatively well-decoupled application. And that’s what happens when you close the loop and keep tabs on what your architecture looks like.
Another feature of NDepend that I like is you can switch to a matrix view for architectures of doom that blow up the dependency graph. This at least lets you visualize them and reason about them after a fashion, even if they don’t lend themselves to easy visual representation in dependency graphs.
Obviously, I’d recommend NDepend, but the important thing is to make sure you have some way, using some tool, to visualize your architecture and your dependencies. As I’ve said, architecture is hard because dependency management is hard. If you don’t have a tool that automates visualization of your dependency graph, you’re flying completely blind, and your code quality and maintenance efforts will pay the price for it.