Spring

REST API (RESTful API)

kkkkkkkkkkkk 2023. 1. 19. 21:32

개념

  • REST 아키텍처 스타일의 제약 조건을 준수
  • RESTful 웹 서비스와 상호 작용할 수 있도록 하는 애플리케이션 프로그래밍 인터페이스(API 또는 웹 API)

API(Application Programming Interface)란?

  • 소비자에게 필요한 콘텐츠(호출)와 생산자에게 필요한 콘텐츠(응답)를 구성합니다.
    • 예, 날씨 서비스용 API
      • 사용자는 우편번호를 제공하고
      • 생산자는 두 부분(첫 번째는 최고 기온, 두 번째는 최저 기온)으로 구성된 응답으로 답하도록 지정
  • 목적
    • 상호 작용하여 정보를 검색하거나 기능을 수행하고자 할 때 API는 사용자가 원하는 것을 시스템에 전달할 수 있게 지원하여 시스템이 이 요청을 이해하고 이행하도록 할 수 있습니다.
  • 장점
    • API는 조직이 보안, 제어, 인증을 유지 관리(누가 무엇에 액세스할 수 있는지 결정)하면서 리소스와 정보를 공유할 수 있는 방법이기도 합니다.
    • 캐싱, 즉 리소스 검색 방법 또는 리소스의 출처에 대해 자세히 알 필요가 없다는 것입니다.

REST(Representational state transfer, RESTful, 레스트풀)

  • REST는 프로토콜이나 표준이 아닌 아키텍처 제약 조건
  • API를 통해 요청이 수행될 때 리소스 상태에 대한 표현을 요청자 또는 엔드포인트에 전송
    • HTTP: JSON(Javascript Object Notation), HTML, XLT, Python, PHP 또는 일반 텍스트를 통해 몇 가지 형식으로 전송
  • 헤더와 매개 변수는 요청의 메타데이터, 권한 부여, URI(Uniform Resource Identifier), 캐싱, 쿠키 등에 대한 중요한 식별자 정보를 포함
    • RESTful API HTTP 요청의 HTTP 메서드도 중요

RESTful 조건

  • 클라이언트, 서버 및 리소스로 구성, 요청이 HTTP를 통해 관리되는 클라이언트-서버 아키텍처
  • 스테이트리스(stateless) 클라이언트-서버 커뮤니케이션: GET 요청 간에 클라이언트 정보가 저장되지 않으며, 각 요청이 분리되어 있고 서로 연결되어 있지 않음
  • 클라이언트-서버 상호 작용을 간소화하는 캐시 가능 데이터
  • 요청된 리소스가 식별 가능하며 클라이언트에 전송된 표현과 분리되어야 합니다.
  • 수신한 표현을 통해 클라이언트가 리소스를 조작할 수 있어야 합니다
  • 클라이언트에 반환되는 자기 기술적(self-descriptive) 메시지에 클라이언트가 정보를 어떻게 처리해야 할지 설명하는 정보가 충분히 포함되어야 합니다.
  • 하이퍼텍스트/하이퍼미디어를 사용할 수 있어야 합니다. 즉, 클라이언트가 리소스에 액세스한 후 하이퍼링크를 사용해 현재 수행 가능한 기타 모든 작업을 찾을 수 있어야 합니다.
  • 요청된 정보를 검색하는 데 관련된 서버(보안, 로드 밸런싱 등을 담당)의 각 유형을 클라이언트가 볼 수 없는 계층 구조로 체계화하는 계층화된 시스템.
  • 코드 온디맨드(선택 사항): 요청을 받으면 서버에서 클라이언트로 실행 가능한 코드를 전송하여 클라이언트 기능을 확장할 수 있는 기능.

참고

https://www.redhat.com/ko/topics/api/what-is-a-rest-api

설계 규칙

  • 1. URI는 동사보다는 명사를, 대문자보다는 소문자를 사용하여야 한다.
  • 2. 마지막에 슬래시 (/)를 포함하지 않는다.
  • 3. 언더바 대신 하이폰을 사용한다.
  • 4. 파일확장자는 URI에 포함하지 않는다.
  • 5. 행위를 포함하지 않는다.

참고

https://gmlwjd9405.github.io/2018/09/21/rest-and-restful.html

RESTfull API 적용

아래와 같은 Consumer 엔티티 정보를 생성한다.

@Getter
@Entity
@Table(name = "CONSUMERS")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Consumer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "CONSUMERS_ID")
    private Long id; // pk값

    @Column(name = "NICKNAME", unique = true)
    private String nickname; // 닉네임

    @Column(name = "PHONE_NUMBER", unique = true)
    private String phoneNumber; // 휴대전화 번호

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "TICKET")
    private Ticket ticket; // 예매 티켓

    @CreationTimestamp
    @Column(name = "CREATE_AT")
    private LocalDateTime createAt; // 생성 시간

    @UpdateTimestamp
    @Column(name = "UPDATE_AT")
    private LocalDateTime updateAt; // 수정 시간
}

ConsumerRepository를 생성한다.

@Transactional(readOnly = true)
public interface ConsumerRepository extends JpaRepository<Consumer, Long> {
    boolean existsByNicknameOrPhoneNumber(String nickname, String phoneNumber);
}

다음으로 Link 를 생성해야 한다.

Link는 문자열로 하드 코딩하지 않고 WebMvcLinkBuilder 로 생성한다.

Link link = WebMvcLinkBuilder
                .linkTo(methodOn(ConsumerController.class).consumerFind(consumerId))
                .withSelfRel();

Controller 의 단건 조회 메서드

@GetMapping("/consumers/{id}")
    public ResponseEntity<RestFullConsumerResponseDto> consumerFind(@PathVariable("id") final Long consumerId) {
        ConsumerResponseDto consumerResponseDto = ConsumerResponseDto.of(consumerService.findConsumer(consumerId));
        Link link = WebMvcLinkBuilder
                .linkTo(methodOn(ConsumerController.class).consumerFind(consumerId))
                .withSelfRel();

        RestFullConsumerResponseDto dto = RestFullConsumerResponseDto.builder()
                .consumer(consumerResponseDto)
                .link(link)
                .build();
        return ResponseEntity.status(HttpStatus.OK).body(dto);

클라이언트의 응답 메시지를 살펴보면 RESTfull 하게 응답이 나오는 것을 확인해볼 수 있다.

{
    "consumer": {
        "nickname": "기영이",
        "id": 1,
        "phone": "01012341230",
        "create_at": "2023-01-19T21:14:53.971178"
    },
    "link": {
        "rel": "self",
        "href": "<http://localhost:8080/consumers/1>"
    }
}

.