I am currently working on building a fluent interface over soap toolkits. I did discussed the possibilities of rich APIs value addition. One of the problem with fluent APIs is that we have to get rid of checked exception to make it easier, but there are cases where fault containment is necessary. I am just thinking the better appraoach would be to generate the unchecked Exception counterparts for each of the fault code & provide the easier error handling.
For Example: (I have modified sample shown by the Martin Fowler), Let us assume that newOrder process will talk to 2 other web services & there is possibilities of 2 soapfault errors.
import static test.Customer.newOrder;
private void orderNew(){
newOrder() .
with(6, "TAL") .with(5, "HPK").skippable()
.withDollar(35.d) .withAccountId(23423l).withLicense("AS900980") .
.with(3, "LGV")
.priorityRush();
}
@Test
public void test(){
try{
orderNew();
}catch(GenricException exp){
switch(exp.getErrorCode()){
case SOAPFaultsCodes.E1001:
// handle the exception possibly giving more useful message or some other recovery action
case SOAPFaultsCodes.7003:
}
throw new RuntimeException("Unhandled Error");
}
}
Here again GenericExcption is abstract Exception extedning RuntimeException that is implemented by all the soap fault exception correspding to the errorcode & will be thrown by the rich APIs.
I guess with JDK7 I guess we can have String based switch() & upgraded catch block.
Well, for unit testing exception we can laverege the annotations support to assert with JUnit4
@Test(expected = E7069Exception.class)
I wanted to generate all these Exceptions automatically from soap faults document, For that I wrote this groovy script. (show casing the usage of multiline string, closure & file I/O)
constantsFile=""
converFileLineIntoException = {
sarray = it.split(" ")
exception = sarray[0]+"Exception"
errorCode = exception.substring(1,sarray[0].length())
constantsFile+="\n public static final int E$errorCode = $errorCode;"
className =
"""package com.yahoo.sm.ws.builders.exception;
// Generated code
public class $exception extends GenericException {
private String description;
private String shortDescription;
public $exception(String description, String shortDescription){
this.description=description;
this.shortDescription=shortDescription;
}
public int errorCode(){
return $errorCode;
}
public String getDescription(){
return this.description;
}
public String shortDescription(){
return this.shortDescription;
}
}
"""
new File(sarray[0]+"Exception.java").write(className)
}
def lines = new File("soapFaultList.txt").eachLine(converFileLineIntoException)
soapFaults = """
package com.yahoo.sm.ws.builders.exception;
public interface SOAPFaultsCodes {
$constantsFile
}"""
new File("SOAPFaultsCodes.java").write(soapFaults)
println "--- Gr8 I am done "
Groovy, JUnit4 & static imports just rocks :-)
Friday, October 17, 2008
Labels:
fluent api,
Groovy,
soap
Sunday, October 12, 2008
Dependency Injection with Guice, JUnit 4 & Get-Set problem:
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.
User.java Represents a User domain object. As we notice it also contains the information that is populated through context (Logged in user, context, date, etc... usually through HttpSession or EJBContext) in the form of AuditInfo object. Here the fullName field (which doesn't make sense to UI) representing the firstName & fullName as 'firstName,lastName' in a single field. This is done to show how easily back end object can be refactored to accomadate client requirements. This can be even applied to data types. Since extracting of interface from any class is supported by all the IDEs, there is no coding effort required here.
IUserService - Exposing the functionality of user management service
UserServiceMockImpl.java - Implements the service by saving the content in a file using object serialization.
IUser.java - Now this is the trick we have IUser that's required by UI layer (which is always a subset of back end object) that's being implemented by the UI model object.
UserForm.java - UI bean honouring java bean spec & can extend the classes like ActionFormBean representing the HTML form in the screen.
UserController.java - Controls the logic flow b/w UI & the back end. Here is the place we will introduce the dependency injection. In a typical web application audit information is captured in HttpSession object, since in test envioronment we will not be having references to HttpRequest, HttpResponse etc... we will inject those data with our Guice & also we will inject service implementation which could be changed without disturbing the other layers.
MockBinder.java - As I am obsessed with fluent interface, generics & type safety, it's really enjoyable to wire up dependencies using Guice APIs rather than through XML.
UserTest.java - & now finally we have unit testing code with JUnit4 test case show-casing the usage of the applications.
Guice simplifies the testing a lot doing everything in java, If you just want dependency Injection, Guice beats all other frameworks (Spring...) hands down in ease of use
JUnit4 is much easy to use with annotations & now we don;t have to extend any class & don't have to write methds with test...(). I really liked the way we can test the Exceptions
Adding interface to both backend & UI objects we can get rid of get/set noise.
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 JUnit 4 - with Java5 features & is quite different from older versions.
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
Subscribe to:
Posts (Atom)