Exposed Domain Object implemented with Spring Aspect Transaction Control + AspectJ








This application was designed and implemented as my first web app following the DDD principles. There are some interesting issues to solve. I had the domain models exposed to the presentation layer.

No facade!??? Yeeeeaah, but not exactly.

I did not see the similar design at other places yet. Which has no facade in front of domain models, no facade between presentation and business layers. I can imagine the reaction from people who are so used to facade. Spring+AspectJ allow you have this design. Using domain object that has the supporting DAO injected and letting "spring's annotation transaction aspect" handles the transaction. It can work.

Although, using Spring+AspectJ can implement this design, it doesn't necessarily mean this design is good for all applications. An evaluation needs to be done.


Here are the pros and cons .


Pros:

  • Faster development. It accelerates the development. No Facade for the exposed domain model. When a domain object is a natural entry point of the business layer, it can serve as the facade, performing business process and returning the results. Within a group of domain models, there usually is one object that is the core. It plays a role to communicate with outside world. If you can find it, that object will be the entry. If you are the same type of person as I am, I don't like those delegating code, it probably has a interface in front of it too, so to write one method in the domain object, I need to change two interfaces, the facade interface and the domain object interface, then write two methods in each implementations of them. The one in the facade implementation is probably only one line. Looks close to: return user.findInboxEmails();
  • No more value object. Some people call it DTO. The domain object is the VO/DTO in this design. Service and factory methods need only return the object as the VO interface.
  • Nicer Encapsulation. It is also an item in Cons. This is all depend on how you write the code. The business logic will be side by side with the states information of this object. On the contrary, in a traditional design, a facade will need to access domain objects' information one way or another.

Cons:

  • Encapsulation. Again? Yes, same item in Pros. When working on the client side, in our case is the presentation layer, without a "careful" design, it is easy for the business logic to leak into the presentation layer. However, with a clear mind, after picking the right business layer entry point, create a good aggregation structure, this is totally avoidable. The entry domain object will play the facade role in this design. This is what I mean, "not exactly without a facade".
  • Tight Coupling. The changes with domain model can impact the presentation layer sometimes. Good way to deal with this problem is having a business interface for the exposed domain object. The Value Object interface and Business Interface for the domain model are used to reduce the potential errors that clients might make. That's why interface exists, doesn't it?
  • Performance. The fine grain style of communication between presentation layer and the domain object, can be an issue for some applications.
  • No Remote Access. There are ways when you really think about it, but so far there is not a easy answer for this. I might will look into write an Aspect for it. That means more coding.


My feeling about DDD, it worth a try, if you are willing to get your mind exercised a bit. Most DDD rejectors are probably used to what we were doing too much. Letting the Entities have their business knowledge is natural. The methods I have for my app look like: user.findEmail(emailId), user.moveEmailToFolder(email, folder). Compair to the popular way of using a service: userService.findEmail(user, emailId), emailService.moveEmailToFolder(user, email, forder). These methods are really state aware methods. To have them handled by a stateless service, extra information will have to be passed into the method as a parameter.


This app uses Spring 2.0.2, Hibernate 3, AspectJ5, LTW, JSF, Facelets for its template and “jsfc”.

AspectJ LTW looks for META-INF/aop.xml files. Here is the aop.xml file I used for configuring the @Configurable and transaction control. Not much details for why and how. I will write my notes inside the xml file from here.



<!DOCTYPE
aspectj PUBLIC
"-//AspectJ//DTD//EN"
"http://www.eclipse.org/aspectj/dtd/aspectj.dtd">

<aspectj>

<!--
Mentioned in my previous post, the CGLIB lazy initialized object need
to be excluded from aspectj weaver lists. We have the Model/Entity
take cared by the Load-Time Weaving -->

<weaver options="-showWeaveInfo -XmessageHandlerClass:org.springframework.aop.aspectj.AspectJWeaverMessageHandler"> <include within="com.myapp.model..*"/>
<exclude within="com.myapp.model..*CGLIB*"/>
<include within="com.myapp.service..*"/>
</weaver>

<!--
You can remove the following aspects, If you trust the weaver. It is going to work without the following aspects tag, at least in theory.

http://www.eclipse.org/aspectj/doc/released/faq.php#q:aspectj5ltw -->

<aspects>
<include
within="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"
/>

<include within="org.springframework.transaction.aspectj.AnnotationTransactionAspect" />
</
aspects>

</aspectj>



That was all for AspectJ. Wasn't hard at all. Maybe I lied about this LTW thing. It isn't hard in some cases. To have LTW working, you need -javaagent:aspectjweaver.jar in your JVM command link. To have it in place can be tricky sometimes. Or impossible. It's not a solution for your project if this -javaagent: can't be used. It also give me a hard time on unit test, integration test. Other then that, Springframe provided the aspect for configure the domain object and LTW the transaction control around your business logic. Let's have a look at the spring application context xml file. I have them in one xml file so it's easy to be explained and understand. For your real world project, organize them in different files by functional or logical group, is a good practice.




<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

<!-- Data Source -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

