35 year old developer. He started developing programs when he was 12 years old, and he’s been almost exclusively working with Java for the last 10 years doing desktop and enterprise applications for companies like Ericsson, Apple, GE, Citigroup, Leapfrog, etc. Guillermo considers working with computers not only his job, but also his hobby. His wealth of knowledge for technology, and the voracity which he consumes new information is impressive. Guillermo is a graduate of the Monterrey Institute of Technology with a Physics Major and a minor in Electronics. Guillermo has posted 1 posts at DZone. View Full User Profile

Wicket Tutorial Series: Designing the Backend

03.12.2009
| 9538 views |
  • submit to reddit

As mentioned in day 2 of our series, we have a main service interface that came from an initial discussion of what we think a pastebin should do. What can be easier than creating an entity model for a pastebin application, right? It’s just a big text area that holds a piece of text (content) that someone wants to share with the rest of the world, or at least with someone else who might be interested in looking at it. However, there are some specific things that we wanted to accomplish in our application that came from a series of ideas by the different team members. Of course, although not really a requirement, we wanted to build this application using Apache Wicket.

Requirements

Here is a list of the ideas (or requirements) we wanted for our pastebin, in no particular order:

  • A paste item must be identified by a unique id and/or timestamp, and it should contain text and optionally a language identifier (useful for syntax highlighting).
  • A Paste item may be a Reply of another Paste item (can have a parent item).
  • A Paste item may have one or more replies (children items).
  • A Paste item may be private. Private items will be identified by a special random string token of a defined length.
  • A Paste item may be associated with a specific user (author).
  • A given author that has been identified to the system will be able to see all the paste items that he/she created.

Models/Entities

From the set of requirements we can get a sense of how the entities are going to be created and how they’re going to relate to each other, as seen in the following diagram:

Class Diagram

This looks like a very simple entity model, but it fits with what we want to accomplish for this particular project. During the brainstorming session many features were discussed, like the ability to edit and/or delete an item that you own (identified by some session token saved in a cookie), the ability to upload images, have an API for external clients that want access to the pastebin, etc. This might be implemented in the future, but were omitted for the first iteration of the project. However, some of the basis for the functionalities is there, like having a client token to identify the different clients accessing the server and be able to show them in the future.

So now that we have a base, let’s get to do some actual coding. As mentioned before, we decided on using Hibernate as our ORM mapping. This is how we define the PasteItem class (the getters and setters have been omitted from the class to make it easier to read):

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import static javax.persistence.EnumType.STRING;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Entity
@NamedQueries({@NamedQuery(name = "item.getById", query = "from PasteItem item where item.id = :id"),
		@NamedQuery(name = "item.find",
				query = "from PasteItem item where item.isPrivate != true order by item.timestamp desc"),
		@NamedQuery(name = "item.findThreaded",
				query = "from PasteItem item where item.isPrivate != true and item.parent is null order by item.timestamp desc"),
		@NamedQuery(name = "item.findByLanguage",
				query = "from PasteItem item where item.isPrivate != true and item.type = :type order by item.timestamp desc"),
		@NamedQuery(name = "item.findByLanguageThreaded",
				query = "from PasteItem item where item.isPrivate != true and item.parent is null and item.type = :type order by item.timestamp desc"),
		@NamedQuery(name = "item.findByToken", query = "from PasteItem item where item.privateToken = :token"),
		@NamedQuery(name = "item.findByUser",
				query = "from PasteItem item where item.isPrivate != true and item.userToken = :token"),
		@NamedQuery(name = "item.count", query = "select count(*) from PasteItem item where item.isPrivate != true")})
public class PasteItem implements Serializable {
	@Id @GeneratedValue(strategy = GenerationType.AUTO)
	protected long id;
	@Lob
	protected String content;
	@Enumerated(STRING)
	protected LanguageType type;
	@Temporal(TemporalType.TIMESTAMP)
	protected Date timestamp;
	@Basic
	protected String userToken;
	@Basic
	protected String clientToken;
	@Basic
	protected boolean isPrivate;
	@Basic
	@Column(name = "PRIVATE_TOKEN", unique = true, updatable = false)
	protected String privateToken;
	@ManyToOne(fetch = FetchType.LAZY, optional = true)
	@JoinColumn(name = "PARENT_ITEM_ID", nullable = true)
	protected PasteItem parent;
	@OneToMany(fetch = FetchType.LAZY, mappedBy = "parent")
	protected List<PasteItem> children;
	...
}

