Rest API Simple

김파란·2024년 5월 27일

SpringAdv

목록 보기
4/8
  • Rest API는 MSA를 위한것이라고 해도 과언이 아님

1. WebService vs WebApplication

1). WebService

  • 네트워크 상에서 서로 다른 종류의 컴퓨터들 간에 상호작용하기 위한 소프트웨어 시스템
  • 월드 와이드 웹(www)과는 달리 웹 서비스는 순수 컴퓨터와 컴퓨터간의 상호작용을 위한 시스템
  • SOAP, WSDL, UDDI중 한가지만 사용해도 웹 서비스를 구현한 것으로 정의한다
  • 웹서비스로 인해 XML 기반 SOAP같이 표준화된 통신 수단을 사용하여 서로 다른 플랫폼 기반 어플리케이션 통신이 가능해졌다
    참고) https://itwarehouses.tistory.com/5

2). WebApplication

  • 서버에 저장되어 있고 브라우저를 통해 실행하는 있는 어플리케이션
  • 온라인 쇼핑몰, 뱅킹 등등 우리가 알고 있는 흔한 서비스

(1). SOAP VS RESTful

1. SOAP(Simple Object Access Protocol)
  • http,https,smtp등을 이용해서 xml기반의 메시지를 교환하는 형태
  • 기본적인 메시지 전송 수단
  • 프록시와 방어벽에 구애받지 않고 비교적 쉽게 통신이 가능하고 플랫폼이나 프로그래밍 언어에 독립적이고 확장가능한 장점이 있지만 비교적 느리다
2. RESTful(REpresentational State Transfer)
  • SOAP은 무겁고 느리고 개발하기 어려워서 restful탄생
  • 컴퓨터나 스마트폰같은 두 단말기간의 인터넷을 통해서 정보를 교환하기 위해서 사용되는 인터페이스
  • 해당하는 자원의 상태(Status), 정보를 주고받는 모든것들을 RESTful이라고 보면 된다
  • HTTP Method를 통해 Resource를 처리하기 위한 아키텍처
  • 기본적으로 HTTP를 사용해서 HTTP Method로 제공한다
  • API: 어플리케이션간에 서로 통신하고 서로 사용하기 위해서 규정된 약속
  • Resource: URI -> 인터넷 자원을 나타내는 유일한 주소 (XML, HTML, JSON)
  • EndPoint: API를 통해 서버가 제공하는 리소스에 접근하기 위해서 제공되는 주소

2. SWAGGER

  • 개발자가 RESTFul 웹서비스를 설계하고, 빌드하고, 문서화하고, 소비할때 필요한 모든일들을 도와주는 오픈소스
  • swagger toolset를 통해서 자동화된 문서와 코드생성, 테스트케이스 생성을 자동으로 해줌
  • 지금은 OpenAPI Specification이란 이름으로도 사용중임 (두개의 이름)

1). OpenAPI의 라이프사이클

  • 설계 -> 프로토타입 -> 구현 -> 테스팅 -> 시뮬레이션(가상화 작업) -> 문서화 작업 -> 배포 -> 클라이언트에게 사용할 수 있도록 해줌

3. Service API

// 회원가입 성공처리가 되면 어디로 가면 볼수있는지 uri을 헤더를 통해서 response
@PostMapping("/users")
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User savedUser = service.save(user);
        
        URI location = ServletUriComponentsBuilder
        .fromCurrentRequest().path("/{id}").buildAndExpand(savedUser.getId()).toUri();
	   //현재 Request에 뒤에 path를 붙인다 예로들어 /users/{id} <-이 id값에는 saved.getId를 넣는다
       // 라는 것
        return ResponseEntity.created(location).build();
    }

1). Exception 처리

// RestApi Exception
@ControllerAdvice
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Object> handleAllExceptions(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);
    }


}

4. Response 데이터 형식변환- xml

  • 의존성 추가하면 xml파일로 Response해줌
  • 요청을 할때 accept를 application/xml으로 바꾸면 된다
implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.17.1'

5. Validation

  • valid처리시 예외처리 메시지바디가 잘못됐을때 쓰는 코드
// mvc 요청바디에 유효성 검사 실패했을때 쓰는 예외
@Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), "validation failed", ex.getBindingResult().toString());
        return new ResponseEntity<>(exceptionResponse, HttpStatus.BAD_REQUEST);
    }

6. Filtering

  • json으로 보여주고 싶지 않을때
@JsonIgnoreProperties(value = {"password","ssn"})
public class User {
    private Integer id;
    private String name;
    private Date joinDate;
//    @JsonIgnore
    private String password;
//    @JsonIgnore
    private String ssn;
}
  • Admin이 User를 개별로 조회할때 ssn은 추가하고싶음
@JsonFilter("UserInfo")
public class AdminUser {
    private Integer id;
    private String name;
    private Date joinDate;

    private String password;
    private String ssn;
}

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

        AdminUser adminUser = new AdminUser();
        if (user == null) {
            throw new UserNotFoundException(String.format("ID[%s] not found", id));
        } else{
        // admin.setName(user.getName) 일일이 이렇게 치기 힘드니까 setter를 이용해서 넣어준다
            BeanUtils.copyProperties(user,adminUser); 
        }
		// id,name,joinDate,ssn은 filter에서 제외하게 해주고 
        SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept("id", "name", "joinDate", "ssn");
        // provider형태로 변해야 사용가능, 어디에다가 적용시킬건지 지정 // UserInfo라는곳에 filter를 적용
        FilterProvider filters = new SimpleFilterProvider().addFilter("UserInfo",filter);

        MappingJacksonValue mapping = new MappingJacksonValue(adminUser);
        mapping.setFilters(filters);

        return mapping;
    }

