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