[Spring] REST API 구현 : HATEOAS, Swagger

진예·2024년 2월 19일
0

Backend : Spring

목록 보기
3/8
post-thumbnail

💡 REST API 구현

스프링 부트 + JPA를 사용하여 회원 관리 API (등록, 조회, 삭제) 구현하기

✔️ 도메인

  • @Table(name = "users") : H2 시스템 내에 user 테이블이 존재하므로, 생성될 테이블의 이름을 users로 지정

  • @JsonIgnore : 조회 시 반환하지 않을 데이터 → 비밀번호와 같은 보안에 민감한 데이터

@Entity
@NoArgsConstructor
@Table(name = "users")
@Getter
public class User {
    @Id @GeneratedValue
    private Long id;
    private String name;
    private int age;

    @JsonIgnore
    private String password;

    public User(String name, int age, String password) {
        this.name = name;
        this.age = age;
        this.password = password;
    }
}

✔️ 리포지토리

  • 스프링 데이터 JPA가 제공하는 JpaRepository 사용 : 기본적인 CRUD 기능 제공
public interface UserRepository extends JpaRepository<User, Long> {}

✔️ 컨트롤러

  • @RestController = @Controller + @ResponseBody : API 구현 시 사용

    • @Controller : 페이지 반환
    • @ResponseBody : 반환되는 데이터를 JSON 등의 타입으로 변환하여 응답 메시지의 바디에 담아서 반환
  • UserDTO : 회원 목록 조회회원 수 + 회원 목록 형태로 반환하기 위한 DTO

@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {
    private final UserRepository repository;

    @GetMapping
    public UserDTO allUsers() {
        List<User> users = repository.findAll();
        return new UserDTO(users.size(), users);
    }

    @PostMapping
    public ResponseEntity<User> save(@RequestBody User user) {
        User saveUser = repository.save(user);
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }

    @GetMapping("/{id}")
    public User oneUser(@PathVariable Long id) {
        Optional<User> optional = repository.findById(id);
        if(optional.isEmpty()) throw new UserNotFoundException(id+"번 회원을 찾을 수 없습니다.");

        return optional.get();
    }

    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        Optional<User> optional = repository.findById(id);
        if(optional.isEmpty()) throw new UserNotFoundException(id+"번 회원을 찾을 수 없습니다.");

        repository.deleteById(id);
    }

    @Data
    @AllArgsConstructor
    static class UserDTO {
        private int cnt;
        private List<User> users;
    }
}

ex1) GET /users : 회원 목록 조회

ex2) POST /users : 회원 저장 → 상태코드 201 created 반환

ex3) GET /users/{id} : 특정 회원 조회 → 존재하지 않는 회원일 경우 404 not found 반환

ex4) DELETE /user/{id} : 특정 회원 삭제 → 존재하지 않는 회원일 경우 404 not found 반환


📒 HATEOAS

Hypermedia As The Engine Of Application State : 요청과 관련된 URI응답에 담아서 반환

ex) 회원 등록 시 회원 목록 + 특정 회원 조회 링크 제공, 특정 회원 조회 시 회원 목록 링크 제공 등 ...


⭐ REST 구현의 3단계

  1. 리소스의 도입 : 서버가 가진 모든 리소스에 대한 고유한 URI 제공
  2. HTTP 동사 : HTTP Method를 통해 리소스에 수행하고자 하는 작업을 표현
  3. HATEOAS : 해당 자원에 대해 호출 가능한 API 정보상태에 반영하여 표현

ex) 회원 삭제 API 설계

LvExplainRequestResponse
1리소스 표현POST /users/1/deleteJSON
2메서드를 통한 작업 표현DELETE /users/1JSON
3관련 API 링크 제공DELETE /users/1JSON + LINK (등록, 목록 조회 ... 등)

📝 스프링 부트 + HATEOAS 구현

특정 회원 조회회원 목록, 회원 삭제 링크 전달하기

✔️ 라이브러리 추가 (build.gradle)

implementation 'org.springframework.boot:spring-boot-starter-hateoas'

✔️ 컨트롤러

  • EntityModel : 결과 데이터 + 관련 링크 반환

    • add() : 링크 추가 ➕ withRel() : 해당 링크에 대한 참조 (설명)
  • WebMvcLinkBuilder : 링크 생성 타겟 컨트롤러 내의 타켓 메서드매핑된 URI

    • linkTo() : 컨트롤러 클래스를 가리키는 WebMvcLinkBuilder 객체 반환
    • methodOn() : 타겟 메서드의 가짜 메서드 콜이 들어있는 컨트롤러 프록시 클래스를 생성
@GetMapping("/{id}")
public EntityModel<User> oneUser_HATEOAS(@PathVariable Long id) {
    Optional<User> optional = repository.findById(id);
    if(optional.isEmpty()) throw new UserNotFoundException(id+"번 회원을 찾을 수 없습니다.");
    User user = optional.get();

    EntityModel<User> entityModel = EntityModel.of(user);
    WebMvcLinkBuilder allUser = linkTo(methodOn(this.getClass()).allUsers());
    entityModel.add(allUser.withRel("all-user"));

    return entityModel;
}

