I recently found myself in a design discussion where I talked about how our code lacked seams, and another developer talked about boundaries. A third developer asked, “What’s a seam?” And this got me thinking.
What IS the difference between a seam and a boundary?
I wasn’t sure, so I did some research. And it turns out that the answer I gave was really about separation of concerns. Related, but different.
If you Google this, you get 535 million results.
A boundary is the line between your code and the code that you don’t control. Further, an external boundary is more literally code that you didn’t write and a third-party API, whether that’s a web API, a third-party package, something another team wrote, the file system, etc. And an internal boundary exists between different components within a system.
It’s helpful to think of a boundary as something you don’t immediately control. External boundaries are always well-defined because they’re impossible for you to modify. Internal boundaries, by contrast, can end up being poorly defined and are more about separation of concerns and much of what makes “clean code.”
When differentiating between seams and boundaries, it’s easiest to just focus on external boundaries. And those boundaries exist whether you wrap it in an interface or not.
If you Google this, you get 37.6 million results. And it doesn’t take long before you start getting references to sewing.
Coined by Micheal Feathers in Working Effectively with Legacy Code (one of our must read books), a seam is a place where you can change behavior without changing the target code.
More practically, it’s the things we do to make our code easily unit-testable, such as injecting mockable interfaces or other fakes where we can control behavior without executing the “real” code.
So, when working with a third-party we don’t control, the seam is the interface wrapping it that we created so that we could mock its behavior for tests. Also, some third-party packages, like popular logging libraries, will provide their own seams which allow you to use their offering directly without sacrificing unit-testability of your code.
They’re related but different.
Best practice is typically to have seams along your boundaries.
But semantically speaking:
- You can have boundaries without seams by using the third-party directly.
- You can have seams without boundaries by creating seams between pieces of your own code, even within the same component.
- You can have seams which cross boundaries by using a third-party provided seam.
Good to know!