Monthly Archives: January 2016

Layering in Java Webapps – My Final Version

What a good layering solution should do

It must handle the following problems:

1. Dividing a system into layers based on modularization principles, such as single-direction dependency, limited entry of components and so on. 

2. Compatible with modern dependency management tools such as Maven 

3. Allowing for evolving into a distributed system in the future 

Here I present my final version of layering after so many years’ development of java webapps.


Layers

layering

Components’ Responsibilities

Component Responsibility Naming Conventions
Biz Layer – Neutral Services  Most reusable core business logic(CRUD, Biz Modelling etc.)

Bean: Xxx

Facade: XxxService

Repository: XxxRepos/ XxxDAO

Application Layer – Front-End Managers The complete logic of use cases for front end users (customers)

Bean: FoXxx

Facade: FoXxxManager

Application Layer – Back-Office Managers The complete logic of use case for back office users (admin, staff etc.) 

Bean: BoXxx

Facade: BoXxxManager 

Application Layer – Partner System Service Providers Remote services for partner systems which belong to the same company

Bean: SsXxx(Ss= Some System)

Facade: SsRpc

Web Layer – Front-End Controllers

Web MVC controllers to serve front end users with browsers

Presentation only. No biz at all

Bean: N/A 

Facade:  FoXxxController

Web Layer – Front-End Web Services

Web Services to serve front end users’ rich clients (desktop/phone app)

Presentation only. No biz at all 

Bean: N/A

Facade: FoXxxResource/FoXxxSoap

Web Layer – Back-Office Controllers

Web MVC controllers to serve back office users with browsers

Presentation only. No biz at all

Bean: N/A

Facade: BoXxxController 

(Continue)

Component Bean DTO Dependency on Layer Below
Biz Layer – Neutral Services 

Fine-grained entity

Data oriented 

Beans as DTOs 

N/A
Application Layer – Front-End Managers

Very coarse-grained

Crossing multiple biz entities 

User oriented

Request/Response pairs as DTOs

Response has error props

Order order = convertSomehow(foNewOrderRequest); 

authService.permissionCheck(currentUserId…) ;

orderService.saveNewOrder(order);

itemService.doAnotherThing(…);

FoOrder foOrder = combineSomehow(order, item, permission);

return FoResponse.success(foOrder); 

Application Layer – Back-Office Managers

Very coarse-grained

Crossing multiple biz entities

User oriented

Similar with above

Similar with above
Application Layer – Partner System Service Providers

Fairly fine-grained 

Partner system oriented 

Beans as DTOs

Order order = convertSomehow(ssOrder); 

itemService.doAnotherThing();

SsOrder ssOrder = addOrRemoveProp(order);

return ssOrder; 

Web Layer – Front-End Controllers N/A N/A

FoNewOrderRequest foNewOrderRequest = webFormToObj(httpRequest); 

FoResponse foResponse  = foOrderManager.doSth(currentUserId, foNewOrderRequest);

httpResponse.putSomehow("data",  foResponse.getData());

httpResponse.putSomehow("error",  foResponse.getError());

Web Layer – Front-End Web Services N/A N/A

FoNewOrderRequest foNewOrderRequest = jsonToObj(jsonRequest); 

FoResponse foResponse  = foOrderManager.doSth(currentUserId, foNewOrderRequest);

return toRestResponse(foResponse); 

Web Layer – Back-Office Controllers N/A N/A Similar with "Front-End Controllers"

Be Pragmatic (Anti-Patterns)

1. Managers are allowed to call repositories(DAOs) directly. It is annoying to have to have a same-name method in XxxService to wrap  its counterpart in XxxRepo.

2. BoXxx/BoRequest/BoResponse should rely on and be allowed to extend FoXxx/FoRequest/FoResponse,  and Back office Controllers should be allowed to call Front end managers.  This is because  back office users are also users.  If you don’t allow this "slight violation", you may end up write tons of duplicate code in Bo Managers. 

3. Web layer annotations such as @XmlElement should be allowed to put on application layer beans and DTOs.  If not, you will have to create a lot of duplicate beans/DTOs on web layer. 


Maven Projects

ideal-maven-projects

(Note: webapp must have "runtime" dependency on impl_fo and impl_bo, which has not been shown in the diagram.)

This is a ideal version of separation.  The problem is it has too many maven projects.   

A pragmatic version


Support for Distributed System Design

1. The maven project "intf-pso" is used as the RPC client stub for partner systems

2. The maven project "intf-fo" can be used as the RPC client stub for web service clients if they are also written in Java

3. The web layer can be an independent system immediately without any compilation error since they only rely on app-layer interfaces at compile time.  Just let intf-fo and intf-bo be its client stub.  In advance, the web layer’s components can each be an independent system. 

If the web layer is transferred to another team

If the web layer is going to be transferred to another team in your organisation, the app layer should go with it. Otherwise, a small change will involve two team’s work, which is unbearable.

They will now be responsible for all the user-specific cases and considered as your partner system. 

web-another-team

The original FO and BO managers should not rely on the biz services any more.  Instead, you must create pso interfaces to wrap them and provide services to the original managers.  


A demo webapp

For a demo webapp with most of the components describe, see  this project on github

Code Snippet: A common duplet class

