본문 바로가기
Spring/스프링 부트 핵심 가이드

[Spring] Ch.5 API를 작성하는 다양한 방법

by ♡˖GYURI˖♡ 2024. 4. 8.
728x90

5.2 GET API 만들기

GET API : 웹 애플리케이션 서버에서 값을 가져올 때 사용하는 API

 

@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {

}

 

컨트롤러에 @RestController와 @RequestMapping을 붙여 내부에 선언되는 메서드에서 사용할 공통 URL을 설정한다.

클래스 수준에서 @RequestMapping을 설정하면 내부에 선언한 메서드의 URL 리소스 앞에 @RequestMapping의 값이 공통 값으로 추가된다.

 

5.2.1 @RequestMapping으로 구현하기

@RequestMapping 어노테이션을 별다른 설정 없이 선언하면 HTTP의 모든 요청을 받는다.

GET 형식의 요청만 받기 위해서는 어노테이션에 별도 설정이 필요하다.

 

@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {
    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String getHello() {
        return "Hello World";
    }
}

 

@RequestMapping 어노테이션의 method 요소의 값을 RequestMethod.GET으로 설정하면 요청 형식을 GET으로만 설정할 수 있다.

 

스프링 4.3 버전 이후로는 @RequestMapping 어노테이션은 더 이상 사용되지 않는다. → 대신 @GetMapping 등을 사용

 

5.2.2 매개변수가 없는 GET 메서드 구현

@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {
    @GetMapping(value = "/name")
    public String getName() {
        return "Flature";
    }
}

 

 

5.2.3 @PathVariable을 활용한 GET 메서드 구현

실무 환경에서는 매개변수를 받지 않는 메서드가 거의 쓰이지 않는다.

웹 통신의 기본 목적은 데이터를 주고받는 것이기 때문에 대부분 매개변수를 받는 메서드를 작성하게 된다.

매개변수를 받을 때 자주 쓰이는 방법 중 하나는 URL 자체에 값을 담아 요청하는 것이다.

 

    @GetMapping(value = "/variable1/{variable}")
    public String getVariable1(@PathVariable String variable) {
        LOGGER.info("@PathVariable을 통해 들어온 값 : {}", variable);
        return variable;
    }

 

URL은 다음과 같다. → http://localhost:8080/api/v1/get-api/variable1/{String 값}

이 메서드는 중괄호로 표시된 위치의 값을 받아 요청한다. (실제 요청 시 중괄호는 들어가지 않으며 값만 존재함)

값을 간단히 전달할 때 주로 사용하는 방법이며, GET 요청에서 많이 사용된다.

 

이 방식으로 코드를 작성할 때는 몇 가지 지켜야 할 규칙이 있다.

  • @GetMapping 어노테이션의 값으로 URL을 입력할 때 중괄호를 사용해 어느 위치에서 값을 받을지 지정해야 함
  • 메서드의 매개변수와 그 값을 연결하기 위해 @PathVariable을 명시하여, @GetMapping 어노테이션과 @PathVariable에 지정된 변수의 이름을 동일하게 맞추어야 함
  • @GetMapping 어노테이션에 지정한 변수의 이름과 메서드 매개변수의 이름을 동일하게 맞추기 어렵다면 @PathVariable 뒤에 괄호를 열어 @GetMapping 어노테이션의 변수명을 지정해야 함
    @GetMapping(value = "/variable2/{variable}")
    public String getVariable2(@PathVariable("variable") String var) {
        return var;
    }

 

 

5.2.4 @RequestParam을 활용한 GET 메서드 구현

URL 경로에 값을 담아 요청을 보내는 방법 외에도 쿼리 형식으로 값을 전달할 수도 있다.

즉, URI에서 '?'를 기준으로 우측에 '{키}={값}' 형태로 구성된 요청을 전송하는 방법이다.

이 같은 형식을 처리하려면 @RequestParam을 활용하면 된다.

 

    @GetMapping(value = "/request1")
    public String getRequestParam1(
        @RequestParam String name,
        @RequestParam String email,
        @RequestParam String organization) {
        return name + " " + email + " " + organization;
    }

 

