Java里提倡使用面向接口编程以减弱组件之间的耦合,在上一篇里已经定义了
@Local
public interface Register {
public String register();
}
这一节里面将实现这个接口来通过测试。
现在应用系统有很多良好的分层方法,但是我还是喜欢Evans的领域驱动设计的方法。
1,视图层将使用RichFaces。
2,使用无状态SessionBean作为Facade对领域逻辑进行封装。
3,领域层实现核心业务逻辑,主要包括有实体,值对象,仓库,服务等。
源文件目录结构大致如下:
src
|----action (SLSB)
|----model (Service, Entity, Repository, Vo)
|----test
view
|----css
|----js
……
经过简单的分析model层向facade暴露UserView接口
package org.qpt.domain.user;
import org.qpt.domain.user.User.RegState;
/**
* 用户接口
* @author xiaoqiu
*/
public interface UserView {
String getConfirmPwd();
String getEmail();
String getName();
String getPwd();
/**
* 注册一个新用户
* @param ur 用户仓库接口
* @return 注册结果状态码(共5种状态,上一节已经提到过)
*/
RegState register(UserRepository ur);
void setConfirmPwd(String confirmPwd);
void setEmail(String email);
void setName(String name);
void setPwd(String pwd);
}
注:因为EJB2严重的依赖于容器的基础设施,如果在实体bean中包含业务逻辑,将很难进行测试,所以实体bean只能作为数据容器,而背离面向对象的分析和设计方法。但是EJB3已经可以对实体进行透明持久化,并且很容易在容器外进行测试,所以就用User实体实现UserView接口,这里用户注册的并没有跨越多个实体,只涉及用户实体,所以包含了业务方法register的User类代码如下:
/**
* 用户实体
*
* @author xiaoqiu
*/
@Entity
@Name("user")
@Scope(SESSION)
@Table(name = "users")
public class User implements Serializable, UserView {
private static final long serialVersionUID = 3836699581343986461L;
@Id
@NotNull
@Length(min = 6, max = 15)
private String name;
@NotNull
@Length(min = 6, max = 15)
private String pwd;
@Transient
private String confirmPwd;
@NotNull
@Email
private String email;
@Transient
private String confirmEmail;
public enum RegState {
EXISTS_EMAIL, EXISTS_NAME, PWD_NOT_EQ_IN_TWICE, EMAIL_NOT_EQ_IN_TWICE, SUCCESS
}
@Override
public RegState register(UserRepository ur) {
if (!pwd.equals(confirmPwd)) {
return PWD_NOT_EQ_IN_TWICE;
} else if (!email.equals(confirmEmail)) {
return EMAIL_NOT_EQ_IN_TWICE;
} else if (ur.findByProperty("name", name)) {
return EXISTS_NAME;
} else if (ur.findByProperty("email", email)) {
return EXISTS_EMAIL;
} else {
ur.save(this);
return SUCCESS;
}
}
……
get&set method
}
考虑到视图层RichFaces和hibernate验证结合可以进行很简单的零脚本ajax验证,所以本例子里面使用了hibernate验证。
由于register方法需要用的UserRepository来访问数据库,所以现在来看一下UserRepository接口。通过隔离基础设施,这就是GOF设计模式中著名的Strategy模式,由于Java语言想要在实体中注入UserRepository在技术上存在困难,所以把UserRepository作为方法的参数传递我个人认为也是一种较为简洁的方法,而且通过接口的隔离不会和基础设施产生强烈的耦合,UserRepository代码如下:
/**
* 用户仓库接口
* @author xiaoqiu
*/
@Local
public interface UserRepository {
void save(User user);
User findByName(String name);
List<User> findAllUser();
/**
* 找出该属性是否存在当前值
* @param name 属性名
* @param value 属性值
* @return 该属性存在这个值返回true,否则返回false
*/
boolean findByProperty(String name, String value);
}
在本例里面UserRepository的实现使用的JPA,这个接口的实现相当容易所以它的实现类UserRepositoryJPAImp代码就不贴出来了,可以直接看提供的源码。
model层的接口已经实现完毕,接下来实现facade,代码如下:
@Stateless
@Name("register")
public class RegisterBean implements Register {
@Logger
private Log log;
@In
FacesMessages facesMessages;
@In
private UserView user;
@In("userRepository")
UserRepository ur;
public String register() {
RegState state = user.register(ur);
switch (state) {
case PWD_NOT_EQ_IN_TWICE:
log.info("the register state is " + state);
facesMessages.addFromResourceBundle("myapp.register.pwd.noteq");
break;
case EMAIL_NOT_EQ_IN_TWICE:
log.info("the register state is " + state);
facesMessages.addFromResourceBundle("myapp.register.email.noteq");
break;
case EXISTS_NAME:
log.info("the register state is " + state);
facesMessages.addFromResourceBundle("myapp.register.name.exists");
break;
case EXISTS_EMAIL:
log.info("the register state is " + state);
facesMessages.addFromResourceBundle("myapp.register.email.exists");
break;
case SUCCESS:
log.info("register a new user #{user.name}" + "\nthe register state is " + state);
facesMessages.addFromResourceBundle("myapp.register.success");
break;
}
return state.toString();
}
}
这里facade是很薄的一层,不能包含任何业务逻辑,它主要根据model返回的状态是向显示层发送信息,使用facesMessages组件就可以很容易向Richfaces发送ajax验证的响应信息,并且事务处理统一在facade层进行,由于使用EJB,所以就可以使用容器提供的事务管理。
上述类的结构如下图所示:
facade: Register-->RegisterBean-->UserView&UserRepository
domain:UserView-->User(Eneity)<-->UserRepository-->UserRepositoryJPAImp
至此为止,注册用户所需要的全部接口都已经实现,现在就可以用seam test命令来运行上一节写好的测试代码了。