7. 버전관리

  1. Path를 이용한 버전관리: @GetMapping(value="/v1/users")
  2. 파라미터를 활용: @GetMaipping(value="/users/{id}", params="version=1")
  3. 헤더활용: @GetMapping(value="/users/{id}", headers="X-API-VERSION=1")
  4. 헤더활용-2 Mime활용 : @GetMapping(value="/users/{id}", produces="application/vnd.company.appv1+json")
    -> Accept: application/vnd.company.appv1+json

8. Swagger

1). Hateoas

  • 현재 리소스와 연관된(호출 가능한) 자원 상태를 제공

(1). Richardson Maturity Model

  • RestFul를 설계할때 성수도 모델에서 말하는 레벨 단계에 따라서 API를 업데이트 할수있다
  1. LEVEL 0: HTTP 사용 -> 단일 URL, 단일 메소드
  2. LEVEl 1: URL Resource -> /movies로 모든 요청을 하는게 아니라 /posts/1
  3. LEVEL 2: Http Status +Http Method로 요청 대상과 목적을 파악 -> GET /posts/1/comments?from=20220424&to=20221027 여기까지 Http API라고 한다
  4. LEVEL 3: URI + HTTP Method + HATEOAS -> 다음 단계로 할 수 있는 작업(전이)를 알려주는 것, 응답받은 메시지를 통해 뭘하는지 알수있는걸 뜻함
  • implementation 'org.springframework.boot:spring-boot-starter-hateoas'
@GetMapping("/users")
    public List<User> retrieveAllUsers() {
        return service.findAll();
    }

     @GetMapping("/users/{id}")
    public EntityModel<User> retrieveUser(@PathVariable int id) {
        User user = service.findOne(id);

        if (user == null) {
            throw new UserNotFoundException(String.format("ID[%s] not found", id));
        }

        EntityModel entityModel = EntityModel.of(user);
        // @GetMapping("/users")를 호출할수있는 링크를 넣어준다는 의미
        WebMvcLinkBuilder linTo = linkTo(methodOn(this.getClass()).retrieveAllUsers());
        entityModel.add(linTo.withRel("all-users")); // all-users -> http://localhost:8080/users
        return entityModel;
    }

2). 스프링부트 Swagger

  • 3.x 스프링부트 의존성추가implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.5.0'

(1). 제목 정보표시

@OpenAPIDefinition(
        info = @Info(title = "My Restful Service API 명세서",
        description = "Spring Boot로 개발하는 RESTful API 명세서 입니다.",
        version = "v1.0.0")
)
@Configuration
public class NewSwaggerConfig {
    @Bean
    public GroupedOpenApi customOpenApi() {
        String[] paths = {"/users/**", "/admin/**"};
        return GroupedOpenApi.builder()
                .group("일반 사용자와 권리자를 위한 User 도메인에 대한 API")
                .pathsToMatch(paths) // 원하는 URI만 제공
                .build();
    }
}

(2). 엔티티 정보 표시

  • JsonIgnore하면 정보표시가 안됨
@Schema(description = "사용자 상세 정보를 위한 도메인 객체")
public class User {
    @Schema(title = "사용자 ID", description = "사용자 ID는 자동 생성됩니다.")
    private Integer id;
    @Schema(title = "사용자 이름", description = "사용자 이름을 입력합니다.")
    private String name;
    @Schema(title = "사용자 등록자", description = "사용자 등록일을 입력합니다. 입력하지 않으면 현재 날짜가 지정됩니다.")
    private Date joinDate;

    @Schema(title = "사용자 비밀번호", description = "사용자 비밀번호를 입력합니다.")
    private String password;

    @Schema(title = "사용자 주민번호", description = "사용자 주민번호 입력합니다.")
    private String ssn;
}

(3). 클래스 정보 표시

@Tag(name = "user-controller", description = "일반 사용자 서비스를 위한 컨트롤러") // 클래스정보 표시를 위한 어노테이션
public class UserController {
    private final UserDaoService service;

    @GetMapping("/users")
    public List<User> retrieveAllUsers() {
        return service.findAll();
    }

    @GetMapping("/users/{id}")
    @Operation(summary = "사용자 정보 조회 API", description = "사용자 ID를 이용해서 사용자  상세 정보 조회를 합니다")
    @ApiResponses(
            { // 메소드 에러 정보표시
                    @ApiResponse(responseCode = "200",description = "OK !! "),
                    @ApiResponse(responseCode = "400",description = "BAD REQUEST"),
                    @ApiResponse(responseCode = "404",description = "USER NOT FOUND"),
                    @ApiResponse(responseCode = "500",description = "INTERNAL SERVER ERROR!!"),
            }
    ) // 파라미터 정보 표시
    public EntityModel<User> retrieveUser(@Parameter(description = "사용자 ID", required = true,example = "1") @PathVariable int id)

9. HAL Explorer

  • 응답에 부가적인 정보를 제공해주는 API
  • API를 쉽게 사용할수 있도록 메타정보를 HyperLink형식으로 제공해주는 기능
  • 의존성만 추가하면 바로 사용 가능 localhost8080만 작성해도 됨
  • hal explorer 가 아니라 spring data rest hal explorer 사용
  • json 방식으로 쉽고 깔끔하게 보여줌 테스트도 가능

0개의 댓글