http://localhost:8080/api/v1/get-api/request2?

name=flature&email=thinkground.flature@gmail.com&organization=thinkground 와 같은 형식이 된다.

키와 @RequestParam 뒤에 적는 이름을 동일하게 설정하기 어렵다면 @PathVariable 예제에서 사용한 방법처럼 value 요소로 매핑한다.

 

만약 쿼리스트링에 어떤 값이 들어올지 모른다면 Map 객체를 활용할 수도 있다.

    @GetMapping(value = "/request2")
    public String getRequestParam2(@RequestParam Map<String, String> param) {
        StringBuilder sb = new StringBuilder();

        param.entrySet().forEach(map -> {
            sb.append(map.getKey() + " : " + map.getValue() + "\n");
        });

        return sb.toString();
    }

 

이런 형태로 코드를 작성하면 값에 상관없이 요청을 받을 수 있다.

e.g. 회원 가입 관련 API의 취미 선택 항목 (값을 기입하지 않을 가능성이 있는 경우)

 

 

5.2.5 DTO 객체를 활용한 GET 메서드 구현

DTO

  • Data Transfer Object
  • 다른 레이어 간의 데이터 교환에 활용
  • 각 클래스 및 인터페이스를 호출하면서 전달하는 매개변수로 사용되는 데이터 객체
  • 별도의 로직을 포함하지 않음
public class MemberDto {

    private String name;
    private String email;
    private String organization;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getOrganization() {
        return organization;
    }

    public void setOrganization(String organization) {
        this.organization = organization;
    }

    @Override
    public String toString() {
        return "MemberDTO{" +
            "name='" + name + '\'' +
            ", email='" + email + '\'' +
            ", organization='" + organization + '\'' +
            '}';
    }

}

 

DTO 클래스에는 전달하고자 하는 필드 객체를 선언하고 getter/setter 메서드를 구현한다. → 롬복 사용

DTO 클래스에 선언된 필드는 컨트롤러의 메서드에서 쿼리 파라미터의 키와 매핑된다.

즉, 쿼리스트링의 키가 정해져 있지만 받아야 할 파라미터가 많은 경우에는 DTO 객체를 활용하여 코드의 가독성을 높일 수 있다.

    @GetMapping(value = "/request3")
    public String getRequestParam3(MemberDto memberDTO) {
        return memberDTO.toString();
    }

 

 

5.3 POST API 만들기

POST API

  • 웹 애플리케이션을 통해 데이터베이스 등의 저장소에 리소스를 저장할 때 사용되는 API
  • 저장하고자 하는 리소스나 값을 HTTP 바디에 담아 서버에 전달
@RestController
@RequestMapping("/api/v1/post-api")
public class PostController {

}

 

 

5.3.1 @RequestMapping으로 구현하기

@RequestMapping(value = "/domain", method = RequestMethod.POST)
public String postExample() {
	return "Hello Post API";
}

 

→ @PostMaiing 사용으로 대체

 

 

5.3.2 @RequestBody를 활용한 POST 메서드 구현

Body 영역에 작성되는 값은 일정한 형태를 취하는데, 일반적으로 JSON(JavaScript Object Notation) 형식으로 전송된다.

 

    @PostMapping(value = "/member")
    public String postMember(@RequestBody Map<String, Object> postData) {
        StringBuilder sb = new StringBuilder();

        postData.entrySet().forEach(map -> {
            sb.append(map.getKey() + " : " + map.getValue() + "\n");
        });

        return sb.toString();
    }

 

@RequestBody는 HTTP의 Body 내용을 해당 어노테이션이 지정된 객체에 매핑하는 역할을 한다.

 

Map 객체는 요청을 통해 어떤 값이 들어오게 될지 특정하기 어려울 때 주로 사용한다.

요청 메시지에 들어갈 값이 정해져 있다면 DTO 객체를 매개변수로 삼아 작성할 수 있다.

 

    @PostMapping(value = "/member2")
    public String postMemberDto(@RequestBody MemberDto memberDTO) {
        return memberDTO.toString();
    }

 

 

 

