Re: Persistence Strategies [Was: Open source WO - offer]
Lenny Marks <[email protected]> Sat, 9 Sep 2006 00:20:10 -0400
| Newsgroups | gmane.comp.web.webobjects.general |
|---|---|
| Message-ID | <[email protected]> |
Hope you don't mind me posting back to the list. Maybe others will be
interested.
On Sep 8, 2006, at 7:44 AM, Jean-François Veillette wrote:
> Hi Lenny,
> I'm interested to know more about your switch to Hibernate.
> I'm a long time eof user, I'm looking around to explore what the
> rest of the world has come up with to solve problem that eof was
> designed to solve.
>
> I do build my business layer framework tied to my persistence model
> (eof). I can see that this might sound like a problem, but frankly
> I don't see how you can build your business layer without being
> aware of the persistence layer and the transaction model. I guess
> I build my layer with the eof paradigm, and doing so I have hard
> time seeing how I would have write it using another paradigm.
>
> As I understand it, POJO objects are the verry leafs of your
> business models, leaving all the management of it to other
> classes. In order to be reusable, persistent pojo class seems to
> primitive, almost like Calendar date, Number, or any other "
> primitive type ". I can hardly see a business object (an Employee,
> a Project, etc) not aware of the persistence layer, not aware of
> the validation needed to be made just before it get saved.
>
No. There's really a little too much to go into, but I'm going to
attempt to summarize/demonstrate with a few quotes and a simple
example. If your really curious, I strongly recommend the book, "J2EE
Development without EJB" published by Wrox. The book is not tied to
any particular ORM solution, but has a great intro to all the stuff I
touch on below. There's not even that much too it if you skip around
the EJB stuff.
(Hibernate In Action pg. 64) The domain model implementation is such
an important piece of code that it shouldn't depend on other Java
APIs. For example, code in the domain model shouldn't perform JNDI
lookups or call the database via the JDBC API. This allows you to
reuse the domain model implementation virtually anywhere. Most
importantly, it makes it easy to unit test the domain model outside
of any application server or other managed environment. We say that
the domain model should be "concerned" only with modeling the
business domain. However, there are other concerns, such as
persistence, transaction management, and authorization. You shouldn't
put code that addresses these cross-cutting concerns in the classes
that implement the domain model. When these concerns start to appear
in the domain model classes, we call this an example of leakage of
concerns.
<quote from Patterns of Enterprise Architecture by Martin Fowler,
David Rice, Matthew Foemmel, Edward Hieatt, Robert Mee, Randy Stafford>
Kinds of 'Business Logic' : Service Layer is a pattern for organizing
business logic. Many designers, including me, like to divide
'business logic ' into two kinds: 'domain logic," having to do
purely with the problem domain(such as strategies for calculating
revenue recognition on a contract) , and 'application logic', having
to do with application responsibilities (such as notifying contract
administrators, and integrated applications, of revenue recognition
calculations).
</quote>
General architecture description:
domain layer - Domain model classes(persistent entities) with domain
logic
data access layer - interfaces for fetching domain/model objects.
Expect to be used within transactions, but do not themselves begin or
end them.
service layer - Transaction management, application logic
presentation tier/layer
Unit Of Work(EOEditingContext, org.hibernate.Session, ..) - http://
www.martinfowler.com/eaaCatalog/unitOfWork.html - A Unit of Work
keeps track of everything you do during a business transaction that
can affect the database. When you're done, it figures out everything
that needs to be done to alter the database as a result of your work
I for the most part try to put methods inside the classes whose data
they are operating on(as good object oriented programming would have
you do), UNLESS there is a good reason not to(such as violation of
the layering or leakage of concerns). For example, I wouldn't add a
method on in my domain layer that uses a data access object to fetch
objects from persistence storage. Instead I would have a service
layer interface that uses data access objects and then delegates to
domain objects.
Here is a simple example from start to end including Inversion of
control and declarative transaction management with Spring. (ignore
the usage of double for money representation)
// -------------------- model(domain) layer ---------------------------
//testable with vanilla junit test
class example.model.Account {
transfer(double amount, Account toAccount) throws
InSufficientFundsException {
.....
}
// ------------------------ data access layer
----------------------------
/ /Interface based programming allows class under tests to be
injected with stubs/mock implementations
// so we can test in isolation
interface example.dao.AccountDAO {
public Account findAccount(Long accountId);
}
//notice this is the only class with any hibernate dependency
class example.dao.hibernate.AccountDAOImpl implements AccountDAO {
private org.hibernate.SessionFactory sessionFactory;
public void setSession(org.hibernate.SessionFactory sf) {
this.session = sf.getCurrentSession();
}
public Account findAccount(Long accountId) {
return (Account)session.get(Account.class, accountID);
}
}
// ------------------------- Service Layer
-----------------------------------
// Using interface based programming allows class under tests to be
injected with stubs/mock implementations
// so we can test in isolation
class example.service.AccountService {
public void transfer(double amount, Long fromAccountId, Long
toAccountId) throws InSufficientFundsException;
}
class example.service.AccountServiceImpl {
private AccountDAO accountDAO;
public void setAccountDAO(AccountDAO dao) {
accountDAO = dao;
}
public void transfer((double amount, Long fromAccountId, Long
toAccountId) throws InSufficientFundsException {
Account sourceAccount = accountDAO.findAccount(fromAccountID);
if(sourceAccount == null)
throw new IllegalArgumentException("Account " + fromAccountId + "
not found");
Account toAccount = accountDAO.findAccount(toAccountId);
if(toAccount == null) {
throw new IllegalArgumentException("Account " + toAccountId + "
not found");
sourceAccount.transfer(amount, toAccount);
}
// ----------------AccountServiceImpTest using EasyMock
--------------------------------------
// Using mock objects allowing to test without DB
AccountServiceImpTest extends TestCase {
private AccountServiceImpl service;
private MockControl accountDAOC;
private AccountDAO accountDAO;
protected void setUp() throws Exception {
accountDAOC = MockControl.createControl(AccountDAO.class);
accountDAO = (AccountDAO)accountDAOC.getMock();
service = new AccountServiceImpl();
service.setAccountDAO(accountDAO);
}
public void testWithSourceAccountNotFound() {
//record expected behavior
accountDAO.findAccount(new Long(1));
accountDAOC.setReturnValue(null);
//end recording of expected collaborator interaction
accountDAOC.replay();
try {
service.transfer(10, new Long(1), new Long(2));
fail("expected exception");
} catch(IllegalArgumentException e) {
assertEquals("Account 1 not found", e.getMessage());
}
//throw exception if expected behavior did not occur
accountDAOC.verify();
}
.....
}
// ----------- Spring Configuration File Including declarative
transaction management -------------------------
....
<bean id="accountDAO" class="example.dao.hibernate.AccountDAOImpl">
<property name="sessionFactory"><ref bean="sessionFactory"/></property>
</bean>
<bean id="accountService" parent="txTemplate">
<property name="target">
<bean class="example.service.impl.AccountServiceImp">
<property name="accountDAO"><ref bean="accountDAO"/></property>
</bean>
</property>
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
....
// ----------- presentation layer code -----------------
class MyWOComponent extends
ABaseComponentThatHandlesDependencyInjection {
private AccountService accountService;
....
public void setAccountService(AccountService s) {
this.accountService = s;
}
public WOComponent transfer() {
//just an example, no handling of bad ids
try
//no need for any transaction demarcation because
service is configured with declarative transaction management
accountService.transfer(amount, fromAccountId, toAccountId);
} catch(InsufficientFundsException e) {
addError("Insufficient funds);
}
return null;
}
}
> I can hardly see a business object (an Employee, a Project, etc)
> not aware of the persistence layer, not aware of the validation
> needed to be made just before it get saved.
>
This is true as Andrus mentioned. If you want the persistence
framework to handle making sure the object graph is consistent, (e.g.
addObjectToBothSidesOfRelationshipWIthKey) and things like automatic
validation callbacks(e.g validateForSave), then your domain objects
would have to be aware of the persistence framework. Personally, I
chose to leave my domain objects unaware. This means that I have to
manually(or have generated for me) methods like the following(which I
can live with):
public void addToEmployeesRelationship(Employee e) {
e.setDepartment(this);
employees.add(e);
}
As far as validation. In Hibernate I use a utility class that
validates nullability and length limits based on the mapping
definitions. Our data access layer is based around the assumption of
an ORM solution that offers transparent persistence(things like
persistence by reachability), so each of the first class domain
objects has a service layer manager of some sort that typically has a
save method. This is where we handle validation.
e.g.
public void saveManuscript(Manuscript m, Individual user) {
try {
manuscriptValidationService.validateForSave(m);
} catch(ValidationException ve) {
throw new RuntimeException(ve);
}
...
> Somehow you need to add some meat around the primitive bones, you
> need to add logic that is aware of the transaction model among
> other things.
> Right now, proper use of eoeditingcontext (which manage the db
> transaction), fetch spec, etc, keep my business logic layer
> complete, transaction aware, entirely independant of the view layer
> api that will be used.
> I'm having hard time figuring out how you build the logic layer of
> your apps, without being aware of the persistence and/or
> transaction mechanism used. Somehow I can't figure out how you can
> avoid being bound with any given library. I think there is a java
> interface ( jdo ) defined for that, aloowing you to depend on the
> interface only, but is it really ready ? (It's a real question, I
> haven't looked at it for a while) I'm thinking that even though
> you have the interface for 75% of the code related to transaction/
> persistence logic, in practice I would think that you still need
> those extra api available but not defined in the interface.
> Resulting in your logic layer dependent of the actual persistence
> library.
>
I'm not the best person to answer questions about JDO, but its not
that relevant. Using a layered architecture, all data access is done
through a set of data access interfaces. Theoretically, you could
swap the set of data access implementations from say Hibernate to
JDO. (Of course this is not the main goal of the layered
architecture, nor would it likely be achievable without needing
changes outside the data access layer). Its really all about
testability.
>> Ah. Thats sounds good. I didn't even go into transaction
>> management, which is another place IMO, Hibernate wins over EOF.
>> Doing declarative transaction management with Spring is really nice.
>
> I thought that Spring was related to the view layer !? I'm confused.
Thats just one of many Spring modules. Spring is at its core, a
lightweight inversion of control container.
Inversion Of Control - http://www.martinfowler.com/articles/
injection.html
Aspect oriented programming - http://www.martinfowler.com/articles/
injection.html
Transaction management - http://static.springframework.org/spring/
docs/1.2.x/reference/transaction.html
-lenny