DekGenius.com
Previous Section  < Day Day Up >  Next Section

4.3 Alternatives to Transparency

Most people use the term transparent in a very specific sense, referring to code that does a job without explicitly revealing the details of that job. Distributed code with location transparency does not explicitly refer to the location of other machines on the network.

Consider a specific example. Persistence frameworks let you save Java objects. If you don't have to build in any support to get those benefits, then you've got transparent persistence. You'd think that you've either got transparency or you don't, but unfortunately, it's not always black or white. You may need to make some minor compromises:

  • Your code may be transparent, but you may have to deal with minor restrictions. For example, some frameworks use JavaBeans API, and require getters and setters on each field (such as earlier versions of Hibernate).

  • You may have to deal with major restrictions. Some frameworks don't support threading or inheritance, like EJB CMP.

  • You may need to add special comments to your code. XDoclet relieves some limitations in other frameworks through code generation, but forces you to maintain specific comments in your code.

  • You may have to make minor code changes, like supporting an interface or inheriting from a class.

  • Your framework may generate your code, but you may not be able to modify that code and still support regeneration, like many IDE wizards.

  • You may need to change the build process, such as in frameworks with code generation like Coco Base, or frameworks like JDO with byte code enhancement.

Some of these restrictions are minor, but some are severe. Before I dig into techniques that promote transparency, you should know about other available techniques, and their possible limitations.

4.3.1 Techniques That Compromise Transparency

In the area of persistence strategies, all frameworks must make serious compromises. The most successful tend to provide the best possible transparency within the business domain model, with respect to persistence. Some of the less-successful techniques invade your programming model in awkward ways.

4.3.1.1 Invading the model

You may suggest that transparency is not important at all, and you should just add code to the model itself. You could just hardwire create, read, update, and delete (CRUD) methods directly to the model. But keep in mind that persistence is not the only type of service that you'll likely need to add. You'll also have to consider security, transactions, and other enterprise services.

Many of my customers applied this approach before calling me to clean up the mess. The problem is that each class gets large and unwieldy, because code for each aspect invades each individual class. It's tough to get a consolidated view of any one particular problem. Additionally, you end up with a quagmire of redundant code. If you suddenly change databases from SQL Server to Oracle, you might find yourself editing each and every class in your model.

4.3.1.2 Subclassing

If you want to build something that's persistent, you could use a persistence framework that forces you to inherit that capability—for example, from a class called PersistentObject, as in Figure 4-3. This creates a problem: since classes can only support single inheritance, you are limited in your choice of inheritance hierarchy. You also do not support true transparency, because you need to make a conscious decision to inherit from PersistentObject. The result works, but it complicates your designs.

Figure 4-3. The persistent object Dog inherits from the class PersistentObject, but you can only add one type of service
figs/bflJ_0403.gif


4.3.1.3 Building a hardwired service

You could keep the model transparent, but build knowledge of each class in your model into your service layer. This technique is a brute-force approach. For example, you could build a data access object called PersonDAO that knows how to store and retrieve a Person object. That DAO would return Person objects so you could deal with the model transparently. That's often a workable solution. It's probably the preferred solution for simple problems, and for novice and intermediate developers. It's also the solution used in the Account example in Chapter 3.

This approach does have real benefits. It's easy to understand and easy to implement for smaller solutions. It leaves you with a transparent object model. The specific service approach does require you to do more work to implement each service. You can imagine how easy that it would be to load a simple object like a person, but loading a complex object with many parts, like a car, would be much more difficult. With a specific service, you have to manage complexities like this yourself. In this case, you need to add that persistence code to each class in your model, and hardwire each relationship by hand. As your requirements get more complex (such as adding caching or an ID generator to your persistence layer), you'll want to think of a more general solution to insulate you from these types of tedious details.

4.3.1.4 Code metadata

One strategy for persisting a model is to add metadata to pieces of the model that need special treatment, and then generate the code for persistence. For example, you may need to mark persistent classes, and mark each persistent field with a comment. If you're looking at this technique purely as a means to provide transparency, then it fails, because you need to change your programming model. It only provides an alternate means for coding.

Although these techniques do not improve transparency, I do recommend the combination of code generation and metadata, because it can relieve you from tedious implementation details. If you're not already familiar with persistence frameworks, here's a little background. Nearly all persistence frameworks make you build at least three things: the model, the database schema (the tables and indices), and a mapping between the two, often in XML. With metadata, you can automate two of the three artifacts. Using a tool like XDoclet, for example, you can add comments to your code that mark a field as persistent, and describe the relationships between the table and the code. In a pre-compilation step, XDoclet can then generate the mapping and schema based on these comments.

Be aware of what you're giving up, though. Inserting metadata actually moves some configuration details into your code. The line between metadata and configuration becomes blurry. For example, you may like metadata if you've got the responsibility for creating both the code and the schema, because it can help consolidate the mapping, code, and tables in one place. However, the approach tends to couple the concerns of the domain model and persistence, and that can bite you. For example, at some future date, you might not maintain the database schema. Then, you would prefer to keep a separate mapping, so when the schema changed, you'd often need to change only the mapping. The moral is to use the technique to save on redundancy where it makes sense, but be careful.

