-
5. API를 작성하는 다양한 방법
-
5.1 프로젝트 설정 - Skip
-
5.2 GET API 만들기
-
5.2.1 @RequestMapping으로 구현하기
-
5.2.2 매개변수가 없는 GET 메서드 구현
-
5.2.3 @PathVariable을 활용한 GET 메서드 구현
-
5.2.4 @RequestParam을 활용한 GET 메서드 구현
-
5.2.5 DTO 객체를 활용한 GET 메서드 구현
-
5.3 POST API 만들기
-
5.3.1 @RequestMapping으로 구현하기
-
5.3.2 @RequestBody를 활용한 POST 메서드 구현
-
5.4 PUT API 만들기
-
5.4.1 @RequestBody를 활용한 PUT 메서드 구현
-
5.4.2 ResponseEntity를 활용한 PUT 메서드 구현
-
5.5 DELETE API 만들기
-
5.5.1 @PathVariable과 @RequestParam을 활용한 DELETE 메서드 구현
-
5.6 [ 한걸음 더 ] REST API 명세를 문서화하는 방법 - Swagger
-
5.7 [ 한걸음 더 ] 로깅 라이브러리 - Logback
-
5.7.1 Logback 설정
-
5.7.1 Logback 적용하기
-
5.8 정리

5. API를 작성하는 다양한 방법
본격적으로 애플리케이션 개발에 필요한 내용들을 소개하고 있다.
HTTP 메서드에 해당하는 API를 개발하고 그 과정에서 필요한 내용이 나와있다.
5.1 프로젝트 설정 - Skip
groupId는 'com.springboot'
name과 artifactId는 'api'
5.2 GET API 만들기
HTTP 메서드 중 GET 메서드는 애플리케이션 서버로부터 값을 가져올 때 사용하는 API이다.
기본적으로 크롬같은 주소창에 주소를 입력하는 행위가 GET 메서드를 사용하는 행위이다.
Springboot에서 GET 메서드를 구현하는데는 여러 가지 방법이 있다. 아래 챕터에서는 각 챕터의 방식을 채택해서 GET API를 구현할 것이다.
아래의 코드는 기본 뼈대가 될 GetController이다.
RequestMapping을 통해 class 내부의 메서드들의 prefix url을 /api/v1/get-api로 선언하였다.
@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {
}
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";
}
}
Spring 4.3 버전 이후부터는 아래에서 다룰 새로운 어노테이션을 사용하기 때문에, 위와 같은 코드는 더 이상 사용하지 않습니다.
5.2.2 매개변수가 없는 GET 메서드 구현
아래에서는 GetMapping 어노테이션을 사용해서 어떠한 매개변수도 받지 않는 GET 메서드를 만들었습니다.
@GetMapping(value = "/name")
public String getName() {
return "Flature";
}
5.2.3 @PathVariable을 활용한 GET 메서드 구현
실무 환경에서는 매개변수를 받지 않는 매개변수가 거의 쓰이지 않습니다.
웹 통신의 기본 목적은 데이터를 주고받는 것이기 때문에, 대부분 매개변수를 받는 메서드를 사용하게 됩니다.
서버로 매개변수를 전달하기 위해서 많이 사용하는 것은 @PathVariable과 @RequestParam이 있습니다.
아래는 @PathVaraiable을 사용해서 사용자로부터 간단한 매개변수를 받아 반환하는 GET API입니다.
@PathVariable을 사용할 때 지켜야 하는 몇 가지 규칙이 있습니다.
URL에서 매개변수를 받기 위해서는 받고자 하는 위치에 {} 중괄호를 쳐서 매개변수로 받겠다는 표현을 해줘야 합니다.
아래에서는 /variable1/{variable1}을 통해서 받고자 하는 매개변수 위치에 중괄호 표시를 해줬습니다.
또한, @PathVariable 뒤에 특정 매개변수의 이름을 정의하고 다른 이름으로 변수에 매개변수의 값을 할당할 수 있습니다.
@Controller
@RequestMapping("/api/v1/get-api")
public class TestController {
@GetMapping("/variable1/{variable1}")
public String getVariable1(@PathVariable String variable1){
return variable1;
}
@GetMapping("/variable2/{variable2}")
public String getVariable1(@PathVariable("variable2") String temp){
return temp;
}
}
5.2.4 @RequestParam을 활용한 GET 메서드 구현
GET 요청을 보내는 방법에서는 크게 2가지로 분류가 되는데 /로 구분되는 Path Variable과 name/?name=123과 같이 같이 ? 뒤에 키-밸류 값 형식으로 값을 전달할 수도 있습니다. 이와 같은 표현식을 쿼리라고 합니다. 이런 방법을 서버에서 요청을 처리하기 위해서는 @RequestParam 어노테이션을 명시해 쿼리 값과 매핑하면 됩니다.
또한, 어떤 쿼리 값이 들어올지 모른다면, Map 자료구조를 사용해서 loop 문을 통해서 내부의 값을 조회할 수 있습니다.
@Controller
@RequestMapping("/api/v1/get-api")
public class TestController {
@GetMapping("/request1")
public String getRequestParam1(
@RequestParam String name,
@RequestParam String email,
@RequestParam String organization) {
return name + " " + email + " " + organization;
}
@GetMapping("/request2")
public String getRequestParam1(
@RequestParam Map<String,String> params) {
StringBuilder sb = new StringBuilder();
params.entrySet().forEach(entry -> {
sb.append(entry.getKey()).append(" : ").append(entry.getValue()).append("\n");
});
return sb.toString();
}
}
5.2.5 DTO 객체를 활용한 GET 메서드 구현
DTO란 Data Trasnfer Object의 약자로, 다른 레이어 간의 데이터 교환에 활용되는 Object입니다.
간략하게 설명하자면, 각 클래스 및 인터페이스를 호출하면서 전달하는 매개변수의 역할을 수행합니다.
DTO는 데이터를 교환하는 용도로만 사용하기 때문에, DTO에는 별도의 로직이 포함되지 않습니다.
DTO와 VO(Value Object)
둘의 역할을 서로 엄밀히 구분하지 않고 사용할 때가 많다. 이렇게 해도 대부분의 상황에서는 큰 문제가 발생하지 않기 때문이다.
하지만, 엄밀히 살펴본다면 역할과 사용법에서 차이가 있습니다.
VO는 데이터 그 자체로 의미가 있는 객체를 의미합니다. 가장 특징적인 부분은 Read-Only로 설계한다는 점이다. VO는 값을 변경할 수 없게 만들어서 데이터의 신뢰성을 유지해야 합니다.
DTO는 외부에서 전달되는 데이터 컨테이너입니다. DTO는 다른 레이어로 전송된다고 위에 서술했었는데, 여기서 레이어는 서비스 레이어, 데이터 레이어와 같은 애플리케이션 내부 레이어를 의미하기도 하지만, 실무에서는 다른 서비스, 다른 노드와 같은 시스템을 의미할 수도 있습니다.
아래에서는 어떠한 어노테이션도 붙이지 않고 Company라는 DTO를 매개변수로 받는 GET API입니다.
실제로 쿼리로 들어올 때는, /ticker=MMM&name=3M과 같은 식으로 들어옵니다.
받는 값은 변화가 없지만, 실제 코드의 양은 줄어든 다는 사실을 알 수 있습니다.
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Company {
private String ticker;
private String name;
}
@Controller
@RequestMapping("/api/v1/get-api")
public class TestController {
@GetMapping("/company")
public String getCompany(
Company company) {
return company.toString();
}
}
5.3 POST API 만들기
POST API는 웹 애플리케이션을 통해 데이터베이스 등의 저장소에 리소스를 저장할 때 사용하는 API입니다. 이전의 GET API에서는 URL의 경로나 파라미터에 변수를 넣어서 요청을 보냈지만, POST에서는 body에 값을 담아서 서버로 전송합니다 그래서 URI가 GET API에 비해 간단합니다.
5.3.1 @RequestMapping으로 구현하기
일반적으로 POST요청은 사용자가 서버에 리소스를 저장하는 데 사용합니다. 그러므로 클라이언트의 요청 트래픽에 값이 포함되어 있습니다. 즉, POST 요청에서는 리소스를 담기 위해 HTTP Body에 값을 넣어 전송합니다.
@RestController
@RequestMapping("/api/v1/post-api")
public class PostController {
@RequestMapping(value = "/hello", method = RequestMethod.POST)
public String getHello(){
return "Hello World";
}
}
5.3.2 @RequestBody를 활용한 POST 메서드 구현
일반적으로 바디의 형식은 Json 형식으로 되어있습니다.
{
"ticker":"MMM"
}
아래에서는 PostMapping이라는 어노테이션을 사용해서 해당 경로로 오는 API 요청에 대해서 postData라는 변수에 대입하고 메서드 내부에서는 forEach문을 통해서 값을 파싱 한다. 또는, DTO 객체를 통해서 body값을 받을 수도 있다.
@RestController
@RequestMapping("/api/v1/post-api")
public class TestController {
@PostMapping("/member")
public String postMember(@RequestBody Map<String, Object> postData){
StringBuilder sb = new StringBuilder();
postData.entrySet().forEach(
entry -> sb.append(entry.getKey()).append(" : ").append(entry.getValue()).append("\n")
);
return sb.toString();
}
@PostMapping("/member2")
public String postMember2(@RequestBody Company company){
return company.toString();
}
}
5.4 PUT API 만들기
PUT API는 웹 애플리케이션 서버를 통해 데이터베이스 같은 저장소에 존재하는 리소스 값을 업데이트하는 데 사용합니다.
수정하기 위해서는 body에 값을 넣어 전달하고 이를 통해 업데이트를 진행합니다.
하지만 실무에서는 PUT Method를 사용하지 않는데, 이유는 PUT 메서드는 해당 테이블에 존재하는 모든 값을 수정하기 때문입니다.
WHERE절을 실수로 넣지 않는 경우, 모든 데이터들이 변경될 우려가 있습니다. 따라서 수정을 하기 위해서는 PATCH Method를 사용하여 진행합니다.
5.4.1 @RequestBody를 활용한 PUT 메서드 구현
@RequestBody를 사용하는 POST 메서드와 구조적으로 동일합니다.
@RestController
@RequestMapping("/api/v1/post-api")
public class TestController {
@PutMapping("/member")
public String postMember(@RequestBody Map<String, Object> postData){
StringBuilder sb = new StringBuilder();
postData.entrySet().forEach(
entry -> sb.append(entry.getKey()).append(" : ").append(entry.getValue()).append("\n")
);
return sb.toString();
}
@PutMapping("/member2")
public String postMember2(@RequestBody Company company){
return company.toString();
}
}
5.4.2 ResponseEntity를 활용한 PUT 메서드 구현
Spring Framework에는 HttpEntity라는 클래스가 있습니다. HttpEntity는 다음과 같이 Header와 Body로 구성된 HTTP 요청과 응답을 구성하는 역할을 수행합니다. ResponseEntity와 RequestEntity는 HttpEntity를 상속받아 구현한 구현체입니다. 그중 Response Entity는 서버에 들어온 요청에 대해 응답 데이터를 구성해서 전달할 수 있습니다.
이 클래스를 활용하면, 응답 코드 변경과 Header와 Body를 더욱 쉽게 구성할 수 있습니다. PUT 메서드뿐만 아니라 모든 메서드에서도 사용할 수 있는 클래스입니다.
@RestController
@RequestMapping("/api/v1/post-api")
public class TestController {
@PutMapping("/member2")
public ResponseEntity<Company> postMember2(@RequestBody Company company){
return ResponseEntity.status(HttpStatus.OK).body(company);
}
}
5.5 DELETE API 만들기
DELETE API는 클라이언트가 서버에 저장한 데이터를 삭제하고자 할 때 사용하는 API Method입니다.
서버에서는 클라이언트로부터 리소스를 식별할 수 있는 값을 받아 데이터베이스나 캐시에 있는 리소스를 조회하고 삭제하는 역할을 수행합니다. 이때 컨트롤러에서는 간단한 값을 받기 때문에 GET메서드와 같이 URI에 값을 넣어 요청을 받는 형식으로 구현됩니다.
5.5.1 @PathVariable과 @RequestParam을 활용한 DELETE 메서드 구현
이전에 GET 메서드를 살펴볼 때와 매우 유사한 구조로 API 요청을 받는다는 것을 알 수 있습니다.
@RestController
@RequestMapping("/api/v1/delete-api")
public class TestController {
@DeleteMapping("/{category}")
public String DeleteVariable(@PathVariable String variable,
@RequestParam Map<String, String> queryParameters) {
return "Delete request received with variable: " + variable +
" and query parameters: " + queryParameters;
}
}
5.6 [ 한걸음 더 ] REST API 명세를 문서화하는 방법 - Swagger
API를 개발하면 명세를 관리해야 합니다.
명세란 해당 API가 어떤 로직을 수행하는지 설명하고 이 로직을 수행하기 위해 어떤 값을 요청하며, 이에 따른 응답값으로는 무엇을 받을 수 있는지를 정리한 자료입니다.
API는 개발 과정에서 계속 변경되므로 작성한 명세문서도 주기적인 업데이트가 필요합니다. 또한, 명세 작성 작업은 번거롭고 시간이 오래 걸립니다. 이러한 문제점을 해결하기 위해서 나온 해결책은 Swagger라는 오픈소스 프로젝트입니다.
Springboot 2.x.x 버전에서는 springdoc, springfox 모두 사용 가능하지만, Springboot 3.x.x 버전대에서는 Springdoc만 사용해야 합니다.
자신이 프로젝트 빌드 툴을 Gradle을 사용한다면, build.gradle에. Maven을 사용한다면 pom.xml에 dependency를 추가합니다.
///build.gradle
// swagger
implementation group: 'io.springfox', name: 'springfox-boot-starter', version: '3.0.0'
// SwaggerConfig
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket swaggerApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.zero.stock.web"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Stock API")
.description("API for stock information")
.version("1.0")
.build();
}
}
이렇게 작성하고 애플리케이션을 재실행한 이후, localhost:8080/swagger-ui/index.html 경로로 접속하면 아래와 같은 API 목록을 볼 수 있고, 어떤 매개변수들이 들어가는지, 어떤 body값을 서버로 전송하는지, 이에 따른 response type과 형태가 어떤지를 확인할 수 있습니다.