We are using annotations to define our entity model. The interesting part here is that we didn’t use any Hibernate-specific annotations, and instead relied on the standard ones from the JPA API. Hibernate understands these annotations so it seems logical to use them because then we can make the application more portable, as we could replace Hibernate for any JPA-enabled library. Also, another great feature of JPA is that we can define a set of named queries that our Dao implementations can use. This allows you to define almost anything that’s related to the persistence layer under one class, like the entity, its properties and the different ways to access the entity via queries under one place, which then becomes your main reference class.

Service layer

We defined a primary service interface that the Wicket front-end will use to interact between the web pages and the rest of the application. The service layer classes are usually responsible for implementing the required business rules of the application, and they can be as complex or as simple as required, depending on what the actual requirements of the application are. Wikipedia defines a business rule as:

Business rule is a statement that defines or constrains some aspect of the business. It is intended to assert business structure or to control or influence the behavior of the business. Individual business rules that describe the same facet of an enterprise are usually arranged into business rulesets. Business rules describe the operations, definitions and constraints that apply to an organization in achieving its goals.

In layman’s terms, a business rule is anything that affects the way we act on our data. This is how our service implementation looks like:

import com.mysticcoders.mysticpaste.model.LanguageType;
import com.mysticcoders.mysticpaste.model.PasteItem;
import com.mysticcoders.mysticpaste.persistence.PasteItemDao;
import com.mysticcoders.mysticpaste.utils.TokenGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class PasteServiceImpl implements PasteService {
	private final Logger logger = LoggerFactory.getLogger(getClass());
	public static final int DEFAULT_TOKEN_LENGTH = 10;
	private PasteItemDao itemDao;
	private int tokenLength;
	public PasteServiceImpl() {
		this.tokenLength = DEFAULT_TOKEN_LENGTH;
	}
	public PasteServiceImpl(PasteItemDao itemDao, int tokenLength) {
		this.itemDao = itemDao;
		this.tokenLength = tokenLength;
	}
	@Transactional(readOnly = true)
	public List<PasteItem> getLatestItems(String clientToken, int count, int startIndex, boolean threaded)
			throws InvalidClientException {
		logger.trace("Service: getLatestItems. clientToken = {}, count = {}, startIndex = {}, threaded = {}",
				new Object[]{clientToken, count, startIndex, threaded});
		List<PasteItem> results = null;
		if (threaded) {
			results = itemDao.findThreaded(count, startIndex);
		} else {
			results = itemDao.find(count, startIndex);
		}
		if (null == results) {
			logger.warn("Found no items in database.");
			results = new ArrayList<PasteItem>();
		}
		return results;
	}
	@Transactional(readOnly = true)
	public PasteItem getItem(String clientToken, long id) throws InvalidClientException {
		return itemDao.get(id);
	}
	@Transactional(readOnly = true)
	public PasteItem findPrivateItem(String clientToken, String privateToken) throws InvalidClientException {
		return itemDao.findByToken(privateToken);
	}
	@Transactional(readOnly = true)
	public List<PasteItem> findItemsByLanguage(String clientToken, LanguageType languageType, int count,
											   int startIndex, boolean threaded)
			throws InvalidClientException {
		List<PasteItem> results = null;
		if (threaded) {
			results = itemDao.findByLanguageThreaded(languageType, count, startIndex);
		} else {
			results = itemDao.findByLanguage(languageType, count, startIndex);
		}
		if (null == results) {
			results = new ArrayList<PasteItem>();
		}
		return results;
	}
	@Transactional(rollbackFor = Exception.class)
	public long createItem(String clientToken, PasteItem item) throws InvalidClientException {
		if (null != item && item.isPrivate()) {
			item.setPrivateToken(TokenGenerator.generateToken(getTokenLength()));
		}
		// set created Timestamp
		item.setTimestamp(new Date(System.currentTimeMillis()));
		return itemDao.create(item);
	}
	public long getLatestItemsCount(String clientToken) throws InvalidClientException {
		return itemDao.getPasteCount();
	}
	public PasteItemDao getItemDao() {
		return itemDao;
	}
	public void setItemDao(PasteItemDao itemDao) {
		this.itemDao = itemDao;
	}
	public int getTokenLength() {
		return tokenLength;
	}
	public void setTokenLength(int tokenLength) {
		this.tokenLength = tokenLength;
	}
}

