A common theme I spot in tactical discussions: naming things after refactoring. As I wrap up the Clean Architecture theme on our streams I decided to provide an entertaining perspective for the weekend with some useful tips on how to approach these issues differently.
💬📘 PSA: A head’s up before we start—we continue the Book Club, starting to read Implementing Lean Software Development in September. You’ll learn how to speed read and apply what you learn quickly, rather than just accumulate knowledge. Very welcome, especially if you procrastinate a lot. Want to improve your software management skills in a month? DM me.
Writing code is a read heavy process
Engineers are notorious for underestimating how much time they spend (waste) reading code. Are you struggling with this too?
There is nothing worse than going into a legacy codebase and trying to understand what’s going on, but all you get as feedback is confusion, not clarity. It’s like reading a book in a language you know, but that is written in a weird dialect with local jargon.
It is this jargon that Domain-driven Design attempts to capture in its event storming sessions, building a common vocabulary. I always advise teams to start any new project by extracting common words that can be used to name things.
Naming things is hard! So involve everyone: designers, managers, customers, founders, engineers. Extract the natural language and create derivatives for synthetic use cases when you need them in your code or infrastructure.
Derivatives— you may ask? What does this have to do with Clean Architecture?
The premise of clean architecture that I have seen great success with adopting is this: Keep your innermost, business-focused jargon to the Domain Layer.
In case you forgot about the layers, refresh your knowledge below 👇
☑️ GOOD: Domain Layer clear of detail and keywords
Booking
RegistrationNotice
FulfilmentOrder
UserCheckedOut
☒ BAD: Synthetic, crude extraction/refactoring 🙈
IBooking
UserableRegistationInterface
AbstractOrderRequest
AbstractUserEventMessageFactory
What is “Detail”?
Clean Architecture—the book—highlights this as the main area of improvement for codebases that are already well-factored following SOLID principles: don’t marry your details.
A detail may be:
A framework
Language constructs
Design Patterns
SDKs
3rd-party services
3rd-party models
Glue code
This doesn’t mean you should be completely decoupled from these concepts. After all, your infrastructure code will likely be dependent on all of these to perform well within non-functional parameters.
However, the purpose of refactoring with Clean Architecture is to redraw boundaries around details as your usage patterns become clearer in order to increase the quality of coupling. This is in essence boundary management, ie. enforcing the layers of what is inside and outside separate concerns. Just like your company has departments, so does your codebase.
I love this summary by Simon Rieß:
Picks up where smell-refactoring fails
The output of refactoring is often clear:
Readability
Maintainability
Testability
Composition
An orthogonal concept that comes in handy for all these concerns is something we in the industry have been touting about but I rarely see a team adopt it purely: Good Object Oriented design.
Not the kind where you use an abstract class to carry some framework glue around. The kind where you encapsulate concerns and move the behavior towards the data—tell, don’t ask. Encapsulation is the #1 rule that I observe teams struggle and resist with the most, followed by the Open-Closed Principle.
Misguided by decades of confusing literature, there is an immense opportunity of improvement for all code bases using these shortcuts and anti-patterns:
☒ Principle says data should be private → Include getters and setters instead
This solves the problem of mutation control, not encapsulation. Encapsulation means that rather than inspect the object and asking it for its data, you pass the object around or tell it to do something (see tell, don’t ask above).
☒ Principle urges separation of concerns → Separate data from their methods
A good example is an Event. It may contain some data. And you want it to emit over the wire using a client. In the wild, you may have seen the following simple anti-pattern of class dependencies:
class Event // Contains data only
class Client // Knows about the web to send over the wire
class EventSendingService // Receives Event and Client to wire them together
Such a design appear benign at first but you may often spot methods named “do X with event” in the client and the service.
ConvertEvent(event e, …)
LogEvent(event e, …)
SendEvent(event e, …)
SaveEvent(event e, …)
ValidateEvent(event e, …)
The wordiness gives you a hint on what’s wrong. All these methods require the data encapsulated inside the Event object to carry out their command. The behavior is separate from the data. However, that doesn’t mean we should couple the event to all the infrastructure concerns. An alternative approach may be:
class Event
public Event() // validation is handled by the constructor
public send(Emitter) // the interface the Client implements
public summarise() // Save and Log and cross-cutting concerns, we let the other services deal with it and give a description of the event
Notice how the we no longer require a synthetic service to augment the event with behavior. It was its responsibility all along. What is not its responsibility, is persisting it into a database and hydrating it from it as one may suspect by using an ORM. Object are not mappers. Neither are they Queries. They control the write nature of how data changes and what side effects they may have with adjacent domain concerns.
This detail is explored more with Aggregates and Invariants in Domain-Driven Design.
That’s it for today. 👋🏼
Wish you a happy weekend, I’ll see you next week for the 20 year DDD anniversary and stream on Thursday!