5.7 [ 한걸음 더 ] 로깅 라이브러리 - Logback
로깅(logging)이란 애플리케이션이 동작하는 동안 시스템의 상태나 동작 정보를 시간순으로 기록하는 것을 의미합니다. 로깅은 개발 영역 중 '비기능 요구사항'에 속합니다. 즉, 사용자나 고객에게 필요한 기능은 아니라는 의미입니다. 하지만 로깅은 디버깅하거나 개발 이후 발생한 문제를 추적하고 해결할 때 원인을 분석하는데 꼭 필요한 요소입니다.
자바 진영에서 로깅을 위해서 가장 많이 사용하는 로깅 라이브러리는 Logback 라이브러리입니다. Logback은 log4j 이후에 만들어진 프레임워크로, Slf4j를 기반으로 구현됐으며, 이전에 있던 라이브러리보다 월등한 성능을 자랑합니다. 또한, spring-boot-starter-web 디펜던시 안에 내장되어 있어 추가적인 설치는 불필요하다는 장점이 있습니다.
5.7.1 Logback 설정
프로젝트 디렉터리 중 resources 패키지 내부에 logback-spring.xml 파일을 생성한 후 아래와 같이 작성합니다.
<configuration>
<property name="LOG_DIR" value="./logs" />
<property name="LOG_FILE_NAME" value="mylog" />
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern> %d{HH:mm:ss.SSS} %highlight(%-5level) %magenta(%-4relative) --- [ %thread{10} ] %cyan(%logger{29}) - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/${LOG_FILE_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/${LOG_FILE_NAME}-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [ %thread ] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework" level="INFO"/>
<logger name="org.hibernate" level="INFO"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
전체 코드가 모두 configuration이라는 필드 내부에 정의되어 있으며, property라는 필드를 통해서 해당 파일 전역에서 사용할 변수를 미리 선언할 수 있습니다.
다음으로 appender를 통해서 로그의 형태를 설정하고 어떤 방법으로 출력할지를 설정하는 곳입니다. Appender 자체는 하나의 인터페이스를 의미하며, 하위에 여러 구현체가 존재합니다.

또한 여러 구현체를 통해서 콘솔에 로그를 출력하거나, 파일에 로그를 저장하는 등 여러 가지의 역할을 수행할 수 있습니다.
RollingFIleAppender를 통해서는 여러 파일을 순회하면서 로그를 저장하는 기능을 수행할 수 있습니다.
Appender에 FILE이라는 이름을 부여하게 되면 해당 Appender에서는 로그를 파일에 저장하는 기능을 수행하는데, SizeAndTImeBasedRollingPolicy를 적용하여 최대 며칠간 최대 용량을 지정하여 해당 용량을 넘어가면 다음 로그파일에 저장할 수 있습니다. 이를 통해 로그파일을 생성하고 나중에 배울 ELK Stack을 통해서 로그의 내용을 시각화할 수 있습니다.
5.7.1 Logback 적용하기
위의 설정 파일을 기반으로 logback을 적용하면 logs라는 폴더 아래에 mylog-yyyy-mm-hh-ss.i.log로 저장되고 30개가 넘어가는 경우 gz 형식으로 압축하여 로그를 저장합니다.


5.8 정리
5장에서는 다양한 API Method를 통해서 어떤 목적을 가지고 서버에 요청을 보내는지에 따라서 GET, POST, PUT, DELETE와 같은 CRUD API를 알아봤습니다. 이미 알고 있던 내용들도 존재하지만, 보다 깊게 살펴볼 수 있었습니다.
'책' 카테고리의 다른 글
[Book] 스프링 부트 핵심 가이드 Chap.8 (0) | 2024.07.14 |
---|---|
[Book] 스프링 부트 핵심 가이드 Chap.6 (0) | 2024.07.02 |
[Book] 스프링 부트 핵심 가이드 Chap.4 (0) | 2024.06.25 |
[Book] 스프링 부트 핵심 가이드 Chap.3 (0) | 2024.06.23 |
[Book] 스프링 부트 핵심 가이드 Chap.2 (0) | 2024.06.23 |

5. API를 작성하는 다양한 방법
본격적으로 애플리케이션 개발에 필요한 내용들을 소개하고 있다.
HTTP 메서드에 해당하는 API를 개발하고 그 과정에서 필요한 내용이 나와있다.
5.1 프로젝트 설정 - Skip
groupId는 'com.springboot'
name과 artifactId는 'api'
5.2 GET API 만들기
HTTP 메서드 중 GET 메서드는 애플리케이션 서버로부터 값을 가져올 때 사용하는 API이다.
기본적으로 크롬같은 주소창에 주소를 입력하는 행위가 GET 메서드를 사용하는 행위이다.
Springboot에서 GET 메서드를 구현하는데는 여러 가지 방법이 있다. 아래 챕터에서는 각 챕터의 방식을 채택해서 GET API를 구현할 것이다.
아래의 코드는 기본 뼈대가 될 GetController이다.
RequestMapping을 통해 class 내부의 메서드들의 prefix url을 /api/v1/get-api로 선언하였다.
@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {
}
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";
}
}
Spring 4.3 버전 이후부터는 아래에서 다룰 새로운 어노테이션을 사용하기 때문에, 위와 같은 코드는 더 이상 사용하지 않습니다.
5.2.2 매개변수가 없는 GET 메서드 구현
아래에서는 GetMapping 어노테이션을 사용해서 어떠한 매개변수도 받지 않는 GET 메서드를 만들었습니다.
@GetMapping(value = "/name")
public String getName() {
return "Flature";
}
5.2.3 @PathVariable을 활용한 GET 메서드 구현
실무 환경에서는 매개변수를 받지 않는 매개변수가 거의 쓰이지 않습니다.
웹 통신의 기본 목적은 데이터를 주고받는 것이기 때문에, 대부분 매개변수를 받는 메서드를 사용하게 됩니다.
서버로 매개변수를 전달하기 위해서 많이 사용하는 것은 @PathVariable과 @RequestParam이 있습니다.
아래는 @PathVaraiable을 사용해서 사용자로부터 간단한 매개변수를 받아 반환하는 GET API입니다.
@PathVariable을 사용할 때 지켜야 하는 몇 가지 규칙이 있습니다.
URL에서 매개변수를 받기 위해서는 받고자 하는 위치에 {} 중괄호를 쳐서 매개변수로 받겠다는 표현을 해줘야 합니다.
아래에서는 /variable1/{variable1}을 통해서 받고자 하는 매개변수 위치에 중괄호 표시를 해줬습니다.
또한, @PathVariable 뒤에 특정 매개변수의 이름을 정의하고 다른 이름으로 변수에 매개변수의 값을 할당할 수 있습니다.
@Controller
@RequestMapping("/api/v1/get-api")
public class TestController {
@GetMapping("/variable1/{variable1}")
public String getVariable1(@PathVariable String variable1){
return variable1;
}
@GetMapping("/variable2/{variable2}")
public String getVariable1(@PathVariable("variable2") String temp){
return temp;
}
}
5.2.4 @RequestParam을 활용한 GET 메서드 구현
GET 요청을 보내는 방법에서는 크게 2가지로 분류가 되는데 /로 구분되는 Path Variable과 name/?name=123과 같이 같이 ? 뒤에 키-밸류 값 형식으로 값을 전달할 수도 있습니다. 이와 같은 표현식을 쿼리라고 합니다. 이런 방법을 서버에서 요청을 처리하기 위해서는 @RequestParam 어노테이션을 명시해 쿼리 값과 매핑하면 됩니다.
또한, 어떤 쿼리 값이 들어올지 모른다면, Map 자료구조를 사용해서 loop 문을 통해서 내부의 값을 조회할 수 있습니다.
@Controller
@RequestMapping("/api/v1/get-api")
public class TestController {
@GetMapping("/request1")
public String getRequestParam1(
@RequestParam String name,
@RequestParam String email,
@RequestParam String organization) {
return name + " " + email + " " + organization;
}
@GetMapping("/request2")
public String getRequestParam1(
@RequestParam Map<String,String> params) {
StringBuilder sb = new StringBuilder();
params.entrySet().forEach(entry -> {
sb.append(entry.getKey()).append(" : ").append(entry.getValue()).append("\n");
});
return sb.toString();
}
}
5.2.5 DTO 객체를 활용한 GET 메서드 구현
DTO란 Data Trasnfer Object의 약자로, 다른 레이어 간의 데이터 교환에 활용되는 Object입니다.
간략하게 설명하자면, 각 클래스 및 인터페이스를 호출하면서 전달하는 매개변수의 역할을 수행합니다.
DTO는 데이터를 교환하는 용도로만 사용하기 때문에, DTO에는 별도의 로직이 포함되지 않습니다.
DTO와 VO(Value Object)
둘의 역할을 서로 엄밀히 구분하지 않고 사용할 때가 많다. 이렇게 해도 대부분의 상황에서는 큰 문제가 발생하지 않기 때문이다.
하지만, 엄밀히 살펴본다면 역할과 사용법에서 차이가 있습니다.
VO는 데이터 그 자체로 의미가 있는 객체를 의미합니다. 가장 특징적인 부분은 Read-Only로 설계한다는 점이다. VO는 값을 변경할 수 없게 만들어서 데이터의 신뢰성을 유지해야 합니다.
DTO는 외부에서 전달되는 데이터 컨테이너입니다. DTO는 다른 레이어로 전송된다고 위에 서술했었는데, 여기서 레이어는 서비스 레이어, 데이터 레이어와 같은 애플리케이션 내부 레이어를 의미하기도 하지만, 실무에서는 다른 서비스, 다른 노드와 같은 시스템을 의미할 수도 있습니다.
아래에서는 어떠한 어노테이션도 붙이지 않고 Company라는 DTO를 매개변수로 받는 GET API입니다.
실제로 쿼리로 들어올 때는, /ticker=MMM&name=3M과 같은 식으로 들어옵니다.
받는 값은 변화가 없지만, 실제 코드의 양은 줄어든 다는 사실을 알 수 있습니다.
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Company {
private String ticker;
private String name;
}
@Controller
@RequestMapping("/api/v1/get-api")
public class TestController {
@GetMapping("/company")
public String getCompany(
Company company) {
return company.toString();
}
}
5.3 POST API 만들기
POST API는 웹 애플리케이션을 통해 데이터베이스 등의 저장소에 리소스를 저장할 때 사용하는 API입니다. 이전의 GET API에서는 URL의 경로나 파라미터에 변수를 넣어서 요청을 보냈지만, POST에서는 body에 값을 담아서 서버로 전송합니다 그래서 URI가 GET API에 비해 간단합니다.
5.3.1 @RequestMapping으로 구현하기
일반적으로 POST요청은 사용자가 서버에 리소스를 저장하는 데 사용합니다. 그러므로 클라이언트의 요청 트래픽에 값이 포함되어 있습니다. 즉, POST 요청에서는 리소스를 담기 위해 HTTP Body에 값을 넣어 전송합니다.
@RestController
@RequestMapping("/api/v1/post-api")
public class PostController {
@RequestMapping(value = "/hello", method = RequestMethod.POST)
public String getHello(){
return "Hello World";
}
}
5.3.2 @RequestBody를 활용한 POST 메서드 구현
일반적으로 바디의 형식은 Json 형식으로 되어있습니다.
{
"ticker":"MMM"
}
아래에서는 PostMapping이라는 어노테이션을 사용해서 해당 경로로 오는 API 요청에 대해서 postData라는 변수에 대입하고 메서드 내부에서는 forEach문을 통해서 값을 파싱 한다. 또는, DTO 객체를 통해서 body값을 받을 수도 있다.
@RestController
@RequestMapping("/api/v1/post-api")
public class TestController {
@PostMapping("/member")
public String postMember(@RequestBody Map<String, Object> postData){
StringBuilder sb = new StringBuilder();
postData.entrySet().forEach(
entry -> sb.append(entry.getKey()).append(" : ").append(entry.getValue()).append("\n")
);
return sb.toString();
}
@PostMapping("/member2")
public String postMember2(@RequestBody Company company){
return company.toString();
}
}
5.4 PUT API 만들기
PUT API는 웹 애플리케이션 서버를 통해 데이터베이스 같은 저장소에 존재하는 리소스 값을 업데이트하는 데 사용합니다.
수정하기 위해서는 body에 값을 넣어 전달하고 이를 통해 업데이트를 진행합니다.
하지만 실무에서는 PUT Method를 사용하지 않는데, 이유는 PUT 메서드는 해당 테이블에 존재하는 모든 값을 수정하기 때문입니다.
WHERE절을 실수로 넣지 않는 경우, 모든 데이터들이 변경될 우려가 있습니다. 따라서 수정을 하기 위해서는 PATCH Method를 사용하여 진행합니다.
5.4.1 @RequestBody를 활용한 PUT 메서드 구현
@RequestBody를 사용하는 POST 메서드와 구조적으로 동일합니다.
@RestController
@RequestMapping("/api/v1/post-api")
public class TestController {
@PutMapping("/member")
public String postMember(@RequestBody Map<String, Object> postData){
StringBuilder sb = new StringBuilder();
postData.entrySet().forEach(
entry -> sb.append(entry.getKey()).append(" : ").append(entry.getValue()).append("\n")
);
return sb.toString();
}
@PutMapping("/member2")
public String postMember2(@RequestBody Company company){
return company.toString();
}
}
5.4.2 ResponseEntity를 활용한 PUT 메서드 구현
Spring Framework에는 HttpEntity라는 클래스가 있습니다. HttpEntity는 다음과 같이 Header와 Body로 구성된 HTTP 요청과 응답을 구성하는 역할을 수행합니다. ResponseEntity와 RequestEntity는 HttpEntity를 상속받아 구현한 구현체입니다. 그중 Response Entity는 서버에 들어온 요청에 대해 응답 데이터를 구성해서 전달할 수 있습니다.
이 클래스를 활용하면, 응답 코드 변경과 Header와 Body를 더욱 쉽게 구성할 수 있습니다. PUT 메서드뿐만 아니라 모든 메서드에서도 사용할 수 있는 클래스입니다.
@RestController
@RequestMapping("/api/v1/post-api")
public class TestController {
@PutMapping("/member2")
public ResponseEntity<Company> postMember2(@RequestBody Company company){
return ResponseEntity.status(HttpStatus.OK).body(company);
}
}
5.5 DELETE API 만들기
DELETE API는 클라이언트가 서버에 저장한 데이터를 삭제하고자 할 때 사용하는 API Method입니다.
서버에서는 클라이언트로부터 리소스를 식별할 수 있는 값을 받아 데이터베이스나 캐시에 있는 리소스를 조회하고 삭제하는 역할을 수행합니다. 이때 컨트롤러에서는 간단한 값을 받기 때문에 GET메서드와 같이 URI에 값을 넣어 요청을 받는 형식으로 구현됩니다.
5.5.1 @PathVariable과 @RequestParam을 활용한 DELETE 메서드 구현
이전에 GET 메서드를 살펴볼 때와 매우 유사한 구조로 API 요청을 받는다는 것을 알 수 있습니다.
@RestController
@RequestMapping("/api/v1/delete-api")
public class TestController {
@DeleteMapping("/{category}")
public String DeleteVariable(@PathVariable String variable,
@RequestParam Map<String, String> queryParameters) {
return "Delete request received with variable: " + variable +
" and query parameters: " + queryParameters;
}
}
5.6 [ 한걸음 더 ] REST API 명세를 문서화하는 방법 - Swagger
API를 개발하면 명세를 관리해야 합니다.
명세란 해당 API가 어떤 로직을 수행하는지 설명하고 이 로직을 수행하기 위해 어떤 값을 요청하며, 이에 따른 응답값으로는 무엇을 받을 수 있는지를 정리한 자료입니다.
API는 개발 과정에서 계속 변경되므로 작성한 명세문서도 주기적인 업데이트가 필요합니다. 또한, 명세 작성 작업은 번거롭고 시간이 오래 걸립니다. 이러한 문제점을 해결하기 위해서 나온 해결책은 Swagger라는 오픈소스 프로젝트입니다.
Springboot 2.x.x 버전에서는 springdoc, springfox 모두 사용 가능하지만, Springboot 3.x.x 버전대에서는 Springdoc만 사용해야 합니다.
자신이 프로젝트 빌드 툴을 Gradle을 사용한다면, build.gradle에. Maven을 사용한다면 pom.xml에 dependency를 추가합니다.
///build.gradle
// swagger
implementation group: 'io.springfox', name: 'springfox-boot-starter', version: '3.0.0'
// SwaggerConfig
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket swaggerApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.zero.stock.web"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Stock API")
.description("API for stock information")
.version("1.0")
.build();
}
}
이렇게 작성하고 애플리케이션을 재실행한 이후, localhost:8080/swagger-ui/index.html 경로로 접속하면 아래와 같은 API 목록을 볼 수 있고, 어떤 매개변수들이 들어가는지, 어떤 body값을 서버로 전송하는지, 이에 따른 response type과 형태가 어떤지를 확인할 수 있습니다.