The business rules for the pastebin are very light in nature. One of the business rule we have from the requirements is to generate a random string token to use as the private paste identifier, so that instead of having a sequential id (which can be guessed), it is identified by this string and a special url.

We’re using slf4j as our logging mechanism. This allows us to statically map our logging to one of log4j, jdk, etc., and it also allows us to have very simple logging messages that will help us ‘debug’ the application in a sense. Nowadays it is considered bad practice to use System.out.println() messages as we don’t have control over them (i.e. they will always appear). Having a logging mechanism with separate message levels allows us to control what we want to show.

It is also interesting to note that in order to support a transactional set of methods, we used Spring’s @Transactional annotation. This annotation allows us to mark a method (and its underlying method calls) as part of one transaction, and optionally to mark said transaction as read-only. Also, take care that marking a method as transactional is often not enough, as we have to specify under which conditions the transaction needs to rollback (in our case, every time an exception is thrown). This is because by default, spring only rolls back transactions when runtime exceptions are thrown.

Persistence layer

Since we are using Hibernate, our persistence layer becomes a really easy, thin layer. Here’s our Dao implementation:

import com.mysticcoders.mysticpaste.model.LanguageType;
import com.mysticcoders.mysticpaste.model.PasteItem;
import com.mysticcoders.mysticpaste.persistence.PasteItemDao;
import java.util.List;
public class PasteItemDaoImpl extends AbstractDaoHibernate<PasteItem> implements PasteItemDao {
	protected PasteItemDaoImpl() {
		super(PasteItem.class);
	}
	public Long create(PasteItem item) {
		save(item);
		return item.getId();
	}
	public PasteItem get(long id) {
		PasteItem item = (PasteItem) getSession().getNamedQuery("item.getById")
				.setLong("id", id).setMaxResults(1)
				.uniqueResult();
		return item;
	}
	public List<PasteItem> findByLanguage(LanguageType languageType, int count, int startIndex) {
		return getSession()
				.getNamedQuery("item.findByLanguage")
				.setParameter("type", languageType)
				.setMaxResults(count).setFirstResult(startIndex).list();
	}
	public List<PasteItem> findByLanguageThreaded(LanguageType languageType, int count, int startIndex) {
		return getSession()
				.getNamedQuery("item.findByLanguageThreaded")
				.setParameter("type", languageType)
				.setMaxResults(count).setFirstResult(startIndex).list();
	}
	public List<PasteItem> find(int count, int startIndex) {
		return getSession().getNamedQuery("item.find")
				.setMaxResults(count).setFirstResult(startIndex).list();
	}
	public List<PasteItem> findThreaded(int count, int startIndex) {
		return getSession()
				.getNamedQuery("item.findThreaded")
				.setMaxResults(count).setFirstResult(startIndex).list();
	}
	public PasteItem findByToken(String privateToken) {
		return (PasteItem) getSession()
				.getNamedQuery("item.findByToken")
				.setParameter("token", privateToken)
				.uniqueResult();
	}
	public long getPasteCount() {
		Long count = (Long) getSession()
				.getNamedQuery("item.count")
				.setMaxResults(1).uniqueResult();
		return null == count ? 0 : count;
	}
}

We have taken advantage of JPA’s @NamedQuery annotation to greatly simplify the code in our Dao implementation. Since all the queries for accessing a PasteItem were already defined inside the entity (in our case, the PasteItem class), we only need to refer to those queries here, set the named parameters and get the results.

