Object-Relational
Mapping
ORM
in Dynamic
Languages
directly. Doing so, however, would violate the Separation
of Concerns principle. 12 The application code would be a
mix of business logic and persistence logic, which makes
it more complex and much more difficult to test. It also
tightly couples the business logic to the ORM framework,
which is undesirable given the furious rate at which Java
EE frameworks evolve.
A better approach is to use the DAO (data-access
object) pattern, 13 which encapsulates the data-access logic
within a DAO class. A DAO defines methods for persisting, loading, and deleting objects. It also defines finder
methods, which execute queries and are discussed in
more detail later. The DAO methods are invoked by the
business logic and call the ORM framework to access the
database.
Figure 3B shows an example of a Hibernate DAO
for the Account domain class. This DAO consists of the
AccountDao interface, which defines the public methods,
and an AccountDaoImpl class, which implements the
interface and calls Hibernate to access the database.
The DAO pattern simplifies the business logic and
decouples it from the ORM framework, but it has some
drawbacks. The first problem is that many DAOs consist
of cookie-cutter code that is tedious to develop and maintain. This has caused some developers to abandon the
DAO pattern and write business logic that directly calls
the ORM framework, despite the drawbacks of doing so.
One way to reduce the amount of cookie-cutter code is
to use a generic DAO. 14 This consists of a superinterface,
which defines the CRUD (create, read, update, delete)
operations, and a superclass, which implements them.
The superinterface and the superclass are parameterized
by the entity class, which makes them strongly typed.
Application DAOs extend the generic DAO interface and
implementation class. Using a generic DAO eliminates
some but not all of the cookie-cutter code, so it’s only a
partial solution.
Another problem with using DAOs is that some
application classes might not be able to reference them.
Modern Java EE applications resolve inter-component
references using a mechanism known as dependency
injection. 15 When the application starts up, an assembler
instantiates each application component and injects it
with references to the required components. Resolving
inter-component references in this way simplifies the
components and promotes loose coupling.
One limitation of dependency injection, however,
is that it does not easily allow noncomponents such as
domain objects to obtain references to components such
as DAOs. Domain objects are instantiated by the application rather than by the component assembler. It’s tricky,
although not impossible, 16 for the component assembler to intercept the instantiation of such objects and
inject dependencies. As a result, business logic residing
in domain objects cannot always reference components
such as DAOs.
There are a couple of ways to work around this
limitation. Components such as services, which can use
dependency injection, pass DAOs as method parameters
to domain classes, which cannot. This works well in some
situations, but in more complex cases the code becomes
cluttered with extra parameters. Another workaround
is to move the code that needs to use the DAOs into
components where it can use dependency injection. The
trouble with moving business logic out of the entities
is that it degrades the design and results in an anemic
domain model.
DYNAMIC PERSISTENCE METHODS IN GORM
GORM provides a different style of persistence API. Rather
than providing an API object, it injects methods for saving, loading, and deleting persistent objects into domain
classes. This mechanism decouples the business logic
from the underlying ORM framework without having to
use DAOs. It also eliminates the need for application code
to obtain references to the ORM framework API objects or
DAOs.
GORM injects several methods into domain classes,
including save(), which saves a newly created object;
get(), which loads an object by its primary key; and
delete(), which deletes an object. Here is an example that
uses these methods:
Customer c = new Customer(“John Doe”)
if ( !c.save())
fail “save failed”
Customer c2 = Customer.get( c.id)
c2.delete()