5.7 [ 한걸음 더 ] 로깅 라이브러리 - Logback
로깅(logging)이란 애플리케이션이 동작하는 동안 시스템의 상태나 동작 정보를 시간순으로 기록하는 것을 의미합니다. 로깅은 개발 영역 중 '비기능 요구사항'에 속합니다. 즉, 사용자나 고객에게 필요한 기능은 아니라는 의미입니다. 하지만 로깅은 디버깅하거나 개발 이후 발생한 문제를 추적하고 해결할 때 원인을 분석하는데 꼭 필요한 요소입니다.
자바 진영에서 로깅을 위해서 가장 많이 사용하는 로깅 라이브러리는 Logback 라이브러리입니다. Logback은 log4j 이후에 만들어진 프레임워크로, Slf4j를 기반으로 구현됐으며, 이전에 있던 라이브러리보다 월등한 성능을 자랑합니다. 또한, spring-boot-starter-web 디펜던시 안에 내장되어 있어 추가적인 설치는 불필요하다는 장점이 있습니다.
5.7.1 Logback 설정
프로젝트 디렉터리 중 resources 패키지 내부에 logback-spring.xml 파일을 생성한 후 아래와 같이 작성합니다.
<configuration>
<property name="LOG_DIR" value="./logs" />
<property name="LOG_FILE_NAME" value="mylog" />
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern> %d{HH:mm:ss.SSS} %highlight(%-5level) %magenta(%-4relative) --- [ %thread{10} ] %cyan(%logger{29}) - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/${LOG_FILE_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/${LOG_FILE_NAME}-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [ %thread ] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework" level="INFO"/>
<logger name="org.hibernate" level="INFO"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
전체 코드가 모두 configuration이라는 필드 내부에 정의되어 있으며, property라는 필드를 통해서 해당 파일 전역에서 사용할 변수를 미리 선언할 수 있습니다.
다음으로 appender를 통해서 로그의 형태를 설정하고 어떤 방법으로 출력할지를 설정하는 곳입니다. Appender 자체는 하나의 인터페이스를 의미하며, 하위에 여러 구현체가 존재합니다.

또한 여러 구현체를 통해서 콘솔에 로그를 출력하거나, 파일에 로그를 저장하는 등 여러 가지의 역할을 수행할 수 있습니다.
RollingFIleAppender를 통해서는 여러 파일을 순회하면서 로그를 저장하는 기능을 수행할 수 있습니다.
Appender에 FILE이라는 이름을 부여하게 되면 해당 Appender에서는 로그를 파일에 저장하는 기능을 수행하는데, SizeAndTImeBasedRollingPolicy를 적용하여 최대 며칠간 최대 용량을 지정하여 해당 용량을 넘어가면 다음 로그파일에 저장할 수 있습니다. 이를 통해 로그파일을 생성하고 나중에 배울 ELK Stack을 통해서 로그의 내용을 시각화할 수 있습니다.
5.7.1 Logback 적용하기
위의 설정 파일을 기반으로 logback을 적용하면 logs라는 폴더 아래에 mylog-yyyy-mm-hh-ss.i.log로 저장되고 30개가 넘어가는 경우 gz 형식으로 압축하여 로그를 저장합니다.


5.8 정리
5장에서는 다양한 API Method를 통해서 어떤 목적을 가지고 서버에 요청을 보내는지에 따라서 GET, POST, PUT, DELETE와 같은 CRUD API를 알아봤습니다. 이미 알고 있던 내용들도 존재하지만, 보다 깊게 살펴볼 수 있었습니다.
'책' 카테고리의 다른 글
[Book] 스프링 부트 핵심 가이드 Chap.8 (0) | 2024.07.14 |
---|---|
[Book] 스프링 부트 핵심 가이드 Chap.6 (0) | 2024.07.02 |
[Book] 스프링 부트 핵심 가이드 Chap.4 (0) | 2024.06.25 |
[Book] 스프링 부트 핵심 가이드 Chap.3 (0) | 2024.06.23 |
[Book] 스프링 부트 핵심 가이드 Chap.2 (0) | 2024.06.23 |