Intention-Revealing Interfaces with Examples
In Domain-Driven Design (DDD), Intention-Revealing Interfaces are part of the model and API of a system's domain layer. The idea is to design interfaces that make it explicitly clear what a particular object or service is supposed to do, just by reading its method names or properties. Essentially, these interfaces aim to be self-explanatory and reveal their capabilities and constraints without requiring developers to dive into the implementation details or read separate documentation. “Name classes and operations to describe their effect and purpose, without reference to the means by which they do what they promise. This relieves the client developer of the need to understand the internals. These names should conform to the UBIQUITOUS LANGUAGE so that team members can quickly infer their meaning. Write a test for a behavior before creating it, to force your thinking into client developer mode.”
The concept is rooted in the broader principle of making the software model closely aligned with the domain it serves, thus improving understanding and communication among team members. Intention-Revealing Interfaces help to minimize misunderstandings, reduce errors, and enhance the speed of development because they're easier to comprehend and use correctly.
For instance, instead of using a generic method name like process()
, you might use something more specific like approveLoanApplication()
or archiveDocument()
. These names express intent and allow developers to understand what should happen when the method is invoked.
What Not to Do: Bad Examples
sortUsingQuickSort()
: This method ties you to the quicksort algorithm, making it harder to switch to another sorting algorithm later.getDataFromAzure()
: Azure-specific? What happens if you move to AWS?encryptWithSHA256()
: This one pigeonholes you into SHA-256, limiting your flexibility to change encryption methods.readFromDisk()
: Too specific. What if you switch to cloud storage?pushToRedisQueue()
: This reveals the underlying queuing mechanism, which might change.
Ambiguous Offenders: Also Bad
doStuff()
: Too generic. Does it sort an array, read a file, or make coffee?calculate()
: Calculate what? Revenue, distance, or the meaning of life?handle()
: Could literally mean anything.
The Gold Standard: Good Examples
sortItems()
: It sorts. How it sorts is its business, not yours.fetchData()
: It'll get you the data. Where it's coming from is not your concern.encryptText()
: It encrypts text. You don't need to know how to use it properly.
Iterations and Integration: The Road to Perfection
Early Integration
Integrating your interface early lets you catch design flaws, helping you iterate towards a more intuitive and flexible design. It's better to realize that calculateRevenue()
is too vague early on than to let it become entrenched in your codebase.
Continuous Integration
The evolving nature of interfaces necessitates a CI pipeline that catches inconsistencies. This automated review means you can adapt without risking your system’s stability.
Iterative Refinement
No interface is perfect from the get-go. By combining early and continuous integration, you can iterate and improve constantly. As you understand more about the system's needs and the developers' preferences, your IRI will get closer to that elusive perfection.
Conclusion
Intention-Revealing Interfaces are an ongoing journey, especially in large, complex systems. They benefit tremendously from iterative design, which itself is facilitated by early and continuous integration. Nailing down the perfect interface is a cycle of planning, integrating, testing, and refining. It’s not about getting it perfect the first time but getting it closer to perfect each time.
Last updated
Was this helpful?