We also defined an abstract class to “generify” (term borrowed from IDEA’s inspection) Hibernate’s access:

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import java.io.Serializable;
public class AbstractDaoHibernate<T> extends HibernateDaoSupport {
	private Class entityClass;
	private SessionFactory sessionFactory;
	protected AbstractDaoHibernate(Class dataClass) {
		super();
		this.entityClass = dataClass;
	}
	@SuppressWarnings("unchecked")
	private T load(Long id) {
		return (T) getSession().get(entityClass, id);
	}
	@SuppressWarnings("unchecked")
	private T loadChecked(Long id) throws EntityNotFoundException {
		T persistedObject = load(id);
		if (persistedObject == null) {
			throw new EntityNotFoundException(entityClass, id);
		}
		return persistedObject;
	}
	public void merge(T detachedObject) {
		getSession().merge(detachedObject);
	}
	public void save(T persistedObject) {
		getSession().saveOrUpdate(persistedObject);
	}
	private void delete(T persistedObject) {
		getSession().delete(persistedObject);
	}
	public void delete(Long id) {
		delete(loadChecked(id));
	}
}

This class takes advantage of Java 5 generics in order to implement the common persistence methods that we have. It also extends Spring’s HibernateDaoSupport to make it easier to integrate with Spring.

Wiring everything together

Once we have the service and the persistence layer, we need a way to put everything together in order for our application to work. Since we are using Spring, we only need to define our beans in the applicationContext.xml:

<?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:tx="http://www.springframework.org/schema/tx"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
	<!-- Services Beans -->
	<bean id="pasteService" class="com.mysticcoders.mysticpaste.services.PasteServiceImpl">
		<property name="itemDao" ref="pasteItemDao"/>
		<property name="tokenLength" value="${private.token.length}" />
	</bean>
	<!-- DAOs -->
	<bean id="pasteItemDao" class="com.mysticcoders.mysticpaste.persistence.hibernate.PasteItemDaoImpl">
		<property name="sessionFactory" ref="sessionFactory"/>
	</bean>
	<!--  Database Beans -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
		<property name="driverClassName" value="${jdbc.driver}"/>
		<property name="url" value="${jdbc.url}"/>
		<property name="username" value="${jdbc.username}"/>
		<property name="password" value="${jdbc.password}"/>
	</bean>
	<!-- Hibernate session factory -->
	<bean id="sessionFactory"
		  class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
		<property name="dataSource" ref="dataSource"/>
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">${hibernate.dialect}</prop>
				<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
				<prop key="use_outer_join">${hibernate.use_outer_join}</prop>
				<prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}</prop>
				<prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
				<prop key="hibernate.cache.provider_class">${hibernate.cache.provider}</prop>
				<prop key="hibernate.connection.pool_size">10</prop>
				<prop key="hibernate.jdbc.batch_size">1000</prop>
				<prop key="hibernate.bytecode.use_reflection_optimizer">true</prop>
			</props>
		</property>
		<property name="annotatedClasses">
			<list>
				<value>com.mysticcoders.mysticpaste.model.PasteItem</value>
			</list>
		</property>
		<property name="schemaUpdate" value="${hibernate.schemaUpdate}"/>
	</bean>
	<!-- Tell Spring it should use @Transactional annotations -->
	<tx:annotation-driven/>
	<bean id="transactionManager"
		  class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory"/>
	</bean>
</beans>

The Service is configured as a bean, with a reference to the Dao implementation and to a variable that will be replaced by maven when building for the appropriate platform, as mentioned in day 1. The Dao is configured with a reference to Hibernate’s sessionFactory bean, and the rest is the configuration for Hibernate (the data source, the transaction manager, etc.).
Since we’re using annotations, we need to set the property annotatedClasses with a list of the Hibernate (or JPA) configured entity classes, in this case the PasteItem class. In order for the @Transactional annotation to work we need to tell Spring that our transaction is driven by those annotations with the <tx:annotation-driven/> tag. I’ve been involved in previous projects where the other developers think they are doing transactions because they used the annotation, but forgot to add this piece to the configuration, thus resulting in database inconsistencies.

Conclusion

Designing the backend involves very little Wicket (or nothing at all as we saw here), but it’s very important to separate our different layers to make the application easier to maintain. Using Spring and Hibernate is win-win situation because we leave many configuration options to Spring, and we are able to provide an easy to use and easy to understand implementation of our service and persistence layer.

Mystic Coders, LLC has been coding web magic since 2000. Mystic is a full-service Development Agency specializing in Enterprise development with Java. They are usually involved in developing enterprise-grade software for companies large and small, and have experience working in diverse industries, including b2b, b2c, and government-based projects. Mystic has done work with large companies such as LeapFrog, Nestlé, Harrah's Entertainment and the Los Angeles Conventions & Visitor's Bureau, among others. Andrew Lombardi, CTO of Mystic, is available for speaking engagements.

