스프링 부트 핵심 가이드 5장 [API를 작성하는 다양한 방법]
5장 API를 작성하는 다양한 방법
5.1 프로젝트 설정
- 4장과 동일
5.2 GET API 만들기
- GET API는 웹 애플리케이션 서버에서 값을 가져올 때 사용하는 API이다.
- 컨트롤러 클래스에 @RestController, @RequestMapping 설정
@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {
}
5.2.1 @RequestMapping으로 구현하기
- @RequestMapping 어노테이션을 별다른 설정없이 선언하면 HTTP의 모든 요청을 받는다.
- GET 형식의 요청을 받기 위한 method 요소 값을 RequestMethod.GET 로 설정해 준다.
package com.springboot.api.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String getHello() {
return "Hello World!";
}
}
- 스프링 4.3 이후 버전은 @RequestMapping을 사용하지 않는다.
// 각 HTTP 메서드에 맞는 어노테이션 사용
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
5.2.2 매개변수가 없는 GET 메서드 구현
- 별도의 매개변수 없이 GET API를 구현하는 경우 아래와 같이 적용할 수 있다.
// http://localhost:8080//api/v1/get-api/name
@GetMapping("/name")
public String getName() {
return "Flature";
}
}
5.2.3 @PathVariable을 활용한 GET 메서드 구현
- 웹 통신의 기본 목적은 데이터를 주고 받는 것이기에 대부분 매개변수를 받는 메서드를 작성한다.
- 매개변수를 받을 때 자주 쓰이는 방법 중 하나인 URL에 값을 담아 전달되는 요청을 처리하는 방법.
// http://localhost:8080/api/v1/get-api/variable1/{String 값}
@GetMapping("/variable1/{variable}")
public String getVariable(@PathVariable String variable) {
return "variable";
}
@PathVariable에 변수명을 매핑하는 방법
// http://localhost:8080/api/v1/get-api/variable2/{String 값}
@GetMapping("/variable2/{variable}")
public String getVariable2(@PathVariable ("variable") String var) {
return var;
}
5.2.4 @RequestParam을 활용한 GET 메서드 구현
- 쿼리 형식으로 값을 전달할 때 사용한다.
- URL 에서 ?를 기준으로 우측에 {키}={값} 형태로 구성된 요청을 전송하는 방법.
// http://localhost:8080/api/v1/get-api/request1?name=value1&email=value2&organization=value3
@GetMapping(value = "/request1")
public String getRequestParam1(
@RequestParam String name,
@RequestParam String email,
@RequestParam String organization) {
return name + " " + email + " " + organization;
}
- 1번줄의 주석을 보면 "?" 오른쪽에 쿼리스트링(query string)이 명시돼 있다.
- 쿼리스트링에는 키(변수의 이름)가 모두 적혀 있기 때문에 이 값을 기준으로 메서드의 매개변수의 이름을 정하면, 자동으로 값을 가져올 수 있다.
- 만약 쿼리스트링에 어떤 값이 들어올지 모른다면 아래과 같이 Map 객체를 활용.
// http://localhost:8080/api/v1/get-api/request2?key1=value1&key2=value2
@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();
}
- 위 형태로 코드를 작성하면 값에 상관없이 요청을 받을 수 있다.
- 매개변수의 항목이 일정하지 않을 경우 Map 객체로 받는 것이 효율적이다.
5.2.5 DTO 객체를 활용한 GET 메서드 구현
5.2.5.1 DTO
- Data Transfer Object의 약자로, 다른 레이어 간의 데이터 교환에 활용된다.
- 간략하게 설명 하자면 각 클래스 및 인터페이스를 호출하면서 전달하는 매개변수로 사용되는 데이터 객체이다.
- DTO에는 private 필드, Getter, Setter만 존재한다.
- DTO와 VO의 역할은 서로 엄밀하게 구분하지 않고 사용할 때가 많다.
public class MemberDto {
private String name;
private String email;
private String mobile;
public String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
.
.
.
@Override
public String toString() {
return "MemberDto { " +
"name'" + name + '\'' +
", email='" + email + '\'' +
", mobile'" + mobile + '\'' +
"}";
}
}
5.3 POST API 만들기
- POST API는 웹 애플리케이션을 통해 데이터베이스에 리소스를 저장할 때 사용되는 API 이다.
- POST API에서는 저장하고자 하는 리소스나 값을 HTTP 바디(body)에 담아 서버에 전달한다.
- URI 가 GET API에 비해 간단하다.
5.3.1 @RequestMapping으로 구현하기
@RequestMapping(value = "/domain", method = RequestMethod.POST)
public String postExample() {
return "Hello Post API";
}
}
5.3.2 @RequestBody를 활용한 POST 메서드 구현
일반적으로 POST 형식의 요청은 클라이언트가 서버에 리소스를 저장하는 데 사용한다. 그러므로 클라이언트 의 요청 트래픽에 값이 포함되어 있다. 즉, POST 요청에서는 리소스를 담기 위해 HTTP Body에 값을 넣어 전송한다.
- Body 영역에 작성되는 값은 일정한 형태를 취기에 일반적으로 JSON(JavaScript Object Notation) 형식으로 전송된다.
// http://localhost:8080/api/v1/post-api/member
@PostMapping("member")
public String postMember(
@RequestBody Map<String, String> postData
) {
StringBuilder sb = new StringBuilder();
postData.entrySet().forEach(
map -> {
sb.append(map.getKey() + " : " + map.getValue() + "\n");
}
);
return sb.toString();
}
- @PostMapping을 사용시 method 요소를 정의하지 않아도 된다.
- @RequestBody는 HTTP의 Body 내용을 해당 어노테이션이 지정된 객체에 매핑하는 역할.
5.4 PUT API 만들기
PUT API는 웹 애플리케이션 서버를 통해 데이터베이스 같은 저장소에 존재하는 리소스 값을 업데이트하는 데 사용한다. POST API와 비교하면 요청을 받아 실제 데이터베이스에 반영하는 과정(서비스 로직)에서 차이가 있지만 컨트롤러 클래스를 구현하는 방법은 POST API와 거의 동일하다. 리소스를 서버에 전달하기 위해 HTTP Body를 활용해야 하기 때문이다.
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();
}
- 만약 서버에 들어오는 요청에 담겨 있는 값이 정해져 있는 경우 DTO 객체를 활용해서 구현해야한다.
- String으로 json을 반환하는 방법도 있지만, DTO 자체를 반환하는 경우도 있다.
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;
}
5.4.2 ResponseEntity를 활용한 PUT 메서드 구현
HttpEntity는 다음과 같이 헤더(Header)와 Body로 구성된 HTTP 요청과 응답을 구성하는 역할을 수행한다.
- RequestEntity와 ResponseEntity는 HttpEntity를 상속받아 구현한 클래스이다.
- ResponseEntity는 서버에 들어온 요청에 대해 응답 데이터를 구성해서 전달
// 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);
}
- ResponseEntity 는 HttpEntity로부터 HttpHeaders와 Body를 가지고 자체적으로 HttpStatus를 구현.
- 이 클래스를 활용하면 응답 코드 변경은 물론 Header와 Body를 더욱 쉽게 구성할 수 있다.
- 예제의 3번 줄에서는 메서드의 리턴 타입을 Response Entity로 설정하고 4~6번줄에서 리턴 값을 만든다.
5.5 DELETE API 만들기
- DELETE API는 웹 애플리케이션 서버를 거쳐 데이터베이스 등의 저장소에 있는 리소스를 삭제할 때 사용한다.
- 데이터베이스나 캐시에 있는 리소스를 조회하고 삭제하는 역할을 수행한다.
5.5.1 @PathVariable과 @RequestParam을 활용한 DELETE 메서드 구현
- @PathVariable을 활용한 DELETE 메서드 구현
// http://localhost:8080/api/v1/delete-api/{String 값}
@DeleteMapping(value = "/{variable}")
public String DeleteVariable(@PathVariable String variable) {
return variable;
}
- @RequestParam을 활용한 DELETE 메서드 구현
// 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
API를 개발하면 명세를 관리해야 한다.
명세란 해당 API가 어떤 로직을 수행하는지 설명하고 이 로직 을 수행하기 위해 어떤 값을 요청하며,
이에 따른 응답값으로는 무엇을 받을 수 있는지를 정리한 자료이다.
API는 개발 과정에서 계속 변경되므로 작성한 명세 문서도 주기적인 업데이트가 필요하다.
또한 명세 작업은 번거롭고 시간 또한 오래 걸리기에 이 같은 문제를 해결하기 위해 등장한 것이 바로 Swagger' 라는 오픈소스 프로젝트이다.
- @ApiOperation : 대상 API의 설명을 작성하기 위한 어노테이션.
- @ApiParam : 매개변수에 대한 설명 및 설정을 위한 어노테이션.
@ApiOperation("Gets all diary data for the selected date")
@GetMapping("/read/diary")
List<Diary> readDiary(@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
@ApiParam(value = "dtae : yyyy-mm-dd", example = "2023-07-30") LocalDate date) {
return diaryService.readDiary(date);
}
5.7 [ 한걸음 더 ] 로깅 라이브러리 – Logback
- 로깅(logging)이란 애플리케이션이 동작하는 동안 시스템의 상태나 동작 정보를 시간순으로 기록하는 것을 의미한다.
- 로깅은 디버깅하거나 개발 이후 발생한 문제를 해결할 때 원인을 분석하는 데 꼭 필요한 요소이다.
- slf4j는 스프링 부트의 spring-boot-starter-web 라이브러리 내부에 내장돼 있어 별도의 의존성을 추가하지 않아도 사용할 수 있다.
- 크게 5개의 로그 레벨(TRACE, DEBUG, INFO, WARN, ERROR)을 설정할 수 있다.
- ERROR : 로직 수행 중에 시스템에 심각한 문제가 발생해서 애플리케이션의 작동이 불가능한 경우를 의미한다.
- WARN : 시스템 에러의 원인이 될 수 있는 경고 레벨을 의미한다.
- INFO : 애플리케이션의 상태 변경과 같은 정보 전달을 위해 사용된다.
- DEBUG : 애플리케이션의 디버깅을 위한 메시지를 표시하는 레벨을 의미한다.
- TRACE : DEBUG 레벨보다 더 상세한 메시지를 표현하기 위한 레벨을 의미한다.
5.7.1 Logback 설정
- resource 폴더 안에 생성 logback-spring.xml 파일 참조하도록 생성해야 한다.
// Logback 설정 logback-spring.xml 파일 예시 코드
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_PATH" value="./logs"/>
<!-- Appenders -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%thread] %logger %msg%n</pattern>
</encoder>
</appender>
<appender name="INFO_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<file>${LOG_PATH}/info.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/info_%d{yyyy-MM-dd}.log.gz</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%thread] %logger %msg%n</pattern>
</encoder>
</appender>
<!-- TRACE < DEBUG < INFO < WARN < ERROR < OFF -->
<!-- Root Logger -->
<root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="INFO_LOG"/>
</root>
</configuration>
5.7.1.1 Appender 영역
- 어펜더 영역은 로그의 형태를 설정하고 어떤 방법으로 출력할지를 설정하는 곳이다.
- 어펜더 자체는 하나의 인터페이스를 의미하며 하위에 여러 구현체가 존재한다.
- Consolekppender: 콘승에 로그를 출력
- FileAppender: 파일에 로그를 저장
- RollingFileAppender: 여러 개의 파일을 순회하면서 로그를 저장
- SNTPAppender: 메일로 로그를 전송DBAppender: 데이터베이스에 로그를 저장
5.7.1.2 패턴(pattern) 정의
encoder 요소를 통해 로그의 표현 형식을 패턴(pattern)으로 정의한다.
- %Logger {length} - 로거의 이름
- %-5level - 로그 레벨, -5는 출력 고정폭의 값
- %msg(%message) - 로그 메세지%d : 로그 기록 시간
- %p : 로깅 레벨
- %F : 로깅이 발생한 애플리케이션 파일명
- %M : 로깅이 발생한 메서드 이름
- %I : 로깅이 발생한 호출지의 정보
- %thread : 현재 스레드명
- %t : 로깅이 발생한 스레드명
- %c : 로깅이 발생한 카테고리
- %C : 로깅이 발생한 클래스명
- %m : 로그 메시지
- %n : 줄바꿈
- %r : 애플리케이션 실행 후 로깅이 발생한 시점까지의 시간
- %L : 로깅이 발생하는 호출 지점의 라인 수
// 로그 형식 예시
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] [%thread] %logger %msg%n</pattern>
5.7.1.3 Root 영역
Root Logger는 로깅 설정에서 가장 핵심적인 요소이다.
Root Logger의 설정은 <root> 태그를 통해 이루어진디.
<root level="INFO">
<appender-ref ref="console" />
<appender-ref ref="INFO_LOG"/>
</root>
5.7.2 Logback 적용하기
- Logback 적용 예시
public class DiaryService {
private static final Logger logger = LoggerFactory.getLogger(WeatherApplication.class);
@Transactional
@Scheduled(cron = "0 0 1 * * *")
public void saveWeatherDate() {
logger.info("오늘도 날씨 데이터 잘 가져옴");
dateWeatherRepository.save(getWeatherFromApi());
}
- 메서드 요청시 logs 폴더 생성, log 기록이 남는다.