Monday, July 23, 2012

Production grade REST services


Writing a REST service in Java is easy, writing a production grade one is almost as easy :)

A simple service

// JSR 311 REST service
@Path("myservice")
public class MyDataLoaderREST {

    @PUT
    @Consumes({"application/json"})
    public void loadFile(String csvdata) {
      CSVReader reader = new CSVReader(new StringReader(data));
      String nextLine = reader.readNext();
      Customer c = new Customer( nextLine[0],nextLine[1]);
      em.persist(c);
    }

}

The above service will work, but it is not really production grade in my opinion.

1) It does not return a positive or negative value
2) If something goes wrong like the csv is malformed, it will be next to impossible to debug.

The solution simple service

1) Always return a result, never return a HTTP error, the end user will never be able to figure out what is wrong.
2) If something obvious and something not obvious go wrong, return the line where the error happened.
3) Also don't treat all exceptions in the same way. Catch a FileNotFoundException and a IOException, you may want to handle differently.
4) Catch the RuntimeException as a final resort
5) Write using TDD!

The test!

// A simple JUnit test for testing the import

public class TestMyDataLoaderREST {

    @Test

    public void testImportCustomer() throws IOException {

        MyDataLoaderREST importFile = new MyDataLoaderREST();

        NodeCalculationLoadResult calcResult = importFile.loadFile("1,2");

        Assert.assertTrue(calcResult.isSuccess());

   }

    @Test

    public void testImportCustomerNull() throws IOException {

        MyDataLoaderREST importFile = new MyDataLoaderREST();

        NodeCalculationLoadResult calcResult = importFile.loadFile(null);

        Assert.assertFalse(calcResult.isSuccess());//make sure an exception was not thrown

        Assert.assertEquals(1,calcResult.getlineNumber());//make sure it's not line 0

   }

The service

// The JSR 311 improved service

import au.com.bytecode.opencsv.CSVReader;

@Path("myservice")

public class MyDataLoaderREST {

    @PUT

    @Consumes({"application/json"})

    public CustomerResult loadFile(String csvdata) {

        int lineNumber = 0;// line number outside try to catch can use it

        try {

         

          CSVReader reader = new CSVReader(new StringReader(data));

          if (nextLine.length < 2) {

                    return new CustomerResult(false, 1, "Error reading at line " + lineNumber + " line should have two or more values");

                }

           while ((nextLine = reader.readNext()) != null) {

           }
            return new CustomerResult(false, -1, lineNumber +" lines parsed and imported");

         } catch (ParseException ex) {

            return new CustomerResult(false, lineNumber, "Error parsing data at line " + lineNumber + " " + ex.getMessage());

        } catch (NumberFormatException ex) {

            return new CustomerResult(false, lineNumber, "Error reading number at line " + lineNumber + " " + ex.getMessage());

        } catch (RuntimeException ex) {

            return new CustomerResult(false, lineNumber, "Programmatic error reading number at line " + lineNumber + " " + ex.getMessage());

        }

    }

}


The domain Class @XmlRootElement
@Data // Project Lombok does all my getters and setters for me

// The domain object

@Data // Project Lombok does all my getters and setters for me

public CustomerResult {

     private boolean success;

     private int lineNumber;

     private String errorMessage;

     private Customer customer;

     public CustomerResult(boolean success , int lineNumber,String errorMessage){

       this.success = success;

       this.lineNumber = lineNumber;

       this.errorMessage = errorMessage;

      }

      public CustomerResult(Customer customer){

         this.customer = customer;

      }

      

}


3 comments:

  1. It isn't clear where the CustomerResult is returned, I guess its after your while loop?

    ReplyDelete
    Replies
    1. Hi Kiren,

      updated :)
      And code looking a lot better too.

      cheers

      Delete