For more about Mystic, check us out at http://www.mysticcoders.com 

Published at DZone with permission of its author, Guillermo Castro.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Marcus Bosten replied on Thu, 2009/03/12 - 11:56am

Hi,

 this is a very interesting article series. However I have some Remarks:

- To implement the Business Rules insite the service layer as you proposed is one way - but I prefer to handle Business Logic within Rich Domain Models (german speakers might taka a look at http://www.rheinjug.de , "Dependency Injection für Geschäftsmodelle"). It keeps the Service simple and supports Single Point of Maintenace.

- One of the big advantages of Wicket is to reduce XML configuration to a minimum. The usage of Spring is shrinking that advantage. Everyone to his taste - but Google Guice is more after my flavour.

 

Thank you for delivering insight into this approach,

Marcus


 

 

 

 

Guillermo Castro replied on Thu, 2009/03/12 - 1:46pm in response to: Marcus Bosten

I'd have to disagree with you somewhat on the use of Rich Domain Models. In the case of the article, we have a very simple domain model consisting of one main entity, in which case we could arguably implement the business rules inside of it. However, as systems grow in complexity, you will be adding more domain objects to the system, and if each one contains its own business rules, then you lose that Single Point of Maintenance feature. Also, as more complex business rules appear in your application, you might want to consider using a business rule engine to manage those, with the models acting only as a go-between.

Also, using Spring's xml configuration allow us to have a single point of maintenance. We could've used Spring annotations to configure everything, but then we'd have to hunt all the annotations around.

Thank you for reading the article. As you said, this is just one of many approaches, and we could've done it any way but ended up choosing what we think it's best, based on our familiarity with the tools.

Guillermo

Cynthia Lennon replied on Wed, 2009/04/22 - 2:32am

It keeps the Service simple and supports Single Point of Maintenace.
Dissertation Writing - Assignment Writing - Custom Dissertation

Scoot Tim replied on Fri, 2009/05/15 - 3:33pm

however, as systems grow in complexity, you will be adding more domain objects to the system, and if each one contains its own business rules, then you lose that Single Point of Maintenance feature. Also, as more complex business rules appear in your application, you might want to consider using a business rule engine to manage those...

jame jack replied on Wed, 2009/06/17 - 7:48pm

تحميل برامج برامج جوالات العاب بنات تكنولوجيا كتب تعليم UltraSurf Internet Download Manager ProgDVB برامج مجانية أفضل المواقع العربية مشاهدة محطات مشفرة Online TV Player 3.0.0.940 Internet Download Manager 5.17 Build 4 رقص شرقي anyTV Pro 4.32 OnLineLive 7.1.1 هزي يانواعم ProgDVB 6.06.2 SopCast 3.0.3 Falco Image Studio 3.6 لعبة تزلج على الجليد UltraSurf 9.4 كاثرين هيغل Katherine Heigl محطة غنوة FreeZ Online TV 1.0 Free Video to Mp3 Converter 3.1.3.51 Advanced MP3 Converter 2.10 Xilisoft Video to Audio Converter 5.1.23.0515 Blaze Media Pro 8.02 AKRAM Media Creator 1.11 DVD Audio Extractor 4.5.4 Free WMA to MP3 Converter 1.16 لعبة نينجا المتقدم لعبة قذف كرة لعبة دراجات البهلوانية لعبة اعداء الغابة تحميل برامج Download DivX Subtitles 2.0 BullGuard 8.5 Google Chrome 2.0.181.1 Dev Dell Studio XPS Desktop 435T Intel Matrix Storage Manager A00 Gigabyte GA-EP45-UD3P Bios F9 Ambush HDConvertToX 1.1.229.1764 MSI Wind Nettop CS 120 Realtek Audio Driver 5.10.0.5618 Biostar T41-A7 6.x Realtek On-Board Audio Driver 5.10.0.5735 for 2000/2003/XP TweakNow RegCleaner 4.1.1 SpeedItup Free 4.97 برامج العاب - Internet Download Manager - برامج جوالات - العاب - محطة غنوة - قنوات فضائية - بنات - تكنولوجيا - كتب تعليم - UltraSurf -

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.