ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Boot API - User Service, Exception Handing...
    FrameWork/Spring&Spring-boot 2024. 2. 19. 12:24

    User Service

    도메인

    도메인 각 회사들이 가지고 있는 도메인을 뜻하는게 아니라 도메인 knowledge라고 해서 비지니스 서비스에서 사용되는 업무 영역, 업무 규칙, 업무 지식을 의미한다.

     

    User도메인

    사용자 정보를 어떤 형태,어떤 구조로 저장할지 어떤 데이터를 저장할지에 대해 설계를 하고 사용자에 아이디, 이름, 가입 날짜와 같은 것들이 들어 갈 것이다. 

     

    UserDaoService.java

    @Component
    public class UserDaoService {
    }

     

    스프링 컨텍스트에 Component로 등록 스프링 컨텍스트가 이해할 수 있는 빈의 유형을 등록하는 것이다. 

     

    Retrieve User resource

    @RestController
    public class UserController {
        private UserDaoService service;
    
        public UserController(UserDaoService service){
            this.service = service;
        }
    
        @GetMapping("/users")
        public List<User> retrieveAllUser(){
            return service.findAll();
        }
    
        @GetMapping("/users/{id}")
        public User retrieveUser(@PathVariable int id){
            return service.findOne(id);
        }

     

    스프링에서는 개발자가 직접 Service 인스턴스를 생성을해서 사용하는 것이 아니라 스프링 프레임워트 내에서 사용가능한 인스턴스들이 Bean이라는 이름으로서 스프링 컨텍스트에서 관리되고 있다. 따라서 필요한 인스턴스가 있을 경우에는 이미 등록된 스프링 인스턴스를 호출하는 방식으로 사용하면 된다. 


    Exception Handling

    throw new UserNotFoundException 추가

    public class UserNotFoundException extends RuntimeException{
        public UserNotFoundException(String message){
            super(message);
        }
    }

     

        @GetMapping("/users/{id}")
        public User retrieveUser(@PathVariable int id){
            User user = service.findOne(id);
    
            if(user == null){
                throw new UserNotFoundException(String.format("ID[%s] not found", id));
            }
    
            return user;
        }

     

    에러를 던질때 Exception자체를 사용할 수도 있고 Error 객체를 사용할 수도 있다.  개발자가 제어할 수 있는 형태는 Exception이어야 한다. 따라서 UserNotFoundException은 RuntimeException을 상속 받아서 만든다.  

     

    상황에 맞는 에러 코드 반환

    500번대 error라는 건 서버측에 어떤 문제가 생겼다는 것이다. 더불어 해당 에러가 발생하면서 생성된 객체를 그대로 클라이언트에게 전달하는 것도 바람직하지 않을 수 있다. 왜냐하면 해당 객체에는 보안에 예민한 정보가 들어있기도 하기 때문다. 따라서 500 에러로 서버의 error 트레이스를 그대로 노출 시키기보다는 상황에 적절한 error코드를 반환하게 한다.

     

    @ResponseStatus추가

    @ResponseStatus(HttpStatus.NOT_FOUND)
    public class UserNotFoundException extends RuntimeException{
        public UserNotFoundException(String message){
            super(message);
        }
    }

    Generic Exception Handling

    예외 클래스를 위해서 exception response라는 POJO 객체를 선언한다. 예외가 발생한 시간,메시지, 상세정보와 같은 정보를 담고 있는 일반화된 예외객체로 사용 

     

    generic exception → ExceptionResponse class 

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class ExceptionResponse {
        private Date timestamp;
        private String message;
        private String details;
    }

     

     

    ResponseEntityExceptionHandler 검색

    @RestController
    @ControllerAdvice
    public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
    
        @ExceptionHandler(Exception.class)
        public final ResponseEntity<Object> handleAllException(Exception ex, WebRequest request){
            ExceptionResponse exceptionResponse =
                    new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
    
            return new ResponseEntity(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    
        @ExceptionHandler(UserNotFoundException.class)
        public final ResponseEntity<Object> handleUserNotFoundException
        (Exception ex, WebRequest request){
            ExceptionResponse exceptionResponse =
                    new ExceptionResponse(new Date(), ex.getMessage(), request.getDescription(false));
    
            return new ResponseEntity(exceptionResponse, HttpStatus.NOT_FOUND);
        }
    }

     

    @ControllerAdvice 애노테이션을 사용해서 특정한 빈을 등록해 놓게 되면 모든 Controller가 실행 될때 마다@ControllerAdvice   어노테이션이 등록된 빈을 실행하게 설정을 할 수 있다.

     

    그리고 위의 정의된 컨트롤러 어드바이스 빈이 실행되는 와중에 Exception이 발생하게 되면 @ExeptionHandler(Exception.class)애노테이션 아래에 정의된 매서드가 실행되게 된다. 

     

    ExceptionResponse를 생성할때 3번째 인자로 들어가는 request.getDescription(false)는 client에 request에 대한 상세 정보를 반환하지 않겠다는 것을 의미한다.


    Versionig

    URI를 사용해서 버전관리 

    /admin/users/{id} → /admin/v1/users/{id}

    @GetMapping("/users/{id}")
        public MappingJacksonValue retrieveUserAdmin(@PathVariable int id){
            User user = service.findOne(id);
       ...
    }

     

    @GetMapping("/v1/users/{id}")
        public MappingJacksonValue retrieveUserAdmin(@PathVariable int id){
            User user = service.findOne(id);
       ...
    }

     

    URI 변경

    REST API에서도 자원에 대한 정보 뿐만 아니라 현재 api의 버전이 어떤 것인지도 같이 표시 해주는것이 좋다. 

     

    Add a new user bean 

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @JsonFilter("UserInfoV2")
    public class AdminUserV2 {
        private Integer id;
        ...
    }

     

     

    URI versioning

    @GetMapping("/v2/users/{id}")
        public MappingJacksonValue retrieveUserAdminV2(@PathVariable int id){
            User user = service.findOne(id);
    
    
            if(user == null){
                throw new UserNotFoundException(String.format("ID[%s] not found",id));
            }
    
            AdminUserV2 adminUserV2 = new AdminUserV2();
            BeanUtils.copyProperties(user,adminUserV2);
            adminUserV2.setGrade("VIP"); //grade
    
            SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter
                    .filterOutAllExcept("id","name","joinDate","grade");
    
            FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfoV2",filter);
    
            MappingJacksonValue mapping =  new MappingJacksonValue(adminUserV2);
            mapping.setFilters(filters);
    
            return mapping;
        }

     

    Paramter를 이용한 버전관리

    request parameter versioning

        @GetMapping("/users/{id}", params = "version=1")
        public MappingJacksonValue retrieveUserAdmin(@PathVariable int id){
            User user = service.findOne(id);
            ....
            }

     

        @GetMapping("/users/{id}", params = "version=2")
        public MappingJacksonValue retrieveUserAdminV2(@PathVariable int id){
            User user = service.findOne(id);
            ....
            }


    Header를 이용한 버전관리

     

    header versioning

        @GetMapping(value = "/users/{id}", headers = "X-API-VERSION=1")
        public MappingJacksonValue retrieveUserAdmin(@PathVariable int id){
            User user = service.findOne(id);
            ...
            }

     

        @GetMapping(value = "/users/{id}", headers = "X-API-VERSION=2")
        public MappingJacksonValue retrieveUserAdminV2(@PathVariable int id){
            User user = service.findOne(id);
            ...
            }

     

     

     


    mime - type를 이용한 버전관리

    mime-type은 데이터 전송을 할 때 특정한 type이나 특정한 데이터가 어떤 유형인지 알려주는 형식이다. Multipurpose Internet Mail Extensions의 약자로 이메일 데이터와 함께 전송되는 파일을 텍스트 문자로 변환해서 이메일 서버에 전송을 했어야 했는데 그 때 사용되었던 포맷 양식이다. 현재는 웹을 통해서 여러형태의 파일을 주고 받기 위해서 사용하는 일종의 파일 형식이다. 

    @GetMapping(value = "users/{id}", produces = "application/vnd.company.app1+json")
        public MappingJacksonValue retrieveUserAdmin(@PathVariable int id){
            User user = service.findOne(id);
            ...
            }

     

        @GetMapping(value = "users/{id}", produces = "application/vnd.company.app2+json")
        public MappingJacksonValue retrieveUserAdminV2(@PathVariable int id){
            User user = service.findOne(id);
            ...
            }

     

     

     

    정리

    일반 브라우저에서 실행 가능

    • URI Versioning
      • ex) Twitter
    • Request Parameter versioning
      • ex)Amazon

    일반 브라우저에서 실행 불가

    • (Custom) headers versioning
      • ex) Microsoft
    • Media type versioning(a.k.a "content negotiation" or "accept header")
      • ex) GitHub

     

    [출처 - Spring Boot 3.x를 이용한 RESTful Web Service 개발, 저 Dowon Lee]

    https://www.inflearn.com/course/spring-boot-restful-web-services

    댓글

Designed by Tistory.