5.4 PUT API 만들기

PUT API

  • 웹 애플리케이션 서버를 통해 데이터베이스 같은 저장소에 존재하는 리소스 값을 업데이트하는 데 사용
  • 리소스를 서버에 전달하기 위해 HTTP Body를 활용해야 하기 때문에 구현 방법은 POST API와 거의 동일
@RestController
@RequestMapping("/api/v1/put-api")
public class PutController {

}

 

 

5.4.1 @RequestBody를 활용한 PUT 메서드 구현

    // http://localhost:8080/api/v1/put-api/member
    @PutMapping(value = "/member")
    public String postMember(@RequestBody Map<String, Object> putData) {
        StringBuilder sb = new StringBuilder();

        putData.entrySet().forEach(map -> {
            sb.append(map.getKey() + " : " + map.getValue() + "\n");
        });

        return sb.toString();
    }

 

서버에 어떤 값이 들어올지 모르는 경우에는 Map 객체를 활용해값을 받을 수 있다.

대부분의 경우 API를 개발한 쪽에서 작성한 명세를 웹 사이트를 통해 클라이언트나 사용자에게 올바른 사용법을 안내한다.

만약 서버에 들어오는 요청에 담겨 있는 값이 정해여 있는 경우에는 DTO 객체를 활용해 구현한다.

    // http://localhost:8080/api/v1/put-api/member1
    @PutMapping(value = "/member1")
    public String postMemberDto1(@RequestBody MemberDto memberDto) {
        return memberDto.toString();
    }

    // http://localhost:8080/api/v1/put-api/member2
    @PutMapping(value = "/member2")
    public MemberDto postMemberDto2(@RequestBody MemberDto memberDto) {
        return memberDto;
    }

 

리턴 타입이 String일 때, context-type이 text/plain이기에 결과적으로 일반 문자열이 전달된다.

리턴 타입이 DTO일 때, context-type이 application/json이기에 JSON 형식으로 값을 변환하여 전달하게 된다.

 

 

5.4.2 ResponseEntity를 활용한 PUT 메서드 구현

HttpEntity는 헤더와 바디로 구성된 HTTP 요청과 응답을 구성하는 역할을 한다.

RequestEntity와 ResponseEntity는 HttpEntity를 상속받아 구현한 클래스인데, 그 중 ResponseEntity는서버에 들어온 요청에 대해 응답 데이터를 구성해 전달할 수 있게 한다.

 

ResponseEntity는 HttpEntity로부터 HttpHeaders와 Body를 가지고 자체적으로 HttpStatus를 구현한다.

이 클래스를 활용하면 응답 코드 변경은 물론 Header와 Body를 더욱 쉽게 구성할 수 있다.

    // http://localhost:8080/api/v1/put-api/member3
    @PutMapping(value = "/member3")
    public ResponseEntity<MemberDto> postMemberDto3(@RequestBody MemberDto memberDto) {
        return ResponseEntity
            .status(HttpStatus.ACCEPTED)
            .body(memberDto);
    }

 

 

5.5 DELETE API 만들기

DELETE API

  • 웹 애플리케이션 서버를 거쳐 데이터베이스 등의 저장소에 있는 리소스를 삭제할 때 사용
  • GET 메서드와 같이 URI에 값을 넣어요청을 받는 형식으로 구현됨
@RestController
@RequestMapping("/api/v1/delete-api")
public class DeleteController {

}

 

 

5.5.1 @PathVariable과 @RequestParam을 활용한 DELETE 메서드 구현

    // http://localhost:8080/api/v1/delete-api/{String 값}
    @DeleteMapping(value = "/{variable}")
    public String DeleteVariable(@PathVariable String variable) {
        return variable;
    }

 

마찬가지로 @DeleteMapping 어노테이션에 정의한 value의 이름과 메서드의 매개변수 이름을 동일하게 ㅓㄹ정해야 삭제할 값이 주입된다.

 