Java doesn’t allow multi-value method returning. To return 2 values for a method, you can create a "pair" object which contains two values.

/**
 * a pair of objects. like other containers, you'd rather return an empty
 * container than a null one
 * 
 * 
 *
 */
public class MyDuplet<L,R> {
	public L left;
	public R right;

	public static  MyDuplet<L,R> newInstance(L left, R right) {
		MyDuplet<L,R> instance = new MyDuplet<L,R>();
		instance.left = left;
		instance.right = right;
		return instance;
	}

	@Override
	public String toString() {
		return ToStringBuilder.reflectionToString(this,
				ToStringStyle.SHORT_PREFIX_STYLE);
	}
}

MyBatis: Automatically set timestamps for beans saved

The Problem

When a bean is inserted or updated, its "createdAt" or "updatedAt" should be set as the current time. MyBatis won’t do that for you by default.

You can manually set them before inserting them. But you may forget it.

Solution

The solution is to have a MyBatis interceptor which does the job before a bean is saved.

@Intercepts({ @Signature(type = Executor.class, method = "update", args = {
		MappedStatement.class, Object.class }) })
public class RepoInterceptor implements Interceptor {

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		MappedStatement stmt = (MappedStatement) invocation.getArgs()[0];
		Object param = invocation.getArgs()[1];
		if (stmt == null) {
			return invocation.proceed();
		}

		if (stmt.getSqlCommandType().equals(SqlCommandType.INSERT)) {
			if (param != null && param instanceof EntityBase) {
				EntityBase e = (EntityBase) param;
				if (e.getCreatedAt() == null) {
					e.setCreatedAt(new GregorianCalendar());
				}
			}
		}

		if (stmt.getSqlCommandType().equals(SqlCommandType.UPDATE)) {
			if (param != null && param instanceof EntityBase) {
				EntityBase e = (EntityBase) param;
				if (e.getUpdatedAt() == null) {
					e.setUpdatedAt(new GregorianCalendar());
				}
			}
		}

		return invocation.proceed();
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {

	}
}

And in mybatis-config.xml

 


	


Code Snippet: Trim all string fields of a bean

You may find it useful where you can’t use Spring MVC’s StringTrimEditor

the trim method

	/**
	 * trim string fields to null. 

	 * Note: only the root string fields are trimmed. string fields of the
	 * bean's composite fields are let alone. 

	 * All reflection related security exceptions are ignored.
	 * 
	 * @param bean
	 */
	public static void trimRootLevelStringFields(Object bean) {
		if (bean == null) {
			return;
		}

		Field[] fields = bean.getClass().getDeclaredFields();
		if (fields == null) {
			return;
		}

		for (Field f : fields) {
			if (f.getType().isPrimitive()) {
				continue;
			}

			if (f.getType().equals(String.class)) {
				try {
					f.setAccessible(true);
					String value = (String) f.get(bean);
					f.set(bean, StringUtils.trimToNull(value));
				} catch (IllegalAccessException e) {
				}

			}
		}
	}


Apply it with all *Manager classes using Spring AOP

@Component
@Aspect
public class ManagerAspect {

	@Before("bean(*Manager)  && execution(public * *(..))")
	public void doAdvice(JoinPoint joinPoint) {	 
		Object[] args = joinPoint.getArgs();
		if (args == null) {
			return;
		}
		for (Object arg : args) {
			if (arg == null) {
				continue;
			}
			// if it is a request object
			if (arg.getClass().getSimpleName().endsWith("Request")) {
				SomeClass.trimRootLevelStringFields(arg);
			}
		}
	}
}

bean-validation + spring example

Add bean validation dependency

Hibernate provides an implementation of bean-validation. So we will dependent on it. But it has nothing to do with Hiberante ORM.

			
			
				org.hibernate
				hibernate-validator
				5.1.3.Final
			

Create a validator class as a spring bean

		

@Component 
public class MyBeanValidator {

	@Resource 
	private Validator validator;

  
 
	public <T> List<String> validateBean(T bean) {
		List<string> errors = new ArrayList<string>();
		Set<ConstraintViolation<T> violations = validator.validate(bean);
		for (ConstraintViolation<T> violation : violations) {
			errors.add(violation.getMessage());
		}
		return errors;
	}

	public void setValidator(Validator validator) {
		this.validator = validator;
	}

}

Use It

@RunWith(MySpringJunit4ClassRunner.class)
@ContextConfiguration(locations = { "/spring/applicationContext-test.xml" })
public class MyBeanValidatorITCase {

	@Resource
	MyBeanValidator myBeanValidator;

 

	@Test
	public void validateBean_InvalidProp() {
		Mb mb = new Mb();
		List<String> errors = myBeanValidator.validateBean(mb, "bean-null");
		Assert.assertEquals(2, errors.size());
		Assert.assertTrue(errors.contains("email-null"));
		Assert.assertTrue(errors.contains("password-null"));
	}

	@Test
	public void validateBean_NoErrors() {
		Mb mb = new Mb();
		mb.email = "hi@hi.com";
		mb.password = "heyya";
		List<String> errors = myBeanValidator.validateBean(mb, "bean-null");
		Assert.assertEquals(0, errors.size());

	}

	private static class Mb {

		@NotNull(message = "email-null")
		private String email;

		@NotNull(message = "password-null")
		private String password;
	}

}