Better Faster Lighter Java [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Better Faster Lighter Java [Electronic resources] - نسخه متنی

Justin Gehtland; Bruce A. Tate

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید








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


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

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.


/ 111