I'm sure every developer wants to be able to change code with confidence and without fear. Readable and self-explanatory code is one aspect of that. Too much coupling is another major source of problems that prevent you from changing one part of the system without causing side-effects. In this talk, I'd like you to show you common sources of unnecessary coupling and offer you options to help prevent and/or break those. I'll talk about how principles like Don't Repeat Yourself and Dependency Injection can be a double-edge sword, how to detect too unnecessary dependencies and how to use the Dependency Inversion Principle to unravel some of those. And yes, I will also talk about controlling dependencies on the package level.
Challenge
1. How to prevent too much coupling?
2. We are tought to decrease coupling and increase cohesion
3. How do you increase cohesion? By applying DRY!
4. But DRY creates more coupling
5. And too much decoupling results in a forest of abstractions
6. The solution is… find a balance (or "it depends")
On the architecture level
1. Use architecture style that embraces DIP (e.g. Onion, Hexagon, Clean Architecture)
2. Organize your code along functional boundaries -> this will make it easier to break off that boundary for rebuilding/strangler/microservices.
3. Apply DRY within those boundaries only, but allow shared services for complicated capabilities
4. Align your test scope with those internal boundaries…
On the package level
• Classes and interfaces that are almost always used together should be packaged together. Those that don’t meet that criteria don’t have a place in that package
• Packages should not require changes (and thus a new release) for unrelated changes.
• There must be no cycles in the dependency structure
• A package should only depend upon packages that are more stable than it is or abstract packages
On the code level
• Avoid technical folders and organize them by functionalities or capabilities
• Things in adjacent folders usually mean they are separate boundaries
• Use the Tell, Don't Ask principle
• Use the Law of Demeter to detect unnecessary coupling
• Encapsulate primitive types and collections in specific types
• Dependency injection is great, but try to avoid a global container
• It's fine to inject concrete classes inside boundaries
Strategy for legacy code
1. Ensure you have a safety net using characterisation tests
2. Find existing seams and decouple them better
a. Identify modules or functional slices
b. Identify groups of classes that are supposed to be used together and are designed to be reusable
c. Identify interfaces or abstractions in DI registration code and see if they are truly designed for reusability
d. Assume that code in adjacent folders is supposed to be independent
e. Move code from different technical folders that belong together in their own (functionally named) folderPublic
3. Use that to define (and visualize) the target architecture
Getting a grip on your code dependencies (2023-10)
1. at the architecture, package and code level
Getting a grip on your dependencies
Dennis Doomen
@ddoomen | Principal Consultant | The Continuous Improver | Aviva Solutions
2. About Me
Hands-on architect in the .NET space with 26 years of experience on
an everlasting quest for knowledge to build the right software the right
way at the right time
@ddoomen | The Continuous Improver
3.
4. The problem of coupling and cohesion
Dennis Doomen | @ddoomen | The Continuous Improver
19. Apply DRY within those “boundaries”
Duplicated
Service 1
Duplicated
Service 1
Duplicated
Service 2
Duplicated
Service 2
Duplicated
Service 1
Centralized
Service 3
Extension
Methods
Extension
Methods
Extension
Methods
Extension
Methods
Helpers Helpers Helpers Helpers
22. Main Package
Application
Bunch of blocks that
can be used directly
Uses composition
over inheritance
Convenience
blocks that don’t
hide the magic
Shared Package
Contract
Contract
Only depend on
more abstract
packages…
Stable Package
…or depend on more
stable packages
Auxiliary Package
Blocks that are not
used together do not
belong together
Optional
Dependency
Dependency
Package
Consumers should
not be faced with
optional
dependencies
No cyclic
dependencies
Principles of successful package management
24. Avoid technical folders and organize code by
functionalities / capabilities
Functional
Folders
“Unit” of (integration)
testing
DRY within boundaries
Can be used to
clarify “public”
parts
Only unit tested
for specific
reasons
Role-based
interface name
33. Find existing seams and
decouple them
1. Install a tool to visualize code
2. Identify modules or functional slices
3. Identify types that are supposed to be used together
4. Identify types are designed to be reusable
5. Find IoC registrations and verify 2 and 3
6. Assume code in adjacent folders to be independent
35. Select a candidate to untangle
first
Application
Legacy
Entangled
Capability
36. Start to untangle
1. Move code to functional folders
2. Apply code-level guidelines
3. Use DIP adapters
4. Duplicate code that isn’t supposed to be
reused
5. Duplicate code that is used in multiple
boundaries
6. Considering moving to local IoC
containers.
IStoreOrders<T>
+ Query<T>();
+ Add<T>();
+ Delete<T>();
NHibernate
Repository
Order Processing
IStoreOrders
+ GetIncompleteOrders(minValue);
+ StoreOrder();
+ CompleteOrder();
Adapter