Dependency Graphs
14 AugustWhich do you prefer?
- Deep chains of dependencies, or
- Shallow but numerous dependencies?
Deeper
Wider
Scenario Number A
The BlogController
depends on User
and Post
,
and they in turn know how to load and save themselves from their respective datastores.
This is fine. It’s neither deep nor wide. It’s even preferred for small, medium, AND large projects for a long time. Regardless, it’s common to separate the persistence behavior from the domain objects for more flexibility when needed.
Consider this amazing chart:
preferred?
yes | xxxxxxxxxxxxxxxx
| \
no | xxxxxxxxxxxxxxxxxxx
-----------+-------------------------------------->
time => early later
It’s easy to start with this and change later. In fact, it’s usually easier to start with this. So plan to start with this and it may never change later. This is one case where taking the easy road in the beginning saves long-term cost.
Subscenario Number A.1
Well, you started storing Posts
created after July 23rd in Postgres as you phase out MongoDB,
and the User
domain model grew to have knowledge of related Posts
.
It’s time to simplify that graph.
This “simplified” structure isn’t that much better but does have some benefits.
At this time the Post
will have a loose coupling to its storage technology via creation date.
When Posts
are migrated to SQL a branch from this graph can be deleted without fear of breaking
the User or Post
domain models.
(We kept the domain model dependency between User
and Post
because that represents a real
relationship in the problem & solution domains (nice to have).)
Scenario Number B
In this scenario, a simple component relies on a TweetCache
to load Tweets
if they aren’t already in the cache. The component also uses the TweetService
directly to post Tweets
and fetch Metadata
.
Here, the depth is problematic.
- There’s a leaky abstraction -
the
TweetCache
isn’t sufficiently abstracting the functionality of theTweetService
so the component has to reach around it to interact with theTweetService
directly. - It doesn’t make sense for the
TweetCache
to both cacheTweets
AND act as a proxy to theTweetService
. Those two functions distinct and are better separated:
This is the kind of arrangement that causes problems from the moment it enters the codebase. Over time the leaky abstraction causes more and more complications, more and more delays.
It’s easily fixed, too. Move the auto-fetching from the TweetCache
into the component
turning that baked-in behavior into a composable behavior.
That’s much nicer. The implementation is easier and clearer, responsibilities of each piece is readily understood looking at the code. Changes will be easier to make so it will be less fragile. That decreases the depth and, in this case, didn’t increase width.
In this scenario, the shallow structure wins.
Scenario Number C
Layers. You may recognize the 7-layer OSI model for computer communications:
The depth of this model is its strength. But beware that each layer could be implemented with either deep or shallow internal dependencies.
Here’s the example from Scenario Number B
- with a few more components and services, and
- drawn as layers.
Note that this diagram includes the unfortunate initial leaky abstraction and adds component-specific “helper” services which both (A) keep the component code to a minimum and (B) complicate things.
And here’s the same scenario, flattened using the same technique as shown in Scenario Number B. Flattening like that lets us take advantage of another technique for decoupling: using a default error handler to interface with the ErrorLog
.
Now these dependency lines mean something. For example, the relationship between the TweetComponent
and the TweetPageComponent
is clear by looking at this diagram and applying a little common sense. We are now armed with information to help reason about why the UserMenuComponent
depends on both the UserService
and the TweetService
- perhaps that’s an inadvertent dependency that could be removed. Either way it will be apparent from the code and easily fixed within this structure.
And the Winner Is
Shallow*
*Don’t go overboard. Having depth in a layered structure is good when the abstractions are right, but within one layer shallow architectures are far better.