For many, the practice of developing software tends to center around technology. We research the latest and greatest programming languages, web frameworks, and text editors. Digging deep into learning the available tools is great fun, and it ultimately results in increased value for our partners, but it only deals with one side of what we do.
In addition to writing code, we work with our partners to determine what their software should do, and how it should do it. These are of course our primary concerns. Each technology stack has entrenched ideas of how the parts of a software system should be laid out, though it’s important to remember that these are just the delivery mechanisms that are responsible for getting the software into the users’ hands.
Be the Master of Your Domain
Once we have defined the goal of an application, our main focus is on modeling the domain of that application. The domain is the space where all of the knowledge and activity of the application lives. It might include a set of complex business rules for running a company’s operations, or it might feature functionality for casual users to obtain information or accomplish a task.
The study of domain modeling is a discipline unto itself. The book Domain-Driven Design, written by Eric Evans and published in 2003, is a mighty tome containing valuable ideas for any software team. Its goal is to help readers deal with the construction and maintenance of increasingly complex software systems.
Some of its ideas are so important that they have worked their way into frameworks that enjoy popular use today. Ruby on Rails developers will quickly recognize the influence of the Entity and Repository patterns on the ActiveRecord instance and class interfaces, respectively. While the book indeed contains technical discussion of Value Objects and Factories, I believe an introduction to domain-driven design should focus on some of the highest-level ideas that permeate the entirety of the approach. It’s those ideas that I’ll focus on here.
Evans writes, “When we set out to write software, we never know enough.”
It’s common to begin a project with a picture of what we’re going to build, only to emerge with a different picture entirely. In the end, however, we have always arrived at a better solution to the problem we’re trying to solve. What drives this process is an openness to learning about the problem at every step.
It’s important to remember that learning can, and must, occur at every level and stage of the project. Working with domain experts will give us enough knowledge to take a stab at a first feature. Review of the feature by the experts will inform further iterations, or in some cases the scrapping of the feature altogether in favor of something different. This feedback loop is vital to understanding the problem space, and ultimately, its solution.
At the implementation level, Evans notes that one outlet for continuous learning is refactoring, about which he has much to say. In fact, Part III: Refactoring Toward Deeper Insight, is roughly one fourth of the book. Refactoring is simply the expression of continuous learning in the implementation. It’s a natural and essential part of development that should be encouraged more often than not.
Continuous refactoring has come to be considered a “best practice,” but most project teams are still too cautious about it. They see the risk of changing code and the cost of developer time to make a change; but what’s harder to see is the risk of keeping an awkward design and the cost of working around that design.
Given this, what guidelines can we use to make a decision whether or not to refactor? Evans offers:
- The design does not express the team’s current understanding of the domain;
- Important concepts are implicit in the design (and you see a way to make them explicit); or
- You see an opportunity to make some important part of the design suppler.
The processes of “making implicit concepts explicit” and “supple design” each warrant their own chapters.
The Ubiquitous Language
In the course of a project, we naturally pick up language from the business or application space. We speak with business experts about accounts, assets, and allocations.
There is a danger that increases as the project runs its course, however, that this language becomes fragmented. Domain experts continue to speak in the terms they use throughout their business, and application developers start to use similar but slightly different terms. The “assets” that were discussed in a meeting might get called “funds” in a database table. Eventually, business folk are speaking one language, and developers are speaking an altogether different one.
This disparity wreaks havoc on team communication. Developers have to remember to translate from their language when speaking with domain experts, or simply suffer the corrections of the experts. Meanwhile, domain experts feel increasingly ostracized by the unfamiliar language the developers are using, which slowly diminishes their motivation and faith in the project.
Not only that, but since an application is constructed using the language of the development team, the final product will not be an accurate representation of the domain.
To combat these dangers, Evans introduces the concept of the ubiquitous language.
Use the model as the backbone of a language. Commit the team to exercising that language relentlessly in all communication within the team and in the code. Use the same language in diagrams, writing, and especially speech.
This language needs to be the same no matter who is communicating. In accordance with the concept of continuous learning, the ubiquitous language may change significantly during the course of the project.
The ubiquitous language need not consist only of the domain experts’ terms. All parties contribute to the language. Experimentation with alternate terms, which may better express a model of the domain, is encouraged. When things start to diverge, the different parts of the team need to agree on what the proper term will be going forward. The important thing is that agreement is explicitly arrived at and adhered to.
When there is a language that grows with knowledge of the domain, and everyone is speaking that language, the team is best equipped to work on the application implementation.
The Implementation is the Model
At the same time that the ubiquitous language is forming, so too is a model of the application domain. Evans defines a model as “a system of abstractions that describes selected aspects of a domain and can be used to solve problems related to that domain.” We fill the model with the important pieces of the domain as it relates to our application, and the interactions between them.
Then, someone writes a line of code.
From there, we need to be on guard for divergence between the application implementation and the model. This is just as dangerous a scenario as divergence of language among team members.
The design of a software system is subject to all kinds of influences, many of which, though convenient, serve to undermine the system’s ability to achieve it’s stated goal. Application frameworks often impose a degree of structure on the applications that use them. Developers bring a particular way of doing things with them from previous projects. Domain concepts are squeezed into preexisting off-the-shelf packages because it’s expedient.
A divergent model and implementation forces those working on the application to maintain knowledge of these separate environments. This additional strain on development promotes increased divergence, to the point that the domain model and implementation become irrelevant to each other. It’s unlikely that an application developed in this fashion will address the needs of its users.
To remedy this, Evans proposes that we keep the software implementation as close to an accurate representation of the domain model as possible.
Demand a single model that serves both purposes well […] Draw from the model the terminology used in the design and the basic assignment of responsibilities. The code becomes an expression of the model, so a change to the code may be a change to the model. Its effects must ripple through the rest of the project’s activities accordingly.
Only when the model and implementation are bound closely can the team add to or change the behavior of the application efficiently. We achieve this fluidity of work by making use of all of the concepts described thus far. We use the ubiquitous language to build the domain model, which helps us to implement the application, all of which feed back into each other in a cycle of continuous learning. Refactoring is the technique that allows us to keep the implementation synchronized with the model.
While I find these introductory ideas to be the most wide-reaching, Evans goes on to detail a wealth of others for helping to manage complexity in our work. These include techniques for maintaining model integrity as complexity increases, the distillation of expanding models into their core concerns, and approaches for supporting multiple models across separate contexts.
These concepts are of great practical value as we reflect on the fact that software isn’t just a collection of features and interfaces, but a solution to a problem within a domain.