ex) GET /users/2 : 2번 회원 정보 + 회원 목록 API 링크 반환


📒 Swagger

개발한 REST API를 자동으로 문서화시키는 프레임워크 → API 목록 확인 및 테스트 가능

✔️ 라이브러리 (build.gradle)

  • Springfox (2020.07 이후로 업데이트 X → doc 사용 권장)
  • Springdoc ⭐ (스프링 부트 3.x 이후 버전부터 springdoc-openapi-ui 사용 시 404 에러 발생)
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'

라이브러리 추가 후 /swagger-ui/index.html에 접속하면 아래와 같은 페이지 확인 가능! (기본값 : 생성된 모든 API 컨트롤러 (@RestController) + 객체 출력)

✔️ 설정 : SwaggerConfig.java

  • @OpenAPIDefinition(info) : API 명세서의 기본 정보 (제목, 설명, 버전 등)

  • GroupedOpenApi : 여러 API 컨트롤러그룹화하여 표시

    • group : 그룹 이름
    • pathsToMatch : 그룹에 속하는 경로 패턴
@Configuration
@OpenAPIDefinition(
        info = @Info(
                title = "회원 관리 API 명세서",
                description = "회원 관리 API 명세서",
                version = "v1.0"
        )
)
public class SwaggerConfig {

    @Bean
    public GroupedOpenApi userAPI() {
        return GroupedOpenApi.builder()
                .group("회원 관리")
                .pathsToMatch("/users/**")
                .build();
    }
}

✔️ 도메인

  • @Schema : 모델(객체)에 대한 상세 정보
@Entity
@NoArgsConstructor
@Table(name = "users")
@Getter
@Schema(description = "회원 정보를 관리하기 위한 도메인 객체")
public class User {

    @Id @GeneratedValue
    @Schema(title = "사용자 id", description = "자동 생성")
    private Long id;

    @Schema(title = "사용자 이름", description = "2 ~ 15글자 이내")
    private String name;

    @Schema(title = "사용자 나이", description = "5세 이상부터 가입 가능")
    private int age;

    @JsonIgnore
    @Schema(title = "비밀번호", description = "8 ~ 20자 이내의 영소문자+숫자+특수기호(!~_=+@#) 조합")
    private String password;

    ...
}

✔️ 컨트롤러

  • @Tag : 클래스(컨트롤러)에 대한 상세 정보
  • @Operation : 특정 경로에 대한 작업(메서드)에 대한 상세 정보
  • @Parameter : 파라미터에 대한 상세 정보
  • @ApiResponse : 응답 상태코드에 대한 상세 정보
@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
@Tag(name = "users", description = "회원 관리 컨트롤러")
public class UserController {
    private final UserRepository repository;

    @GetMapping
    @Operation(summary = "회원 목록 API", description = "전체 회원 목록을 조회")
    public UserDTO allUsers() {
        List<User> users = repository.findAll();
        return new UserDTO(users.size(), users);
    }

    @PostMapping
    @Operation(summary = "회원 등록 API", description = "새로운 회원을 등록")
    public ResponseEntity<User> save(@RequestBody User user) {
        User saveUser = repository.save(user);
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }

    @GetMapping("/{id}")
    @Operation(summary = "회원 조회 API", description = "사용자 ID를 통해 특정 회원 조회")
    @ApiResponses({
            @ApiResponse(description = "OK", responseCode = "200"),
            @ApiResponse(description = "NOT FOUND", responseCode = "404"),
            @ApiResponse(description = "INTERNAL SERVER ERROR", responseCode = "500")
    })
    public EntityModel<User> oneUser_HATEOAS(@Parameter(description = "사용자 ID", required = true, example = "1")
                                                 @PathVariable Long id) {
        Optional<User> optional = repository.findById(id);
        if(optional.isEmpty()) throw new UserNotFoundException(id+"번 회원을 찾을 수 없습니다.");
        User user = optional.get();

        EntityModel<User> entityModel = EntityModel.of(user);
        WebMvcLinkBuilder allUser = linkTo(methodOn(this.getClass()).allUsers());
        entityModel.add(allUser.withRel("all-user"));

        return entityModel;
    }

    @DeleteMapping("/{id}")
    @Operation(summary = "회원 삭제 API", description = "사용자 ID를 통해 특정 회원 삭제")
    public void deleteUser(@PathVariable Long id) {
        Optional<User> optional = repository.findById(id);
        if(optional.isEmpty()) throw new UserNotFoundException(id+"번 회원을 찾을 수 없습니다.");

        repository.deleteById(id);
    }

    ...
}
  • @Tag + @Operation
  • @Operation + @Parameter
  • @ApiResponses

🙇🏻‍♀️ 참고 1 : [개정판 2023-11-27] Spring Boot 3.x 를 이용한 RESTful Web Services 개발
🙇🏻‍♀️ 참고 2 : Hypermedia-driven REST API
🙇🏻‍♀️ 참고 3 : [SpringBoot] Swagger API 문서 자동화 간단 연동, 테스트하기
🙇🏻‍♀️ 참고 4 : [Spring Boot] Springdoc 라이브러리를 통한 Swagger 적용

profile
백엔드 개발자👩🏻‍💻가 되고 싶다

0개의 댓글