Slow-Moving Targets

Tuesday, September 07, 2004

Kicking It Up A Notch (Part 2: The Essence of MDA)

Pattern-Happy

We've applied transformation rules to a conceptual model of the entities in our system (see my previous post). These rules transform an abstract model into a more concrete model. Another set of rules translates this more concrete model into code in the form of Data Description Language (DDL). Wonderful! But what about the rest of the system?

Many software methodologies or processes apply the same technique to a model of the software that will read and write customer records. They start by drawing the boxes and lines to represent the software in a model. Sometimes they leap from there, to code, and sometimes they refine the model into a more detailed, more specific model. This specific model will naturally be filled with allowances for the target implementation platform. At this point, I should apologize. I've switched directions from my previous post and started to write of top-down construction. We were in the process of zooming out, so let's go back to our working system and look at the software side of things.

We're staring at hundreds of Java classes in this small Customer Relations Management system (CRM). The way our system was built, there's a small component for each table in the system. We notice that each component has the same three or four classes and implements some common interfaces. For each component, one of the classes makes JDBC calls, one translates between a JDBC record set and a Java bean, and another class provides a set of operations to the outside world for the table it embodies.

If this sounds familiar to you, you're absolutely right. This is a form of the Data Access Object (DAO) pattern for Java persistence. (Please read this post as orthogonal to the religion of persistence frameworks. Personally I'm partial to Hibernate, but DAOs provide an easy example.) As we look beyond the customer component we can spy a few other patterns. The Data Transfer Object (DTO) pattern ("orthogonal..." think "orthogonal") works in combination with the DAO pattern. We see a sort of wrapper class that implements caching while simplifying the programming interfaces to the DAOs. This is a combination of the Facade and Decorator patterns.

Whoever built this system really knew what they were doing. It may not be the best or the coolest way to build a Java application, but this code is very consistent. It is easier to maintain a system when you can readily understand the code, and consistent code is much easier to understand. If the code is well-factored into applied design patterns, so much the better. These design patterns allow us to get a wider perspective on how the pieces of our system work together.

OK, so an experienced developer or an architect made some decisions that resulted in a consistent application of technology to the business domain. In this software, our domain concept of the customer lives across many classes.

  • Customer DAO
  • Customer DTO
  • Customer decorating facade
  • Customer UI classes and JSPs
  • and more...

  • I'm going to refer to this set of technology design decisions as the architecture of the application. This includes those choices such as the use of relational databases over flat files or OODBs. But repeated application of the architectural design to the domain concepts resulted in the well-factored code we see in this CRM application.

    This leads us to another example of repetition. Patterns in the code we're examining can be identified and factored out in a manner that allows us to specify the combination of a domain concept (Customer) with a set of collaborating patterns. The work of producing the code, however, requires that we repeatedly interpret the intent of the patterns. We should automate this somehow.

    Driven to Abstraction

    We resolved this sort of repetition when building the database by representing the concepts we cared about as models. From a fairly generic (yet well-formed) model of our domain concepts, we automated the creation of a more specific model. From that specific model, we generated working code in the form of DDL. We need to go through this abstraction process again for the software. We'll find, however, that the mappings for software are slightly more complex.

    A set of design patterns and idioms collaborate with each other to implement the system. These patterns and idioms are parameterized with details specific to each persistent domain class. Let me rephrase that: These patterns and idioms are parameterized with a model. We can factor out the details of a how a pattern collaboration is parameterized from how its members actually collaborate to achieve the solution to a generic problem. But where do we put the details of the pattern collaboration? In the code generation templates, of course.

    We have a model that has enough of the details of our target implementation to control how the implemented code comes out. We see model elements like DataClass (corresponding to the DTO), DAOComponent, DAOFacade, and WebComponent. These model elements have rules that say how they fit together, and how the combination of properties on them may be set. But it is the code generation templates we have lashed up to these metamodel elements that assigns meaning (or semantics) to them.

    Now we have the knowledge of how to implement (pattern collaboration), and what to implement (abstract model) neatly factored out, each one from the other. We now have a very easy way of applying an application design consistently.

    We're still aiming to drive out repetition, however, so we have to find a way to factor out the actual domain notion of Customer from these special purpose models. While we need to have a CustomerDataClass and a CustomerDAO, we want some way to express that any time we encounter a persistent domain class in the system, it ought to be handled this way by default. We solved this problem with relational database design and implementation by having two kinds of models, a logical and a physical and having rules that mapped out how one could be created from the other.

    Naturally we want to do the same thing here, so we use a very generic class model where we represent the core characteristics of Customer. We have a set of rules that map out what model elements to create in our more concrete models. But why this extra step? Why not simply throw that conceptual (domain) model straight into the code generation templates?

    The reason is that not every part of the pattern collaboration may apply for every occurrence of a particular model element. For example, perhaps we don't need a decorating facade for domain classes that are owned in a composite relationship. We could add this kind of knowledge to the pattern collaboration, but that would make them far more complex. It is much easier to indicate these choices with the presence or absence of model elements. We could add this information to the domain model, but now we have knowledge in the domain model that doesn't really belong there. After all, the fact that CustomerCall doesn't get a decorating facade isn't really one of the core characteristics of CustomerCall, is it? It is a characteristic of the implementation. That decision belongs in a mapping, so we'll put it there.

    We now have four different kinds of models (at least). We have a logical database model and a physical database model, a domain class model, and an implementation-oriented software model. Rule sets map some implementation decisions for us and code generation templates produce the actual implementation (whether it be DDL or Java code). Our work isn't done, though. We have repetition of the Customer concept between the domain class model and the logical database model.

    Solving this repetition is fairly easy. The trick is to have one of our highly abstract models serve as the source for mappings to many kinds of less abstract models. In this case, our domain class model probably has enough information to fill this role. The mappings between the domain model and the physical DBMS model become a little more complex, but object-relational mapping is a well-worn art that can be automated suitably.

    We've done a fairly good job of factoring knowledge in our development framework. We have a domain model where we capture the information specific to the requirements of the domain. We have mappings that identify large-grained architectural choices, such as mappings to a relational database model, and mappings to a specific kind of Java software model. We have models that contain enough information to effectively parameterize a series of detailed design decisions captured as code generation templates.

    But what about that Order Entry project coming next month? We'll have to go through this process of abstraction all over again. This means we have one more significant level of factoring so that we may reuse the approach. We'll discuss this in Part 3.