When you’re creating a new class, do you know exactly where to put it in your solution or do ya just kinda shove it near something similar and call it good?
Think about the last class you wrote – would you say it’s in its proper place? Is it flexible and easy to change?
Aside from being developer buzzwords (and aside from the nifty strategies laid out in my how to organize your projects article), do you know what that would actually look like?
Unless it’s explained to you, you can end up flying by the seat of your pants writing code a certain way simply because it matches the surrounding code, but without any real professional consideration.
Fortunately, as I continue my read thru the book Adaptive Code via C# by Gary McLean Hall, I bring more sweet tidings of pro level dev chops.
This article introduces a technique in class design known as the Stairway Pattern. It’s a pattern that will keep your code loosely coupled AND, as a side effect, will improve project organization.
It’s one of those techniques that make you feel like a responsible adult like learning how to change a flat tire.
The Entourage Anti-Pattern
To preface the pattern, the author first explains the anti-pattern that we should avoid, known as the Entourage Anti-Pattern.
In a nutshell, the Entourage Anti-Pattern is putting your implementations in the same project as their interfaces.
Now, when I read this I was delighted because I’ve been doing that since the beginning of time, so I knew I was about to learn something.
The author mentions a couple reasons this can be a bad idea:
First, by having the implementation right there, a less experienced developer might come along and be tempted to use new
and hardcode the dependency into the client class, despite the interface’s existence. Take a look at my earlier dependency injection article to see why hardcoding dependencies is a code-smell.
Second, and the reason it’s called the Entourage Anti-Pattern, is that clients will potentially need to bring in assemblies they don’t need.
I’ve got a great example of this in one of the applications I work on at the time of writing this. This app offers an e-signature feature that makes use of DocuSign, a very popular e-signature platform that has it’s own API for managing the entire document signing flow.
When we built this feature, we coded to an IESignatureService
interface so that we could later swap out DocuSign for something cheaper or homegrown. Now, although we are using an interface, the DocuSignService
implementation (which depends on a DocuSignAPIClient
library assembly) sits next to it in the same project. This project also happens to be a very large one that holds a lot of other business logic unrelated to e-signature.
The problem shows itself when you look at the bin
directory of every project that makes use of that business logic project. DocuSignAPIClient.dll
exists in all of them!.
That DLL is an entourage member of the business logic project, tagging along even when it’s not needed.
The Stairway Pattern
To fix this, we apply the Stairway Pattern, which is simply keeping the implementation in a separate assembly.
Clients should only depend on an interface, which can either live in the same project as the client, or also in a separate assembly. It’s called the stairway pattern because the UML diagram showing dependencies looks… like stairs.
For our e-signature example, we could place our DocuSignService
in a separate ServiceImplementations
project so that its can be decoupled from the business logic project until the time that it’s needed.
But Joe, you’ll eventually have to call new
, so the entourage will have to come along at some point, right?
Yes! If you’re using dependency injection, your implementations will be instantiated at the entry point of your application in something like an IoC container. But the benefit is that that is the only place new
will be called. Giving you the ability to swap out implementations very easily.
I thought about this more while reading and found this very useful StackOverflow response from the book’s author himself. Reading it is mandatory!
When to use it
I see this pattern coming in handy for most service classes.
One thing I’m not sure about though, is refactoring mature codebases that aren’t already built with dependency injection. Would it be better to stay consistent with the existing patterns?
Side note on consistency: I like the analogy five books on a pile and one on the shelf is better then all six in a pile that I came across in an excellent article called Livable Code by John Hotterbeekx. It’s an article that will help ground you in reality after all this pristine, adaptable, clean code talk.
The author also mentions a common fear: that implementing this pattern could lead to large increase in the number of projects in your solution, but he doesn’t explain why that wouldn’t be the case. You’ll notice in that StackOverflow question, other folks had the same concern.
What do you think? Would you simply throw all service implementations into a single project?
I guess it depends on how many different things your application does. With the “everything but the kitchen sink” line-of-business apps I’ve worked on, implementing a pattern like this would seem daunting considering the number of features to refactor out.
On the other hand, I could start putting the metaphoric book on the shelf, and communicate to my team that this would be the new practice going forward.
If working on something quite new, I would without a doubt make this a standard practice.
Top comments (2)
Recently, I have always advocated separating interfaces from implementation, even if someone on the team says "99% of the implementation of these interfaces will never change." Never say never. Splitting libraries saved our refactoring recently when moving a large project from EntityFramework to EntityFrameworkCore. We just changed the implementation in 10 projects with EntityFramework and went to drink coffee.
Yes, exactly! This is a strategy you could use in the context of creating a layered architecture.
It's a tactical way to implement the layered architecture in a way that's truly decoupled.
A lot of folks could think that just because they have three distinct projects for UI, Biz logic, and Data, that they have implemented proper layering, not realizing that the literal dependencies between classes is quite important.