|
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.