또는 @RequestParam 어노테이션을 통해 쿼리스트링 값도 받을 수있다.

    // http://localhost:8080/api/v1/delete-api/request1?email=value
    @DeleteMapping(value = "/request1")
    public String getRequestParam1(@RequestParam String email) {
        return "e-mail : " + email;
    }

 

 

5.6 REST API 명세를 문서화하는 방법 - Swagger

Swagger 관련 내용은 따로 블로깅할 예정

 

 

5.7 로깅 라이브러리 - Logback

로깅

  • 애플리케이션이 동작하는 동안 시스템의 상태나 동작 정보를 시간순으로 기록하는 것
  • 비기능 요구사항이지만 문제 발생 시 원인 분석하는데 꼭 필요한 요소 
  • slf4j 활용(spring-boot-starter-web 라이브러리 내장)

 

Logback의 특징

  • 크게 5개의 로그 레벨을 설정할 수 있음
    • ERROR : 로직 수행 중에 시스템에 심각한 문제가 발생해서 애플리케이션의 작동이 불가능한 경우
    • WARN : 시스템 에러의 원인이 될 수 있는 경고 레벨
    • INFO : 애플리케이션의 상태 변경과 같은 정보 전달
    • DEBUG : 애플리케이션의 디버깅을 위한 메시지 표시
    • TRACE : DEBUG 레벨보다 더 상세한 메시지 표현
  • 실제 운영 환경과 개발 환경에서 각각 다른 출력 레벨을 설정해서 로그를 확인할 수 있음
  • Logback의 설정 파일을 일정 시간마다 스캔해서 애플리케이션을 재가동하지 않아도 설정을 변경할 수 있음 
  • 별도의 프로그램 지원 없이도 자체적으로 로그 파일을 압축할 수 있음
  • 저장된 로그 파일에 대한 보관 기간 등을 설정해서 관리할 수 있음

 

5.7.1 Logback 설정

Appender 영역

로그의 형태를 설정하고 어떤 방법으로 출력할지를 설정하는 영역

  • ConsoleAppender : 콘솔에 로그 출력
  • FileAppender : 파일에 로그 저장
  • RollingFileAppender : 여러 개의 파일을 순회하면서 로그 저장
  • SMTPAppender : 메일로 로그 전송
  • DBAppender : DB에 로그 저장

 

encoder 요소를통해 로그의 표현 형식을 패턴으로 정의할 수 있다.

패턴 의미
%Logger{length} 로거의 이름
%-level 로그 레벨. -5는 출력 고정폭의 값
%msg(%message) 로그 메시지
%d 로그 기록 시간
%p 로깅 레벨
%F 로깅이 발생한 애플리케이션 파일명
%M 로깅이 발생한 메서드 이름
%I 로깅이 발생한 호출지의 정보
%thread 현재 스레드명
%t 로깅이 발생한 스레드명
%c 로깅이 발생한 카테고리
%C 로깅이 발생한 클래스명
%m 로그 메시지
%n 줄바꿈
%r 애플리케이션 실행후 로깅이 발생한 시점까지의 시간 
%L 로깅이 발생한 호출 지점의 라인 수

 

 

Root 영역

설정 파일에 정의된 Appender를 활용하려면 Root 영역에서 Appender를 참조해서 로깅 레벨을 설정한다.

만약 특정 패키지에 대해 다른로깅레벨을 설정하고 싶다면 root 대신 logger를 사용해 아래와 같이 지정할 수 있다.

    <root level="INFO">
        <appender-ref ref="console"/>
        <appender-ref ref="INFO_LOG"/>
    </root>

 

또는

    <logger name="com.springboot.api.controller" level="DEBUG" additivity="false">
        <appender-ref ref="console"/>
        <appender-ref ref="INFO_LOG"/>
    </logger>

 

logger 요소의 name 속성에는 패키지 단위로 로깅이 적용될 범위를 지정하고 level 속성으로 로그 레벨을 지정한다.

additivity 속성은 앞에서 지정한 패키지 범위에 하위 패키지를 포함할지 여부를 결정한다.

기본값은 true이며, 이 경우 하위 패키지를 모두 포함한다.