When you are coding in a hurry, it is very tempting to write business logic in the first place that comes to mind, such as a button click handler. However, for all but the simplest systems, such a practice leads very quickly to a chaotic system whose business logic is scattered like the ash from an erupting volcano. So let's take a step back and see how you can develop an application more intelligently.
"Divide and Conquer"
“Divide and Conquer” is a useful metaphor for organizing the effort of developing a complex system. A software architect can assign major system responsibilities to various components or layers (which I will refer to as a “part”). As long as the responsibilities of a part are understood, a developer can develop the part in such a way as to satisfy those responsibilities. And as long as the parts integrate well, a useful system can emerge.
“Divide and Conquer” has many advantages:
- A team can work in a coordinated fashion on several parts of the system simultaneously, without stepping on each other’s toes.
- Assigning a limited set of related responsibilities to a system part simplifies its design.
- Modifying a system is easier when the part that is responsible for the functionality that must be changed can be identified.
- Ultimately, the complexity of the system decreases as its various responsibilities, and the strategies for handling them, become clear. Unnecessary complexity is what can make “the last 10%” of a project go into deep cost and time overruns, as quality issues and poor design make their effects known.
“Divide and Conquer” is the driving force behind creating logical layers of a user interface, an application layer, a data layer, and a domain model in an enterprise software application.
While the advantages of separating UI, application, and data logic into their own layers are well understood and frequently practiced, the point behind creating a domain model is not as widely grasped. A domain model defines the business entities represented by a system, the activities associated with them, the relationships between them, and the rules and constraints that govern them. These are clearly very important aspects of the system, even though (from the end user’s perspective) they are not as visible as the user interface, application flow, or database. I will illustrate the importance of a domain model by examining how insurance endorsements work in the homeowners point-of-sale system that my team has developed.
Example of a Complex Business Domain
Let’s start by examining the data model for endorsements:
- An endorsement can be associated with either an application (future policy) or with a risk (such as a dwelling or a vehicle). Therefore the Endorsement table has 2 foreign keys: one that relates an endorsement to an application (via the Quotenumber) and the other that relates it to a risk (via the RiskId).
- Endorsements come in a bewildering variety. Some endorsements have no related data; some have one or more limits; some have one or more attributes, few of which are common between endorsements. Clearly it is unwise to attempt to create a single table with identified columns for each of the possible limits or attributes that an endorsement may possess. In addition, any attempt to create a unique table for each endorsement’s associated attributes is going to result in a very large number of tables—along with the responsibility of defining a new table each time a new endorsement type is created. Therefore, the design we chose was to create 2 dependent tables for Endorsement: an EndorsementLimit table and an EndorsementDescription table. Each table has 3 fields: an EndorsementId (foreign key back to the associated endorsement), a “type” field that allows the system to distinguish between all the related descriptions (or limits) of an endorsement, and the associated data field (a numerical limit or a varchar description). Note that segmentation and history are irrelevant to a point-of-sale system, so the schema is not designed with that capability.
- Some endorsements are concerned with parties to an insurance application other than the applicant, such as an additional interest or an additional insured. Since these additional entities reside in a different table (the OtherParty table), we use a join table (EndorsementParty) to manage the association between Endorsement and OtherParty.
This portion of the database schema is represented graphically below:

