I was evaluating Guice,JUnit-4,while trying out with these same I wrote a sample application using the same. & also tried out a possible solution for the problem with data conversion.
In a typical web application we also use heavily with get/set to convert from UI representation of object to back end implementation object. Many a times (In my experience most of the time) they are all heavy parallel structures required by framework (Like classic struts forces UI object to extend & ActionFormBean, limited data type support) or data type representation in the different UI models. With the advent of domain driven design we have more rich domain objects which usually contains many other data/behaviour which should not be or need not be exposed to the UI layer.
The problem with this conversion is that they are not only look dumb consuming lot of source code lines but they are error-prone & since there is no contract with back end we are forced to unit test the code as we cannot rely on compile time checks. I also tried a possible solution for this, 3 years back I tried out implementing this in a classic struts based web application & was successful in reducing the number of bugs.
The sample application is user management system. (Please note that the code has been written in such a way to show the usage Guice, JUnit & type safe data conversion & shouldn't be mistaken as real time design,there are no exceptions, validation etc...). It follows a typical MVC pattern followed in web applications.
Model Layer |
\src\model\User.java |
1 /*
2 * To change this template, choose Tools Templates
3 * and open the template in the editor.
4 */
5
6 package model;
7
8 import controller.IUser;
9
10 /**
11 *
12 * @author praveenm
13 */
14 public class User implements IUser,java.io.Serializable {
15
16 private String userName;
17 private String eMail;
18 private int age;
19 private String fullName;
20 private static final String DELIMETER=",";
21
22 private AuditInfo auditInfo = new AuditInfo();
23
24 public String getUserName() {
25 return userName;
26 }
27
28 public User(){
29
30 }
31
32
33 public void setUserName(String userName) {
34 this.userName = userName;
35 }
36
37 public String getEMail() {
38 return eMail;
39 }
40
41 public void setEMail(String eMail) {
42 this.eMail = eMail;
43 }
44
45 public int getAge() {
46 return age;
47 }
48
49 public void setAge(int age) {
50 this.age = age;
51 }
52
53 public String getFirstName() {
54 return fullName.split(DELIMETER)[0];
55 }
56
57 public void setFirstName(String firstName) {
58 if(getLastName()!=null){
59 fullName = firstName+DELIMETER+getLastName();
60 }
61 }
62
63 public String getLastName() {
64 return fullName.split(DELIMETER)[1];
65 }
66
67 public void setLastName(String lastName) {
68 if(getFirstName()!=null){
69 fullName = getFirstName()+DELIMETER+lastName;
70 }
71
72
73 }
74 public void setFullName(String str){
75 fullName=str;
76
77 }
78
79 public AuditInfo getAuditInfo() {
80 return auditInfo;
81 }
82
83 public void setAuditInfo(AuditInfo auditInfo) {
84 this.auditInfo = auditInfo;
85 }
86
87
88
89 }
IUserService - Exposing the functionality of user management service
\src\model\IUserService.java |
1 package model;
2
3 import ui.IUser;
4 import java.util.List;
5
6 /**
7 * A simmple service representing the general User management activities.
8 * @author praveenm
9 */
10 public interface IUserService {
11
12 User get(String userName);
13 boolean saveOrUpdate(User user);
14 boolean delete(String userName);
15
16 boolean saveOrUpdate(IUser user,AuditInfo info);
17 List<IUser> getUsers();
18 }
19
20
UserServiceMockImpl.java - Implements the service by saving the content in a file using object serialization.
\src\model\AuditInfo.java |
1 /*
2 * To change this template, choose Tools Templates
3 * and open the template in the editor.
4 */
5
6 package model;
7
8 import java.io.Serializable;
9 import java.util.Date;
10
11 /**
12 *
13 * @author praveenm
14 */
15 public class AuditInfo implements Serializable {
16 private String updatedBy;
17 private String createdBy;
18 private Date updatedDate;
19 private Date createdDate;
20 private boolean isAdmin;
21
22 public String getUpdatedBy() {
23 return updatedBy;
24 }
25
26 public void setUpdatedBy(String updatedBy) {
27 this.updatedBy = updatedBy;
28 }
29
30 public String getCreatedBy() {
31 return createdBy;
32 }
33
34 public void setCreatedBy(String createdBy) {
35 this.createdBy = createdBy;
36 }
37
38 public Date getUpdatedDate() {
39 return updatedDate;
40 }
41
42 public void setUpdatedDate(Date updatedDate) {
43 this.updatedDate = updatedDate;
44 }
45
46 public Date getCreatedDate() {
47 return createdDate;
48 }
49
50 public void setCreatedDate(Date createdDate) {
51 this.createdDate = createdDate;
52 }
53 @Override
54 public String toString(){
55 return "updatedBy=["+updatedBy+"] "+
56 "createdBy=["+createdBy+"] ";
57 }
58
59 public boolean isIsAdmin() {
60 return isAdmin;
61 }
62
63 protected void setIsAdmin(boolean isAdmin) {
64 this.isAdmin = isAdmin;
65 }
66 }
67
68
UI Layer |
1 package ui;
2 /**
3 *
4 * @author praveenm
5 */
6 public interface IUser extends java.io.Serializable {
7
8 int getExperience();
9 String getEMail();
10 String getFirstName();
11 String getLastName();
12 String getUserName();
13 void setExperience(int Experience);
14 void setEMail(String eMail);
15 void setFirstName(String firstName);
16 void setLastName(String lastName);
17 void setUserName(String userName);
18
19
20 }
21
22
UserForm.java - UI bean honouring java bean spec & can extend the classes like ActionFormBean representing the HTML form in the screen.
\src\ui\UserForm.java |
1 package ui;
2
3 import ui.IUser;
4 import java.io.Serializable;
5
6 /**
7 *
8 * @author praveenm
9 */
10 public class UserForm implements IUser,Serializable {
11 private String userName;
12 private String eMail;
13 private int experience;
14 private String firstName;
15 private String lastName;
16
17 public String getUserName() {
18 return userName;
19 }
20
21 public void setUserName(String userName) {
22 this.userName = userName;
23 }
24
25 public String getEMail() {
26 return eMail;
27 }
28
29 public void setEMail(String eMail) {
30 this.eMail = eMail;
31 }
32
33 public int getExperience() {
34 return experience;
35 }
36
37 public void setExperience(int experience) {
38 this.experience = experience;
39 }
40
41 public String getFirstName() {
42 return firstName;
43 }
44
45 public void setFirstName(String firstName) {
46 this.firstName = firstName;
47 }
48
49 public String getLastName() {
50 return lastName;
51 }
52
53 public void setLastName(String lastName) {
54 this.lastName = lastName;
55 }
56
57 }
58
59
controller |
\src\controller\UserController.java |
1 package controller;
2
3 import ui.IUser;
4 import com.google.inject.Inject;
5 import java.util.List;
6 import model.AuditInfo;
7 import model.IUserService;
8
9 /**
10 *
11 * @author praveenm
12 */
13 public class UserController {
14
15 private final IUserService service;
16 private AuditInfo info;
17
18 @Inject
19 public UserController(IUserService _service){
20 service=_service;
21 System.out.println("UserController Instaniated ");
22 }
23 @Inject
24 public void setAuditInfo(AuditInfo info){
25 this.info=info;
26 }
27
28 public boolean save(final IUser user){
29 return service.saveOrUpdate(user,info);
30 }
31
32 public IUser get(String userName){
33 return service.get(userName);
34 }
35
36 public boolean delete(String userName){
37 if(!info.isIsAdmin()){
38 throw new IllegalAccessError("You don;t have the permission to delete");
39 }
40 System.out.println("users"+service.getUsers());
41 return service.delete(userName);
42 }
43 public List<IUser> users(){
44 return (List<IUser>)service.getUsers();
45 }
46
47 }
48
49
Unit test layer |
\test\MockBinder.java |
1 package test;
2
3 import com.google.inject.AbstractModule;
4 import java.util.Date;
5 import model.AuditInfo;
6 import model.IUserService;
7 import model.UserServiceMockImpl;
8
9 /**
10 *
11 * @author praveenm
12 */
13 public class MockBinder extends AbstractModule {
14
15 final boolean isAdmin;
16
17 public MockBinder() {
18 isAdmin = false;
19 }
20
21 public MockBinder(boolean isAdmin) {
22 this.isAdmin = isAdmin;
23 }
24
25 @Override
26 protected void configure() {
27 binder().bind(IUserService.class).to(UserServiceMockImpl.class).asEagerSingleton();
28 if (!isAdmin) {
29 binder().bind(AuditInfo.class).to(DummyAuditInfo2.class);
30 } else {
31 binder().bind(AuditInfo.class).to(DummyAuditInfo.class);
32
33 }
34 }
35 }
36
37 class DummyAuditInfo extends AuditInfo {
38
39 public DummyAuditInfo() {
40 super.setIsAdmin(true);
41 super.setCreatedBy("Praveen M");
42 super.setCreatedDate(new Date());
43 super.setUpdatedBy("Praveen");
44 super.setUpdatedDate(new Date());
45 }
46 }
47
48 class DummyAuditInfo2 extends AuditInfo {
49
50 public DummyAuditInfo2() {
51 super.setIsAdmin(false);
52 super.setCreatedBy("someone");
53 super.setCreatedDate(new Date());
54 super.setUpdatedBy("someone");
55 super.setUpdatedDate(new Date());
56 }
57 }
58
59
60
UserTest.java - & now finally we have unit testing code with JUnit4 test case show-casing the usage of the applications.
test\UserTest.java |
1 package test;
2
3 import com.google.inject.Guice;
4 import ui.IUser;
5 import controller.UserController;
6 import junit.framework.Assert;
7 import org.junit.BeforeClass;
8 import org.junit.Test;
9 import ui.UserForm;
10
11 /**
12 * The power & simplicity of JUnit4 test cases.
13 * @author praveenm
14 */
15 public class UserTest {
16
17 private static UserController controller;
18
19 @BeforeClass
20 public static void bootStrap(){
21 controller = Guice.createInjector(new MockBinder(true)).getInstance(UserController.class);
22 }
23
24 @Test
25 public void save(){
26 IUser form = new UserForm();
27 form.setExperience(9);
28 form.setEMail("praveen.manvi@yahoo.com");
29 form.setFirstName("praveen");
30 form.setUserName("pmanvi");
31 form.setLastName("Manvi");
32 Assert.assertTrue(controller.save(form));
33
34 form.setExperience(10);
35 form.setEMail("praveen.manvi@yahoo.com");
36 form.setFirstName("praveen");
37 form.setUserName("pmanvi123");
38 form.setLastName("m");
39 Assert.assertTrue(controller.save(form));
40 }
41
42 @Test
43 public void get(){
44 IUser user = controller.get("pmanvi");
45 if(user!=null)
46 Assert.assertEquals("praveen", user.getFirstName());
47 }
48
49 @Test
50 public void delete(){
51 // if(controller.get("pmanvi123")!=null)
52 Assert.assertEquals(controller.delete("pmanvi123"), true);
53 }
54
55 @Test(expected= IllegalAccessError.class)
56 public void unauhorizedDelete(){
57 controller = Guice.createInjector(new MockBinder(false)).getInstance(UserController.class);
58 controller.delete("pmanvi123");
59 }
60
61 }
62
63
------- Final Summary------- |
Although it makes(or forces) back end to be aware of UI & other form of clients, in some cases (like we we use ORMs instead of JDBCTemplate) one time conversion logic shifts from UI to backend & it might put more development load on persistent layer team. My guess is these objections can be ignored because of the benefit of to cleaner & less error prone code. In case of distrbuted enviornment all the extra fields can be set to null to reduce the payload, but anyway number of rows are much more important than the number of columns in deciding the data size.
Hope that this sample application help new comers to understand/appreciate the value of Guice & JUnit4 libraries.
References;
Guice Dependency Injection Framework from Google
1 comment:
Enjoying your blog
Post a Comment