|
#1
|
|||
|
|||
|
Hello,
We've heard time and time again to "put business logic inside your business objects". Most of the time, full business logic requires dependencies to be injected in my business object. The business objects themselves (Business, Account, etc) are managed by Hibernate. Their dependencies are/should be managed by Spring. How common is this? If my business object is something that comes from the database, but requires external dependencies, then Spring must create and wire up my object. This seems fair and good, but I haven't seen many examples of this usage. It makes me wonder if my business domain objects (those that come from the database) should ever rely on external dependencies. My gut says yes: keep business logic in business object. Note: this does not mean DAO type actions. Of course, I could keep the business objects free from external dependencies, but then their ability to do work is greatly lessened. Thoughts? Comments? War stories? Thanks very much! Seth |
|
#2
|
||||
|
||||
|
Don't forget about good ol' message passing! I find it helpful to pass in parameters to my business objects via messages telling them to "do some work" associated with the data they encapsulate. This is often better than asking them for the data so I can do the work for them.
For example, in my most recent application, I developed a fairly sophisticated data loader that could parse custom file formats and translate them into a normalized database schema, performing type conversion and data transformations. In this system I had a central "loader service" (a workflow object) which encapsulated the general parse / load algorithm. I also had individual parses that encapsulated logic to parse a particular file format into fields, and separate metadata objects that would then perform any necessary type conversions/data transformations on parsed field values. The result was a bunch of business objects working together, with the workflow object brokering a good deal of the message passing, telling the other objects to "do work" as part of a larger algorithm. Why was this good? Data and behaivior were kept together, the workflows algorithm was kept completely generic (meaning it was coded once and hasn't changed since even though our system has grown to support a large number of custom formats and conversion rules.) Just food for thought! Keith |
|
#3
|
|||
|
|||
|
Quote:
![]() This is exactly what I want to do with my business objects: BusinessObject bo = facade.load(pk); bo.myBusinessMethod(); facade.save(bo); where bo.myBusinessMethod() might do something with velocity, for instance. So I'll need Spring to wire up my BusinessObject, which is loaded from DB via Hibernate. Just wondering how common that is, to wire Hibernate objects using Spring. Seems to be pretty important if we want to keep business logic inside business objects. Seth |
|
#4
|
|||
|
|||
|
If I understand what you're saying correctly, the thing you will need to worry about is performance when you're loading a large set of Spring-managed domain objects from the db. Since each Hibernate-loaded domain object obviously must be a prototype (non-singleton) in the Spring context, the performance hit will come when you need to wire up 10,000 new instances of Foo. If this is not a problem, the concept sounds viable.
The only other downside I can think of is the added complexity in your business objects (where did that object come from). However, this is a weak argument because the Spring application context provides a very valuable kind of "documentation" about how objects within the application interact. A Spring context provides a means to distinguish the forrest from the trees when you need to learn what an application does in an overall sense vs. how it handles the details.
__________________
~ Daniel Miller |
|
#5
|
|||
|
|||
|
Quote:
There seems to be two different definitions of the term "business object" out there. 1) The domain objects that come from the database. The objects from the business model. 2) Where the work is done. It seems that we should be combining definitions 1 and 2. Is that a safe assumption? Here are the rule breakers: 1) Business Object is not a transaction demarcation. That is the role of the Facade. 2) Business Object is not concerned with persistence. That is the role of the DAO. For a Business Object to contain Business Logic, it will inevitably need dependencies. This is something I haven't seen too much in examples. All the examples only show CRUD operations as the business logic. Just trying to find out how practical it is to keep business logic inside business objects. And if my definition of Business Object is even correct. ![]() I'll post a Use Case that I think will clearly outline this problem if that would help. The more best practices we can catalogue, the better. ![]() Seth |
|
#6
|
|||
|
|||
|
Martin Fowler has some valuable insite on this topic in his article on the Anemic Domain Model.
This quote sums it up fairly well: Quote:
__________________
~ Daniel Miller |
|
#7
|
|||
|
|||
|
That's a great quote! Thanks for posting.
So, back to my original question/observation: If we're supposed to put business logic into business objects, then business objects need dependency injection. And if that's true, then objects loaded by persistence should be managed by Spring. That seems like a logical conclusion. So where are the examples? ![]() Of course, it's easy to figure out, and some examples have been posted to the mailing list in the past. It would be really nice to see this type of practice in some of the sample apps included with Spring. Granted, to warrant this, a more complex business operation other than CRUD would be needed. Leading off of this thread (good thread, and thanks for all the insight!) I've got another observation of Best Practice vs Real World. I'll start another thread to see what others think. |
|
#8
|
|||
|
|||
|
I would tend to agree with you, though this brings up other questions. Two other thoughts off the top of my head:
1. What about the client? Do business objects cross the service (facade) boundary, or do you copy everything over into another graph (DTOs?) before sending to the client? If you send the business objects themselves, you would have to rewire all their dependencies on the client side... if you don't send the business objects, then the client is forced to use the facade to access all business logic (good or bad, you decide). 2. It sure would be nice if Spring had a built in facility for managing Hibernate objects. Maybe there is one and I'm not aware of it? I think right now you would have to implement a custom Hibernate interceptor, no? Anyway, Spring is geared for one time context initialization from a performance perspective... I wonder how well it would perform when injecting into thousands of instances on the fly? |
|
#9
|
|||
|
|||
|
Quote:
Quote:
Sounds like a good example candidate for the Wiki, no?
|
|
#10
|
|||
|
|||
|
Quote:
http://thread.gmane.org/gmane.comp.j...work.user/3402 http://thread.gmane.org/gmane.comp.j...work.user/3513 And here is the code for the interceptor Code:
/*
* SpringInterceptor.java
*
*/
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import net.sf.hibernate.CallbackException;
import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Interceptor;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.type.Type;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.propertyeditors.ClassEditor;
import org.springframework.util.StringUtils;
/**
*
* @author Oliver Hutchison
* @see Interceptor
*/
public class SpringInterceptor implements Interceptor, InitializingBean,
BeanFactoryAware {
private BeanFactory beanFactory;
private SessionFactory sessionFactory;
private String sessionFactoryBeanName;
private Map persistantClassBeanNames = new HashMap();
private Interceptor delegate;
/**
* Constructor
*/
public SpringInterceptor() {
super();
}
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public void setSessionFactoryBeanName(String sessionFactoryBeanName) {
this.sessionFactoryBeanName = sessionFactoryBeanName;
}
public void setPersistantClassBeanNames(Properties persistantClassBeanNames) {
ClassEditor ce = new ClassEditor();
for (Iterator i = persistantClassBeanNames.keySet().iterator(); i
.hasNext();) {
String className = (String) i.next();
String beanName = persistantClassBeanNames.getProperty(className);
ce.setAsText(className);
Class clazz = (Class) ce.getValue();
this.persistantClassBeanNames.put(clazz, beanName);
}
}
public void setDelegateInterceptor(Interceptor delegateInterceptor) {
this.delegate = delegateInterceptor;
}
public void afterPropertiesSet() {
if (beanFactory == null) {
throw new IllegalArgumentException("beanFactory is required");
}
if ((sessionFactory == null)
&& !StringUtils.hasText(sessionFactoryBeanName)) {
throw new IllegalArgumentException(
"sessionFactory or sessionFactoryBeanName is required");
}
}
/**
* @see net.sf.hibernate.Interceptor#instantiate(java.lang.Class,
* java.io.Serializable)
*/
public Object instantiate(Class beanClass, Serializable id)
throws CallbackException {
String name = (String) persistantClassBeanNames.get(beanClass);
if (name == null) {
if (delegate == null) {
return null;
} else {
return delegate.instantiate(beanClass, id);
}
}
if (beanFactory.isSingleton(name)) {
throw new UnsupportedOperationException("Bean name [" + name
+ "] must be a prototype. i.e. singleton=\"false\"");
}
Object newEntity = beanFactory.getBean(name);
try {
BeanWrapper wrapper = new BeanWrapperImpl(newEntity);
wrapper.setPropertyValue(getSessionFactory().getClassMetadata(
beanClass).getIdentifierPropertyName(), id);
} catch (HibernateException e) {
throw new CallbackException(
"Error getting identifier property for class " + beanClass,
e);
}
return newEntity;
}
private SessionFactory getSessionFactory() {
if (sessionFactory == null) {
sessionFactory = (SessionFactory) beanFactory
.getBean(sessionFactoryBeanName);
}
return sessionFactory;
}
/**
*
* @see net.sf.hibernate.Interceptor#onLoad(java.lang.Object,
* java.io.Serializable, java.lang.Object[], java.lang.String[],
* net.sf.hibernate.type.Type[])
*/
public boolean onLoad(Object entity, Serializable id, Object[] state,
String[] propertyNames, Type[] types) throws CallbackException {
if (delegate != null) {
return delegate.onLoad(entity, id, state, propertyNames, types);
} else {
return false;
}
}
/**
* @see net.sf.hibernate.Interceptor#onFlushDirty(java.lang.Object,
* java.io.Serializable, java.lang.Object[], java.lang.Object[],
* java.lang.String[], net.sf.hibernate.type.Type[])
*/
public boolean onFlushDirty(Object entity, Serializable id,
Object[] currentState, Object[] previousState,
String[] propertyNames, Type[] types) throws CallbackException {
if (delegate != null) {
return delegate.onFlushDirty(entity, id, currentState,
previousState, propertyNames, types);
} else {
return false;
}
}
/**
* @see net.sf.hibernate.Interceptor#onSave(java.lang.Object,
* java.io.Serializable, java.lang.Object[], java.lang.String[],
* net.sf.hibernate.type.Type[])
*/
public boolean onSave(Object entity, Serializable id, Object[] state,
String[] propertyNames, Type[] types) throws CallbackException {
if (delegate != null) {
return delegate.onSave(entity, id, state, propertyNames, types);
} else {
return false;
}
}
/**
* @see net.sf.hibernate.Interceptor#onDelete(java.lang.Object,
* java.io.Serializable, java.lang.Object[], java.lang.String[],
* net.sf.hibernate.type.Type[])
*/
public void onDelete(Object entity, Serializable id, Object[] state,
String[] propertyNames, Type[] types) throws CallbackException {
if (delegate != null) {
delegate.onDelete(entity, id, state, propertyNames, types);
}
}
/**
* @see net.sf.hibernate.Interceptor#preFlush(java.util.Iterator)
*/
public void preFlush(Iterator entities) throws CallbackException {
if (delegate != null) {
delegate.preFlush(entities);
}
}
/**
* @see net.sf.hibernate.Interceptor#postFlush(java.util.Iterator)
*/
public void postFlush(Iterator entities) throws CallbackException {
if (delegate != null) {
delegate.postFlush(entities);
}
}
/**
* @see net.sf.hibernate.Interceptor#isUnsaved(java.lang.Object)
*/
public Boolean isUnsaved(Object entity) {
if (delegate != null) {
return delegate.isUnsaved(entity);
} else {
return null;
}
}
/**
* @see net.sf.hibernate.Interceptor#findDirty(java.lang.Object,
* java.io.Serializable, java.lang.Object[], java.lang.Object[],
* java.lang.String[], net.sf.hibernate.type.Type[])
*/
public int[] findDirty(Object entity, Serializable id,
Object[] currentState, Object[] previousState,
String[] propertyNames, Type[] types) {
if (delegate != null) {
return delegate.findDirty(entity, id, currentState, previousState,
propertyNames, types);
} else {
return null;
}
}
}
|
![]() |
| Thread Tools | |
| Display Modes | |
|
|
Similar Threads
|
||||
| Thread | Thread Starter | Forum | Replies | Last Post |
| Hibernate Long Session Per Flow? | akw | Spring Web Flow | 21 | Dec 12th, 2005 08:06 PM |
| Spring container fails with no exception | naor | Core Container | 9 | Oct 1st, 2005 03:39 PM |
| Spring code remarks | Alarmnummer | Architecture Discussion | 18 | Apr 7th, 2005 07:17 AM |
| Spring Framework / Spring Rich Client Library Hell? | steve_smith | Spring Rich Client Project | 14 | Feb 21st, 2005 05:41 PM |
| Mixing hibernate & spring into single beans | ImanRahmati | Core Container | 1 | Dec 21st, 2004 07:26 PM |