So if we have a data model, why do we need a domain model? Why can’t some UI code simply write its data directly to the tables? Clearly this one way of designing a system—you can, after all, do anything with software (provided you have sufficient time and resources).
Let us consider, though, the knowledge about the business domain that the point-of-sale application must manage with regard to endorsements:
- The relationship of endorsement attributes to an endorsement. A homeowners “HO 05 28” (golf cart) endorsement has several related attributes: a limit (cart value), a make, a model, a serial number, and an indicator of whether collision coverage is included. Each one of these attributes is stored in its own record in the EndorsementLimit or EndorsementDesc table. The DescType field distinguishes the four EndorsementDesc records from one another; however, the system must somehow keep track of which DescType is used for which attribute.
- Differences between the format of stored data and how it is used. In a database system that does not have a built-in boolean type (such as the DB2 flavor used at my shop), there are probably as many different ways to store boolean data as there are programmers. True vs. false could be represented as “0” vs. “1”, or as “1” vs. “2”, or as “T” vs. “F”, or as “t” vs. “f”, or as “true” vs. “false”, or as “Y” vs. “N”, etc. (In one of my consulting engagements, I actually saw all of these plus a couple more in a single application.) To handle a golf cart endorsement’s indicator of collision coverage, the system must be able to reliably map between the stored data and a run-time choice of true/false.
- The relationships between endorsements and other entities. Some endorsements are related to the risk being insured (such as the earthquake endorsement), and some are related to the (potential) policy itself (such as an “Additional Insured” endorsement).
- The relationships between endorsement attributes. The “HO 04 65” endorsement (scheduled property) contains a set of schedules; each schedule is designated by a letter such as “A” or “J” and has a description (the personal property being insured) and a limit. Since descriptions and limits are stored in separate tables, the system must have some way of identifying and associating the schedules’ descriptions and limits.
- Rules and constraints on various endorsements. The 6 schedules on a “HO 04 65” endorsement have base (default) limits, for example, such that the total limit of a schedule can be described as the base limit plus an additional limit. Mathematically speaking, we use the formula Total Limit = Base (fixed by rule) + Additional (stored in EndorsementLimit table). So the system must be able both to provide base, additional, and total limits (given the stored additional limit) and to update the stored additional limit (based on user entry).
Complexity Resolved: A Domain Model
The beauty of our team's domain model is that it completely hides all of this complexity from the user interface.
- An endorsement class is defined for each type of endorsement, and it provides an interface that is radically simple for the UI developer to use. To give just one example, the golf cart endorsement provides a boolean CollisionIncluded property that can be mapped to a checkbox.
- All of the other issues (which EndorsementDesc record the indicator is stored in, how the boolean is mapped to a varchar, etc.) are managed inside an endorsement class—frequently by using capabilities inherited from the EndorsementBase class or borrowed from utility classes in the domain layer.
- Responsibilities are clearly grouped and assigned in this way of designing a system. Tasks and attributes that are common to all endorsements are the responsibility of the EndorsementBase class. The tasks and attributes that are unique to a particular endorsement are the responsibility of the corresponding subclass of EndorsementBase (which already has the common endorsement capabilities via its inheritance relationship with EndorsementBase). Neither of these classes needs to know anything about the application or user interface which will use them, which in turn need to know nothing at all about the internal implementation details of an endorsement class. The diagram below depicts the assignment of some endorsement responsibilities to classes in our domain layer.
- The association of an endorsement with a policy or with a risk is managed via its membership in an EndorsementCollection that is accessed via the HomeApplication object or the HomeApplication.Home object (of type HomeRisk), respectively. The relationship between these various entities is depicted below.
Conclusion
There are some things that should never leak. Space shuttle O-rings. Tires. Diapers. And now that you've read this article, add your business domain model to the list. Always isolate your business domain logic in a cohesive model; do not let the logic leak into a "smart UI" or into the data layer! If you follow this rule, you will save yourself and your team much time and effort when you need to modify the business logic in your application.
Also, keep the importance of your domain model in mind when it is time to choose a development tool. A rapid application development (RAD) tool that can only design screens and logic flows might be a very good choice for building a very simple system that needs little complexity in its business logic. However, it is very difficult to envision how a RAD tool would be able to represent the relationships and constraints inherent to a complex business domain, such as homeowners insurance endorsements. Given the inherent limitations of a RAD tool, the complexity that my team's domain model currently manages quite capably would have to spill out into the flows and screens themselves.
While the result may initially seem counterintuitive, a RAD tool that at first glance makes everything look simple might very well in the end produce a system far more complex than a system produced with a set of tools that offer a robust and complete set of programming capabilities. And this unanticipated complexity can make the systems you develop far more difficult to implement on time and to extend with new customer-requested capabilities.