Sunday, December 14, 2014

Spring MVC : Rest Controller Versioning menggunakan Custom Annotation

Bagi kita yang tidak asing dengan Spring framewok tentu sangat mudah untuk membuat Rest Api dengan menggunakan class Controller. Kita hanya tinggal membuat method yang akan diexpose sebagai rest api dengan menggunakan RequestMapping.
Nah bagaimana jika kita ingin menggunakan versioning pada Rest APInya. Misalnya ada kasus seperti  ini :
Kita sudah punya Rest Api : /person. Tetapi pada suatu saat ada pengembangan baru di Api tersebut dan Api yang lama tidak boleh dihilangkan dulu karena masih ada pihak yang menggunakannya. Maka kita perlu memberikan version pada Rest api tersebut. Rest api yg lama akan dibiarkan. Kemudian dibuat rest api yang baru dengan nama /v2/person.
Cara yang paling sederhana yakni kita buat method controller baru dan diberi RequestMapping("/v2/person").
Tapi cara ini sebenarnya membuat code menjadi tidak clean dan tidak konsisten dalam penamaan.

Pada tulisan saya berikut ini akan saja coba cara untuk melakukan versioning menggunakan @Annotation dan custom RequestMapping.

1. Kita buat dulu class ApiVersion annotation

 @Target({ElementType.METHOD, ElementType.TYPE})  
 @Retention(RetentionPolicy.RUNTIME)  
 public @interface ApiVersion {  
   int[] value();  
 }  

2. Buat class custom RequestMapping handler

 public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {  
   private final String prefix;  
   public ApiVersionRequestMappingHandlerMapping(String prefix) {  
     this.prefix = prefix;  
   }  
   @Override  
   protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {  
     RequestMappingInfo info = super.getMappingForMethod(method, handlerType);  
     ApiVersion methodAnnotation = AnnotationUtils.findAnnotation(method, ApiVersion.class);  
     if(methodAnnotation != null) {  
       RequestCondition<?> methodCondition = getCustomMethodCondition(method);  
       // Concatenate our ApiVersion with the usual request mapping  
       if(info != null)  
         info = createApiVersionInfo(methodAnnotation, methodCondition).combine(info);  
     } else {  
       ApiVersion typeAnnotation = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);  
       if(typeAnnotation != null) {  
         RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);  
         // Concatenate our ApiVersion with the usual request mapping  
         if(info != null)  
           info = createApiVersionInfo(typeAnnotation, typeCondition).combine(info);  
       }  
     }  
     return info;  
   }  
   private RequestMappingInfo createApiVersionInfo(ApiVersion annotation, RequestCondition<?> customCondition) {  
     int[] values = annotation.value();  
     String[] patterns = new String[values.length];  
     for(int i=0; i<values.length; i++) {  
       // Build the URL prefix  
       patterns[i] = prefix+values[i];  
     }  
     return new RequestMappingInfo(  
         new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(), useSuffixPatternMatch(), useTrailingSlashMatch(), getFileExtensions()),  
         new RequestMethodsRequestCondition(),  
         new ParamsRequestCondition(),  
         new HeadersRequestCondition(),  
         new ConsumesRequestCondition(),  
         new ProducesRequestCondition(),  
         customCondition);  
   }  
 }  

Annotation @ApiVersion akan dibaca valuenya berapa kemudian akan digabungkan dengan value @RequestMapping, sehingga bisa menjadi /v2/person /v1/person saat diload pertama kali oleh Spring. Jika controller tanpa menggunakan annotation akan tetap dibaca /person

3. Buat class WebMvcConfig

 @Component  
 public class WebMvcConfig extends WebMvcConfigurationSupport {  
   @Bean  
   @Override  
   public RequestMappingHandlerMapping requestMappingHandlerMapping() {  
     return new ApiVersionRequestMappingHandlerMapping("v");  
   }  
 }  

Semua RequestMapping yang menggunakan ApiVersion otomatis akan diawali dengan ''v''.

4. Hilangkan tag annotation-driven diServletContext.xml, gantikan dengan class WebMvcConfig yang sudah dibuat tadi.

 <!--  
 <annotation-driven />  
 -->  
 <beans:bean class="com.andri.sample.version.WebMvcConfig" />  

5. Buat controller person

 @Controller  
 public class HomeController {  
      private static final Logger logger = LoggerFactory.getLogger(HomeController.class);  
      
      @RequestMapping(value = "/person", method = RequestMethod.GET)  
      @ResponseBody  
      public ResponseEntity<String> getPerson(){  
           String result = "this is rest person api version 1";  
           return getJsonResponse(result, HttpStatus.OK);  
      }  

      @RequestMapping(value = "/person", method = RequestMethod.GET)  
      @ResponseBody  
      @ApiVersion(2)  
      public ResponseEntity<String> getPersonV2(){  
           String result = "this is rest person api version 2";  
           return getJsonResponse(result, HttpStatus.OK);  
      }  
      public static <T> ResponseEntity<T> getJsonResponse(T src, HttpStatus status) {  
           HttpHeaders headers = new HttpHeaders();  
           headers.set("Content-Type", MediaType.APPLICATION_JSON_VALUE);  
           return new ResponseEntity<T>(src, headers, status);  
      }  
 }  

6. Start spring application menggunakan jetty atau tomcat, atau application server lain.

Sekarang coba akses url rest nya : /person dan /v2/person

http://localhost:8080/sample/person


http://localhost:8080/sample/v2/person



referensi : http://stackoverflow.com/questions/20198275/how-to-manage-rest-api-versioning-with-spring
source-code : https://andri_khrisharyadi@bitbucket.org/andri_khrisharyadi/spring-rest-versioning.git

Selamat Mencoba.

No comments:

Post a Comment