The metadata problem comes up regularly. Marking future requirements within code, marking persistent fields, and highlighting certain capabilities of a method, class, or property are just three examples. JDK Versions 1.4 and before don't have an adequate solution. For example, the Java language uses naming to tell the Java reflection API that a method supports a property—if get or set precedes a method name, then it's treated as a property. The XDoclet tool is growing because Java developers need this capability.

A committee is looking into adding metadata directly to the Java language in a specification request called JSR 175. For example, when you create a DAO, there's no good place to keep the JDBC connection. You can sometimes solve this problem through instrumentation. In order to define a metadata attribute called Persistent, create an interface like this:

@Documented
public @interface Persistent {
  public String jdbcURL( );
  public String username( ) default "sa";
  public String password( ) default "";
}

In order to use the interface, add it to your class like this:

@Persistent(jdbcURL="jdbc:odbc:MyURL", username="btate", password="password")
public class Person
{
              // enter the rest of the code here
}

You can access the attribute through reflection. Then, you won't have to pass the JDBC connection through to each DAO. You could potentially use this technique the same way people use XDoclet today. The difference is that the Java compiler itself would be examining your attributes, not a third party.

4.3.1.5 Imposing an invasive programming paradigm

Many people have tried to solve the problem of crosscutting concerns. Some of those attempts actually improved our lives, but many were mediocre or downright awful. For example, Java's first attempt to solve crosscutting enterprise concerns used components. The idea makes sense. You can build a container that supports services. When you add a component to the container, it has access to the services, without requiring any code changes to the model. Your components are transparent (Figure 4-4). The devil is in the details, however. This approach depends heavily on how you define a component.

Figure 4-4. Components can theoretically access services without knowledge of the service
figs/bflJ_0404.gif


Java's most ambitious attempt at component-oriented development for the enterprise, EJB, has been in many ways a disaster. Here's how it works: the container accepts EJB components. To build a component, you define an interface that conforms to the specification and build a separate implementation. You then bundle deployment details in a configuration file called a deployment descriptor. When you deploy the EJB, the framework generates a component that uses your implementation and the interface you defined.

In this model, the container accepts frighteningly complicated components and the programming model blows away any notion of transparency. For example, to create a persistent component, a developer needs to build five Java classes, plus a deployment descriptor, plus the database schema. Further, the component model does not completely hide the service from the implementation. To use the persistence service, you must implement the entityBean interface and create a primaryKey class. This approach is not transparent. It's invasive.

Finally, you cannot readily extend or modify the services of the model. They are so integrated and coupled that it's nearly impossible to inject new services or modify the old ones in meaningful ways. Commercial vendors don't encourage you to even try. For example, RMI is the communication protocol for EJB. It's so tightly integrated with the container and other services that it's nearly impossible to replace.

Table 4-1 shows a summary of the alternatives that limit transparency. Notice it's not all cut and dried. Usually, generating transparent code takes time and effort, so save that technique for the problems that will benefit it most.

Table 4-1. Implementation techniques that reduce transparency

Technique

Advantages

Disadvantages

Invading the model

Easy to build

Model becomes too complex

Maintenance gets tougher

Combines concerns of services with model

Subclassing

Provides services without additional coding

Uniform interface

It complicates introduction of new services

It abuses inheritance, leading to confusion

It imposes code changes on the model

Hardwired service

The model remains transparent

Changes in the model also force changes in the services layer

Instrumentation

Reduces replication of code

Consolidates code and configuration, often easing implementation

Imposes code changes related to a service on the model

Couples configuration and code, possibly complicating maintenance

Imposing invasive coding models

Subject to implementation

Subject to implementation


In general, as your domain model increases in complexity, insulating the model and services from one another becomes more important and you need to achieve better transparency. In the next chapter, we'll discuss techniques that take an extra step to achieve transparency.

4.3.2 Moving Forward

Now that you've seen (and likely used) many of the techniques that limit transparency, it's time to examine some of the preferred techniques. Think about transparency in more general terms. You want to perform a service on a model without imposing any conditions on the model at all. In other words, you want to isolate your passive model to its own box. You also want to build services that have no advanced knowledge of your model. Here are some basic assumptions about transparency (as I am defining it) to know before we continue:


The model consists purely of plain old Java objects (POJOs) or beans.

The services cannot assume anything about the objects, and will need to deal with the model in the most general terms. That means the service will deal with the model as plain Java objects, or beans.


New services or model improvements must not force developers to make source code changes elsewhere

In other words, the service should not arbitrarily force changes on the model. Neither should the service need to change to support a different model.


All model and service code changes must be automated, and may happen no sooner than build time

If the user can't make changes, you've got to automate any coding changes. Further, you can make those changes no sooner than build time, meaning there should be no specialized preprocessor, macro language, or other type of nonstandard build step.

I intentionally allow two types of changes: first, I allow configuration, because it's not Java code, and it's flexible enough to change after build time. In fact, configuration is a preferred part of most solutions. I also permit controllers and impose no restriction on them. Controllers need not be transparent. This strategy makes sense if you think of controllers as clients of both the service and the model. It doesn't make sense to hide an interface from its client, so I allow unrestricted access to the model, or the service, from the controller.

Since so many people value transparency and work to make it happen, it pays to look at a few problem spaces and examine the solutions that have been suggested. Persistence frameworks, lightweight containers, and aspect-oriented programming frameworks all need transparency to function. These are the ways that other frameworks solve the transparency problem.

    Previous Section  < Day Day Up >  Next Section