Separate Domain From Presentation
William Opdyke's 1992 Doctoral Thesis "Refactoring Object-Oriented
Frameworks" identified eight categories that all refactorings fall
into. Martin Fowler's book, "Refactoring Improving the Design of
Existing Code"identifies 72 specific refactoring patterns, each of
which involve programmer activity in one or more of Opdyke's eight
categories. This article is about one specific refactoring pattern, on
page 370 in Fowler's book, that Fowler named Separate Domain From
Presentation.
No nontrivial project is perfect. Thus, every nontrivial program can
always be improved. And, we're not very good if we can't spot places
where we think improvements could be made. Of course, whether those
improvements should be made, given time and money constraints, is an
entirely different issue. However, we should always be able to spot
candidates for improvement. After all, hindsight is much easier than
foresight.
When I look back on past projects that I have worked on and ask myself
where improvements could be made, and then I prioritize those
improvements and categorize them, the top of the list has always been
domain separation issues. Thus, the single biggest problem in every
past project I've worked on has been the lack of an adequate
separation of presentation code from domain code. In every case, the
single suggestion that would have had the biggest impact on project
quality, would have been to more fully separate the presentation from
the domain. In other words, every past project has suffered
significantly from a lack of proper and complete separation.
This problem is not language specific, as the following poor examples
from Objective-C, C++, Java, and Smalltalk illustrate. Even though the
languages for each project were different, the projects shared a
common set of mistakes: If we can learn to avoid these mistakes, we
will create systems that are simpler and easier to change and reuse.
Java
I once came to a Smalltalk project where the company had decided to
migrate to Java (this was during the initial Java heyday). To
kickstart their effort, they hired a bunch of very high priced Java
consultants to design the project, and serve as Java mentors
throughout the project. The client company believed in OO technology
(that’s why they initially picked Smalltalk), but now they wanted an
OO project using the best technology, which they erroneously were
equating with "the most popular". They were willing to pay for the
best, and that’s what they thought they were getting when they brought
in an established Java firm to architect the migration. Before any
code had been written, I had the opportunity to look at some of the
design documents produced by these consultants.
The design was said to have a "three-tiered architecture", with three
prominent layers in it. One layer had what they called View Data
Objects, and another layer was a hybrid of Views (windows and
associated widgets) together with what they called View Manager
Objects.
Upon inspection of the View Data Objects, I found the only methods
contained in these objects were getters and setters. In other words,
all of the View Data Objects were nothing but data structures, with no
real behavior (I commonly refer to such objects as struct objects, to
signify they are nothing but data structures).
The View Manager Objects were designated as the mediators between the
Views and the View Data Objects, where the Views were the normal
widgets and windows that are (typically) painted with a GUI tool. In
other words, the View Manager Objects were the equivalent of
subclasses of ApplicationModel in VisualWorks (VW), or of
AbtAppBldrView for VisualAge (VA), upon which your painted canvas is
installed. In their design, as near as I could tell, all of their
application behaviors were in the View Manager Objects. That would be
functionally equivalent to putting all of your VW code in your
ApplicationModel subclass, or your VA code in your AbtAppBldrView
subclass.
Thus, every View was a view into a struct object, and the domain
behaviors for the struct objects were written in the View Manager for
each view (window and associated widgets). Additionally, just as the
name suggests, the View Manager was also responsible for managing the
application-specific behaviors of the view, such as programmatic tab
sequencing and the disabling/enabling of buttons and text fields based
on user input. Thus, the View Manager was tightly bound to the View,
just as its name implies that it would be, and also had domain code
interwoven within it.
The documents showed no layer distinction between the Views and the
View Manager. The third layer was said to be the database itself (a
major RDBMS). Thus, the layers, as defined by their documents, were as
follows:
- Views and View Managers
- View Data Objects
- RDBMS
Part of the responsibility of the View Manager Objects was to move
data between the data objects and the database, and process the data
as needed during the transfer. In other words, the View Manager
Objects were the data processors in a classical "read data/ process
data/ write data" cycle typically found in procedural code.
In spite of what the documentation said, this was not a three-tiered
architecture. It was a classic two-tiered architecture that we see so
often with tools like Visual Basic or PowerBuilder, where the
processing code is intertwined with the display code. It was not even
an OO architecture. So much for high-priced Java "experts"!
C++
Now I’ll back up a few years and tell about a C++ project. I was asked
to comment on and document a C++ class library that was being written
to replace a solid (but procedural) C library they had been using. The
primary architecture for the C++ project was a normal C procedural
design, but with the struct keyword changed to a class keyword, and
accessors written. That’s it. Nothing else was different from a
standard C program.
Thus, struct objects were everywhere, with nothing but getters and
setters, and such objects were typically considered to be the domain
layer, just as the Java project would do later.
What do you suppose my recommendations were for this project?
I was ignored, so staff meetings for this project were very
frustrating. I once exclaimed: "there should be no publicly accessable
accessors in the code!". Yes, that statement was false, and I knew it,
but I was trying to drive a point home. Most of the people in the
staff meeting never questioned me further, and just went away thinking
I was nuts, but a couple of people came to me in private later and
asked me to explain what I meant. My response was:
Think about it if you don’t have accessors, where must you put the
code?
In both cases, they paused and considered what I had just said, and
then the light turned on. They understood what I meant. The answer was
obvious the code had to be put into the class that was managing the
data. Without accessors, there was no other place the code could be
put!
Objective-C
Now I’ll back up a few more years to an Objective-C project. This was
a NeXTSTEP application that followed the same poor architecture of the
Java project that I mentioned first. To give you some idea of the size
of the application, it encompassed approximately 28,000 lines of
Objective-C code, and had 15-20 major windows in the user interface,
and many minor windows in it. Each window of the application was built
separately using NeXT’s Interface Builder tool. This tool would save
the interface into a file that NeXT called a NIB file (NeXT Interface
Builder file), which again is analogous to the ApplicationModel
subclass in VW or the AbtAppBldrView subclass in VA. It used a
separate class for each window to interface that window to the domain,
and this class was abstractly called the NIB Manager, because it
"managed" the NIB file (the actual class name for the NIB manager of
each NIB file varied, and it usually bore a name similar to what was
in the title bar of the window that was being managed). Since each NIB
file had exactly one window defined in it (and occasionally an
additional dialog or two), each NIB Manager interfaced exactly one
window to its dependent domain "data" objects. Hence, the architecture
had:
- the window and it’s widgets, which corresponded to the View
objects in the Java example.
- the NIB Manager, which corresponded to the Java View Manager
- data structures to hold view data, which corresponded to the Java
View Data Objects
Hence, this project suffered from all of the same design problems that
the Java project did.
Smalltalk
Now I’ll fast-forward to a project that came after all of the projects
already mentioned. This was a Smalltalk project. On this project, the
commendable decision was made by executive management to actively
support "re-use". Of course, executive management also understandably
wanted to leverage their existing legacy staff a staff that was not
versed in Smalltalk. Management had also heard (and unfortunately
believed) that Smalltalk was a "difficult" language to learn.
Fortunately, they also believed that once you learned the language,
you could develop much faster with Smalltalk than with other
languages. But they also believed the myth that the implementation
language was irrelevant to the design, and that you could design
independently of the implementation language. Hence, they believed
that all of their prior experiences with procedural languages
qualified them to mandate the design and architecture of this project.
Now, with all of these beliefs, it should not come as a surprise what
architecture was eventually mandated by management.
The legacy team was tasked to create a set of "re-usable RPC services"
that were to reside on the legacy machines (IBM mainframes), and were
to be written in the legacy language (COBOL). Those services would be
callable via standard RPC mechanisms. Thus, those services were
exactly as the "RPC" name implies Remote Procedure Calls and were
semantically just a set of classical subroutine calls into a
subroutine library. The fact that those subroutines executed on a
remote machine was irrelevant to those semantics. A service call was
just a conventional subroutine call.
The Smalltalkers were then tasked with creating the GUI to call those
services. Hence, the Smalltalk program was to be presentation only,
with no domain logic. And the actual domain logic was to be in a layer
that was just a procedural, classical subroutine library, written in
an entirely different language.
At first glance, this project appeared to at least recognize a
distinction between the domain layer and the presentation layer. But
when minor domain behavior changes or additions were needed by the
Smalltalkers, it was easier for them to just throw some minor changes
into their ApplicationModel subclass that was driving the window than
it was to push for the needed changes in the COBOL services. Thus,
some of the domain behaviors became intermingled with the presentation
logic, and the balance of the domain behaviors were in an RPC
subroutine library written in COBOL.
Confession
I was the architect of exactly one of these projects. I was the one
that designed the Objective-C project. It was my first foray into
OO-land, about ten years ago (with the exception of dabbling with C++
in college a few years earlier), and you can see it wasn't OO at all.
I didn't even know how to do OO at that time but I thought I did. And
that, I believe, is the main reason that we see these same mistakes
over and over those other designers (and often management) think they
know OO, but don't.
Among other problems, every one of those projects suffered from the
lack of an adequate and crisp separation of presentation code from
domain code.
So what should we do about it?
Once And Only Once
"Once And Only Once" for the old RDBMS crowd is represented in their
mathematically rigorous theories of normalization. The whole impetus
for relational database normalization is so that data appears "Once
And Only Once" in the database, because otherwise there are
well-documented "update" anomalies that can occur, due to the classic
two-space problem created when the same data exists in more than one
place.
What many people miss, though, is that this same two-space problem
that normalization is supposed to correct for data can also exist for
code. If you don’t practice "Once And Only Once" for code, a very
similar set of anomalies can occur, where the behaviors can get out of
sync. "Once And Only Once" for code is the next logical step down the
evolutionary path begun many years ago by the RDBMS crowd but this
time it is for code instead of data.
"Once And Only Once" is the basis of good design. It is foundational,
and is the feature behind what makes the OO paradigm what it is. For
example, in the simplest case, proper use of the OO paradigm
eliminates the repetitive use of "if", "case", and "switch" statements
(or their equivalent in whatever language you want to consider), and
instead implements the equivalent functionality within the messenger
itself. Proper use of the OO paradigm forces a "Once And Only Once" of
these kinds of control structures into a single replacement
mechanism-- the messenger itself.
Additionally, static languages (such as C++ or Java) mix the notions
of classes and typing, and try to make them mean the same thing. They
are forced to do this so that they can annotate variables with type
information, and in so doing, they scatter type information all
through the program type information ends up in each variable
declaration as well as within the class declarations. For example,
consider the following hypothetical code snippet:
Graph myGraph = new Graph();
Why should I have to say "Graph" twice? This is a clear violation of
"Once And Only Once".
In contrast, with Smalltalk the concept of "substitutability" (and
therefore "subtyping") is not associated with the inheritance
hierarchy, and types are encapsulated with the objects themselves.
This means that should a type change later be necessary, then
Smalltalk has succeeded in better localization of the change than Java
or C++. This difference can clearly be seen if the process of making a
change to a method signature in a class requires all clients of the
affected class to recompile, even if they don’t use the method that is
being changed! Such constraints don’t exist in dynamic languages like
Smalltalk the only clients that are effected in this case are those
that actually use the method that was changed. This difference is
because of a closer adherence to "Once And Only Once", where type
information isn’t replicated with every variable declaration. Thus,
static OO languages by their very nature unavoidably introduce
two-space problems into your program, and this is precisely why
programs created with them are generally more brittle than programs
created with dynamic OO languages such as Smalltalk.
Practicing "Once And Only Once" will also force you to gather the
domain code into one place the domain layer because otherwise the
domain logic gets scattered all through the other layers. There is no
way to practice "Once And Only Once" without gathering the domain
behaviors into one place.
Of course, it is not difficult to introduce two-space problems into a
poorly built Smalltalk program. Problems caused by bad system design
can dwarf the magnitude of the inherent two-space problems built into
a well-crafted Java or C++ program. We need to avoid making these
mistakes, otherwise much of the advantage of using Smalltalk is lost.
And how do we do that?
We do that by practicing "Once And Only Once". And, the most
significant application of "Once And Only Once", and the one with the
most far-reaching effects, is to gather all of the domain behaviors
into one place the domain layer.
The Domain Layer
So what exactly is the domain layer? What behaviors belong in the
domain objects?
The domain layer, properly done, is the life and intelligence of the
object system. Properly done, it is the program! And, it has no
particular user interface! It doesn’t care about the nature of the
user interface, or even if there is a user interface. In an
environment where the object paradigm is the only paradigm in the
system, the domain layer is what is left after you’ve tried
relentlessly to make the rest of the system so simple it couldn’t
possibly break.
Anytime the Presentation Layer (typically a GUI) ends up with code
that is not so simple it couldn't possibly break, treat that situation
as a code smell, and try to fix it by simplifying the GUI code and
moving the code to the domain layer. What I have found, is quite often
you think you need a piece code in the GUI, but it turns out that,
given a little more thought, it was actually possible to move it to
the domain layer, and in the process keep the GUI code so simple it
can't possibly break.
What is invalid data? What data is mandatory? Only the domain should
know. If you embed this knowledge in the GUI, you will violate "Once
And Only Once". What if the rules change? Do you really want to track
down every place in the GUI where the validation knowledge is present?
The GUI is the wrong place for validation logic.
Also, there is no particular need, aside from human convenience and
human aesthetics, for the user interface to even be graphical. The
user interface could be command line oriented, if you wish it to be.
Thus, it should be possible to exercise the program from a Smalltalk
workspace.
The domain layer is the program, and any other code in the system
should border on the trivial. If you don’t have it that way, then you
probably haven’t completely separated the domain layer from the rest
of the system.
Separate Domain From Presentation(370), is a refactoring near the end
of Martin Fowler’s Refactoring book, in a chapter that was co-authored
by Kent Beck. That chapter is entitled Big Refactorings, and Separate
Domain From Presentation it is indeed a big refactoring. Many
refactorings can be mechanically done, as is evidenced by the
existence of the Refactoring Browser. But Separate Domain From
Presentation(370) cannot be mechanically done. Only a general
guideline of the mechanics of the refactoring can be given.
If you are presented with a program that does not have the domain
separated properly, typically it will be because the domain code is
intertwined with the display code. For this situation, the mechanics
of the refactoring are as follows:
Mechanics
* Each window of the GUI of your application will have an
overriding theme. It could be anything, but a few examples might
be a customer name-and-address theme, or an application security
theme, or a clerk setup theme, or a preferences theme, or
anything else. You need to discover the major theme of each
window. That theme very likely will identify a domain class.
Create the domain class represented by the major theme of each
window.
* For each window, identify the data that the window uses. Of that
data, identify which of it is domain data, and which of it
should be private to the user interface, and which of it is
needed for both. Create an instance variable in the domain class
for each piece of domain data, even if it is also needed
directly by the user interface.
* Initially you will need to create, as a minimum, getter methods
for the instance variables of your domain class, and you
probably will need setter methods as well. Of course, the use of
these methods outside of your domain class is a violation of
encapsulation, but until you get all of the domain behaviors
properly moved from your window manager (subclass of
ApplicationModel for VW or AbtAppBldrView for VA) into your
domain class, you have no choice. You might be able to refactor
out the external use of these accessors later, but for now, your
window manager will need to be able to access the raw data of
your domain, just as it is doing now, and process that data.
Thus, it will need accessors.
* Change the window manager class to access the data from the
domain class via the domain accessors. In doing so, you will
also be identifying all of the code that accesses the data.
* Now that you have identified the code that accesses the data,
move that code down into the domain class so that external use
of the accessor is no longer needed. If your window manager
class contains SQL code, all of that code needs to be moved to
the domain object.
* As a test to determine if you have moved all of the code that
you should, you should be able to modify the getters of your
domain class to just return copies of the domain data instead of
the actual domain data, and making that change should not impact
the operation of your program. If making that change does impact
your program, then your window manager class is directly
manipulating domain data instead of letting the domain class do
it. You need to break that coupling.
* At this point, you should have a separation of GUI from the
domain, although the domain classes will probably not be well
factored. Factoring the domain classes is a different set of
refactorings to be done later.
Example
Here is an example of a screen of a Preferences window used in a
VisualWorks application. The window manager class for this window is
PreferencesUI, which was built as a subclass of the VisualWorks
ApplicationModel class.
The main theme of this screen was factored out into the domain class
called Company. Looking at this window, and the data held by the
window, immediately revealed two additional domain classes:
USPhoneLine and USPostalAddress (actually, they began as Phone and
PostalAddress, but changed to USPhoneLine and USPostalAddress at a
later date). Domain and range constraints that were originally
embedded in the GUI were moved to their respective domain classes, and
the properties of all the textfields in the GUI were switched to
display and hold strings only. For example, the PhoneLine class was
given the ability to separate a string into the
AreaCode/Exchange/Station/Extension components of a phone number, and
would echo back a properly formatted string of the current phone
number when requested to do so. Thus, when the GUI would "Save" a
phone number, it handed the string to the domain, which was an
instance of the Company class. The company would then instantiate a
USPhoneLine instance, passing to it the string that it was given. The
USPhoneLine class would interpret the string while it constructed an
instance, and the company would answer that formatted interpretation
back to the GUI. The GUI would then redisplayed whatever the domain
responded with, so that the user could see what the domain’s
interpretation of what they had typed in was.
Likewise, whatever was typed in the "State:" textfield was just handed
to the domain (an instance of the USPostalAddress class, in this
case), and the domain would echo back its interpretation of it. If the
domain could make sense of it, a two-letter abbreviation for the state
that was recognized was answered, and that interpretation (the
two-letter state abbreviation) was redisplayed so that the user got
immediate feedback.
The other widgets followed a similar design, wherein they accepted
whatever the user typed in, and just handed it to the domain without
otherwise trying to interpret the data. It was up to the domain to
interpret the data, and the GUI just redisplayed whatever the domain
decided its interpretation was. For example, the code to process the
"Minimum Acceptable Customer Age:" was just:
minCustAge value: (company minCustAge: minCustAge value).
In this code snippet, the string in the ‘minCustAge’ textfield is
accessed and passed to the company. The company looks at the incoming
data, and makes a determination of what to do with it based on the
business rules it knows about. It then answers its interpretation of
the data passed to it, and that interpretation is then redisplayed to
the user. The GUI doesn’t otherwise look at or try to interpret the
values at all.
The net result was that the GUI was dramatically simplified, with most
of the code moving to the domain layer. And, about half of what was
left of the GUI code ended up in the #save method that was triggered
when the "Save" button was clicked, and all of that code just blindly
moved data to the domain and redisplayed the domain’s answer, just as
the code snippet shows.
Of course, this design approach meant that the application became
screen-oriented, and didn’t give feedback to the user until the "Save"
button was actually clicked, because it was at that time that the
screen data was sent to the domain for its interpretation. But, this
application was later webified, and this screen-oriented behavior was
actually desired, because HTML web browsers are screen-oriented.
It worked well.