Monday, 30 April 2012

Decorator Pattern & Checked Exception Wrapping

Have you ever come across a situation that checked exceptions made your code looking ugly and hard writing unit tests?
I’ve recently spent some time on a REST service acting like a man in the middle between two distributed systems. The service was interacting between two systems. Due to nature of distributed unreliable systems there has to be exception checking for every communication function amongst systems. Obviously checked exceptions over time has made rest service class look nasty and writing unit tests started to take more time. I’ve realized that in the rest service, I was just doing some logging, returning meaningful error codes in exception checking blocks, so I decided to wrap checked exceptions with a decorator so that service class would look better.
To give a high level brief, I have an interface defining some functions with a checked exception declaration.

1. Interface definition
public interface ExecutionManager {
  public Boolean checkJobExists(String job) throws ExecutionException;
  public void createNewJob(String job) throws ExecutionException;
  public void updateNext(String jobName, Integer nexId) throws ExecutionException;
}

2. Rest Service

An implementation of ExecutionManager is used throughout REST service class where all checked exceptions are wrapped with custom runtime WebException

@Path("execute")
@Singleton
public class ExecutionImpl  {
@Override
    public JAXBElement<BuildStatus> buildStatus(UriInfo ui,
                                                String packName,
                                                String packVersion,
                                                String packType,
                                                String buildId) {
// code blocks at here
        ExecutionManager mng;
        try {
            mng = this.buildManagement();
            exists = mng.checkIobExists(job);
        } catch (ExecutionException e) {
            throw new WebException(Response.Status.INTERNAL_SERVER_ERROR, 
WebExceptionBean.ErrorCode._EXECUTION_ERROR, 
"Unable to check job is on execution server", e);
        }

// code blocks at here
       
        try {
            ExecutionStatus jbs = mng.getBuildStatus(job, requestedBuild);
            return objectFactory.createBuildStatusResponse(jbs);
        } catch (ExecutionException e) {
            throw new WebException(Response.Status.INTERNAL_SERVER_ERROR, 
WebExceptionBean.ErrorCode._EXECUTION_ERROR, 
"Unable to retrieve build details from execution server", e);
        }
    }
}


3. Decorating checked exceptions

I’ve created a new decorator class implementing the Executionmanager and accepting it as the constructor parameter. With decorator pattern, I am able to remove throws declaration and wrap checked ExecutionExpception, removal of throws declaration is allowed, as the decorator is not introducing a new exception  to be checked.

public class ExceptionWrappingDecorator implements ExecutionManager {
    ExecutionManager manager;
    public ExceptionWrappingDecorator(ExecutionManager manager) {
        this.manager = manager;
    }

@Override
   public Boolean checkJobExists(String jobName) { // removed throws declation
       try {
           return this.manager.checkJobExists(jobName);
       } catch (ExecutionException e) {
           throw new WebException(Response.Status.INTERNAL_SERVER_ERROR,
               WebExceptionBean.ErrorCode.TEST_PACK_EXECUTION_ERROR,
               "Unable to check job is on execution server", e);
       }
   }

@Override
public void createNewJob(String jobName) {  //removed throws declation
    try {
        this.manager.createNewJob(jobName);
    } catch (TestPackExecutionException e) {
        throw new TesWebException(Response.Status.INTERNAL_SERVER_ERROR,
            TesWebExceptionBean.ErrorCode.TEST_PACK_EXECUTION_ERROR,
            "Unable to create new test pack job on execution server", e);
    }
}

@Override
public void updateNext(String jobName, Integer nextBuildId) {
        try {
            this.manager.updateNextBuildId(jobName, nextBuildId);
        } catch (ExecutionException e) {
            throw new WebException(Response.Status.INTERNAL_SERVER_ERROR,
                WebExceptionBean.ErrorCode.EXECUTION_ERROR, e,
                "Unable to update set nextbuildId -> %s of job -> %s on execution server", nextBuildId, jobName);
        }
}


4. Improved new REST service class

Rest service class looks cleaner with decorator usage, obviously main purpose of the service class is dealing with requests instead of being drown in try-catch blocks. By creating exception decorator, side effect codes are moved away from service class while not losing any information carried in the exceptions.

@Path("execute")
@Singleton
public class ExecutionImpl  {  
@Override
public JAXBElement<BuildStatus> buildStatus(UriInfo ui,
                                            String packName,
                                            String packVersion,
                                            String packType,
                                            String buildId) {

// code blocks at here 

   ExceptionWrappingDecorator mng=ExecutionHelper.getExceptionWrappingDecorator();
    boolean exists = mng.checkJobExists(jobName);

// code blocks at here

  ExecutionStatus jbs = mng.getBuildStatus(jobName, requestedBuild);
}


5. Simplified Unit Tests for Exception Decorator

Decorator pattern has also helped refactoring unit tests and increasing the test coverage.

public class ExceptionWrappingManagerTest {
    @Mock
    ExecutionManager tpmng;
    @Mock
    ExecutionException ex;
    ExceptionWrappingDecorator mng;
    String job = "error-job";

    public ExceptionWrappingManagerTest() {
        MockitoAnnotations.initMocks(this);
        mng = new ExceptionWrappingDecorator(tpmng);
    }

    @Test(expected = WebException.class)
    public void testCheckJobExists() throws Exception {
        Mockito.when(tpmng.checkIfJobExists(job)).thenThrow(ex);
        mng.checkIfJobExists(job);
    }

    @Test(expected = WebException.class)
    public void testCreateNewJob() throws Exception {
        Mockito.doThrow(ex).when(tpmng).createNewJob(job);
        mng.createNewJob(job);
    }

    @Test(expected = TesWebException.class)
    public void testUpdateNextBuildId() throws Exception {
        Integer id = 1;
        Mockito.doThrow(ex).when(tpmng).updateNextBuildId(job, id);
        mng.updateNextBuildId(job, id);
    }

}

No comments:

Post a Comment