<!--
Maven will filter out these parameters. This is a trick Yuan Ji suggested. What a genius! Maven is used for this app for building, distributing, continuous testing and a bunch other stuff. To let Maven produce the product, in my case a war file, for different env, using filter files is the nature solution. However, developers are using Eclipse, compiling, running, and debugging, working with a template file is not that convenient. Every time the developer changed the template files, maven command has to be fired to generate the working copy for local env. Developers would like to work with the original file, without running the maven command, "mvn web:inplace" to generate the runtime copy. Then refresh project in Eclipse after every generating process.

Yuan suggested to use the PropertyPlaceholderConfigurer. Config one configurer, does the filter property replacing at dev run time, and when the Maven build process take place, the parameter will be replaced with the real values come from different maven filter files. You can have filter-dev.properties, filter-sys.properties files and so on. This way, even the spring property configurer is in the context.xml file, it will has no property to replace.

This trick has only one drawback, the filter-local.properties file will be in your final product, unless you play another trick to get the following spring <bean> configure removed during your packaging process.

<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

<property name="locations">
<list>
<value>classpath*:filters/filter-local.properties</value>
</list>
</property>

</bean> -->

<property name="driverClassName" value="${hibernate.connection.driver_class}" >
<property name="url" value="${hibernate.connection.url}" />
<property name="username" value="${hibernate.connection.username}" /> <property name="password" value="${hibernate.connection.password}" />
</bean>

<!-- Hibernate SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mappingResources">

<!--
Did not use JPA annotation here. I hope you know what to do. -->

<list> <value>com/myapp/model/Role.hbm.xml</value> <value>com/myapp/model/User.hbm.xml</value> <value>com/myapp/model/Email.hbm.xml</value> <value>com/myapp/model/Folder.hbm.xml</value>
</list></property>

<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect"> org.hibernate.dialect.MySQLInnoDBDialect
</prop>
<prop key="hibernate.query.substitutions">

true 'Y', false 'N' </prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.connection.autocommit">false</prop>
</props>
</property>
</bean>

<!-- Transaction manager, used by -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>

<!-- Datastore bean for persistence -->
<bean id="datastore" class="com.myapp.dao.hibernate.DatastoreHibernate">
<property name="sessionFactory" ref="sessionFactory" />
</bean>

<!-- Transaction controlled service/factory -->
<bean id="userManager" class="com.myapp.service.impl.UserManagerImpl">
<property name="datastore" ref="datastore" />
</bean>


<bean id="emailManager" class="com.myapp.service.impl.EmailManagerImpl">
<property name="datastore" ref="datastore" />
</bean>


<!-- *************************************************
Remember the AnnotationTransactionAspect in aop.xml file. It does the transaction control. The AspectJ runtime itself is responsible for aspect creation, and the means of configuring the AspectJ created aspects via Spring depends on the AspectJ instantiation model (per-clause) used by the aspect. The majority of AspectJ aspects are singleton aspects. Configuration of these aspects is very easy, simply create a bean definition referencing the aspect type as normal, and include the bean attribute 'factory-method="aspectOf"'. This ensures that Spring obtains the aspect instance by asking AspectJ for it rather than trying to create an instance itself.

-->
<bean class="org.springframework.transaction.aspectj.AnnotationTransactionAspect" factory-method="aspectOf">
<
property name="transactionManager" ref="transactionManager" />
</bean>

<!--
To let AspectJ dependency inject domain objects with Spring context configurations, We need @Configurable on the java class. We also need to define and configure the Entity/Model class in the "prototype" scope. Spring will do the rest work for us. Bean factory will do some magic that I did not know much yet.

Remember the AnnotationBeanConfigurerAspect in aop.xml file. Itself needs configuring by Spring (in order to obtain a reference to the bean factory that is to be used to configure new objects). That's why aop:spring-configured is here.


Instances of EmailEntity, UserEntity, and FolderEntity are going to be injected with datastore, emailManager, etc. at runtime. "New" and Hibernate queries will create these instances, with AspectJ, spring can have these domain objects equiped with all the services and database support.

-->

<aop:spring-configured />


<bean class="com.myapp.model.EmailEntity" scope="prototype">
<property name="datastore" ref="datastore" />
<property name="emailManager" ref="emailManager" />
<property name="mailEngine" ref="mailEngine" />

</bean>

<bean class="com.myapp.model.UserEntity" scope="prototype">
<property name="datastore" ref="datastore" />
</bean>

<bean class="com.myapp.model.FolderEntity" scope="prototype">
<property name="datastore" ref="datastore" />

</bean>
</beans>



Properly created aggregation structure is the key of this design. In my example, the FolderEntity is not going to be exposed as an Entity/Model to the client. I only need a value object interface to be returned by userEntity.findFolderById(). I didn't see the needs of expose EmailEntity as a business object yet. When the domain models reach to the complication point, it can be done.

These UML diagrams is a very high level overview. Suggestions and all opinions are welcomed.

One more word, you probably don't really want to have a findInboxMails() exposed to the presentation. It's not safe in speaking of the performance and memory consuming. Have a method let user define a range or a page size for the returned email list.

Comments

Popular posts from this blog

Spring framework, Hibernate and MySQL Replication

the Art of Kindling a Light in the darkness of mere being