API?
API는 문자 그대로 애플리케이션 프로그래밍 인터페이스를 의미합니다. 이 컴퓨팅 인터페이스 덕분에 두 개의 개별 소프트웨어 시스템 간의 통신 및 데이터 교환이 가능합니다. API를 실행하는 소프트웨어 시스템에는 다른 소프트웨어 시스템이 수행할 수 있는 일부 필수 기능이 포함되어 있습니다.
본격적으로 Spring Boot로 애플리케이션을 개발하기 시작하기 위해, 각 HTTP 메서드에 해당하는 API를 개발해보겠습니다.
GET API만들기
GET API는 웹 애플리케이션 서버에서 값을 가져올 때 사용하는 API입니다. GET API를 작성하는 방법은 다양하며, 지금은 애플리케이션으로 들어오는 여러 요청에 대한 처리 방법의 하나로 진행해보겠습니다.
실무에서는 HTTP 메서드에 따라 컨트롤러 클래스를 구분하지 않는다고 합니다. 하지만 저는 일단 메서드별로 클래스를 생성해보겠습니다.
controller 패키지를 생성하고 GetController 클래스를 생성하겠습니다.
그리고 위처럼, 컨트롤러에 ‘@RestController’와 ‘@RequestMapping’을 붙여 내부에 선언되는 메서드에서 사용할 공통 URL을 설정합니다.
클래스 수준에서 ‘@RequestMapping’을 설정하면 내부에 선언한 메서드의 URL 리소스 앞에 ‘@RequestMapping’의 값이 공통 값으로 추가됩니다.
@RequestMapping으로 구현하기
‘@RequestMapping’ 어노테이션을 별다른 설정 없이 선언하면 HTTP의 모든 요청을 받습니다. 그러나 GET 형식의 요청만 받기 위해서는 어노테이션에 별도 설정이 필요합니다. ‘@RequestMapping’ 어노테이션의 method 요소의 값을 ‘RequestMethod.GET’으로 설정하면 요청 형식을 GET으로만 설정할 수 있습니다.
스프링 4.3 버전 이후로는 새로 나온 아래의 어노테이션을 사용하기 때문에 ‘@RequestMapping’ 어노테이션은 더 이상 사용되지 않습니다. 따라서 각 HTTP 메서드에 맞는 어노테이션을 사용하며 진행합니다.
각 HTTP 메서드
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
이후 API 테스트를 위해서 Postman을 돌려서 예제에서 작성한 메서드를 호출해 봅니다.
그러면 하단에 작성한 return의 내용인 'Hellow World'가 Response로 나오는 것을 볼 수 있습니다.
매개변수가 없는 GET 메서드 구현
위와같이 별도의 매개변수 없이 GET API를 구현하는 경우, URL을 입력하고 요청할 때, 스프링 부트 애플리케이션이 정해진 응답을 반환합니다.
@PathVariable을 활용한 GET 메서드 구현
실무 환경에서는 매개변수를 받지 않는 메서드가 거의 쓰이지 않는다고 합니다. 웹 통신의 기본 목적은 데이터를 주고받는 것이기 때문에 대부분 매개변수를 받는 메서드를 작성하게 됩니다.
매개변수를 받을 때 자주 쓰이는 방법 중 하나는 URL 자체에 값을 담아 요청하는 것입니다.
1번째 줄을 보면 중괄호 {}로 표시된 위치의 값을 받아 요청하는 것을 알 수 있습니다.
실제로 요청할 때는 중괄호가 들어가지는 않고 괄호 안의 값만 존재합니다. 값을 간단히 전달할 때 주로 사용하는 방법이며, GET 요청에서 많이 사용됩니다.
이렇게 코드를 작성할 때는 ‘@GetMapping’ 어노테이션의 값으로 URL을 입력할 때 중괄호를 사용해 어느 위치에서 값을 받을지 지정해야 합니다. 또한 메서드의 매개변수와 그 값을 연결하기 위해 3번 줄과 같이 ‘@PathVariable’을 명시하며, ‘@GetMapping 어노테이션과 ‘@PathVariable’에 지정된 변수의 이름을 동일하게 맞춰야 합니다.
만약 ‘@GetMapping’ 어노테이션에 지정한 변수의 이름과 메서드 매개변수의 이름을 동일하게 맞추기 어렵다면, ‘@PathVariable’ 뒤에 괄호를 열어 ‘@GetMapping’ 어노테이션의 변수명을 지정합니다.
2번 줄에 적혀있는 ‘variable’과 3번 줄에 적힌 매개변수명인 ‘var’가 서로 일치하지 않는 상황에서 두 값을 매핑하는 방법입니다. ‘@PathVariable’에는 변수의 이름을 특정할 수 있는 value 요소가 존재하며, 이 위치에 변수 이름을 정의하면 매개변수와 매핑할 수 있습니다.
@RequestParam을 활용한 GET 메서드 구현
GET 요청을 구현할 때 방금 전에 살펴본 URL 경로에 값을 담아 요청을 보내는 방법 외에도 쿼리 형식으로 값을 전달할 수도 있습니다. URL에서 “?”를 기준으로 우측에 ‘{키} = {값}’ 형태로 구성된 요청을 전송하는 방법입니다.
애플리케이션에서 이와 같은 형식을 처리하려면 ‘@RequestParam’을 활용하면 되는데 매개변수 부분에 ‘@RequestParam’ 어노테이션을 명시해 쿼리 값과 매핑하면 됩니다.
1번 줄을 보면 “?” 오른쪽에 쿼리스트링(query string)이 명시돼 있습니다. 쿼리스트링에는 키(변수 이름)가 모두 적혀 있기 때문에 이 값을 기준으로 메서드의 매개변수에 이름을 매핑하면 값을 가져올 수 있습니다. 키와 ‘@RequestParam’ 뒤에 적는 이름을 동일하게 설정하기 어렵다면 ‘@PathVariable’ 예제에서 사용한 방법처럼 value 요소로 매핑합니다.
만약 쿼리스트링에 어떤 값이 들어올지 모른다면 Map 객체를 활용할 수도 있습니다.
이런 형태로 코드를 작성하면 값에 상관없이 요청을 받을 수 있습니다.
예를 들어, 회원 가입 관련 API에서 사용자는 회원 가입을 하면서 ID 같은 필수 항목이 아닌 취미 같은 선택 항목에 대해서는 값을 기입하지 않는 경우가 있습니다. 이러한 경우에는 매개변수의 항목이 일정하지 않을 수 있어 Map 객체로 받는 것이 효율적입니다.
DTO 객체를 활용한 GET 메서드 구현
DTO란?
DTO는 Data Transfer Object의 약자로, 다른 레이어 간의 데이터 교환에 활용됩니다. 각 클래스 및 인터페이스를 호출하면서 전달하는 매개변수로 사용되는 데이터 객체입니다.
DTO는 데이터를 교환하는 용도로만 사용하는 객체이기 때문에 DTO에는 별도의 로직이 포함되지 않습니다.
※DTO와 VO
DTO와 VO(Value Object)의 역할을 서로 엄밀하게 구분하지 않고 사용할 때가 많습니다.
대부분의 상황에서는 큰 문제가 발생하지 않지만 정확하게 구분하자면 역할과 사용법에서 차이가 있습니다.
VO : 데이터 그 자체로 의미가 있는 객체를 의미합니다. 가장 특징적인 부분은 읽기전용(Read-Only)으로 설계한다는 점이다. 즉, VO는 값을 변경할 수 없게 만들어 데이터의 신뢰성을 유지해야 합니다.
DTO : 데이터 전송을 위해 사용되는 데이터 컨테이너라고 볼 수 있습니다. 같은 애플리케이션 내부에 정의된 레이어일 수도 있고 인프라 관점에서의 서버 아키텍처 상의 레이어일 수도 있습니다.
실습을 위해 ‘com.spring_project 패키지 하단에 dto라는 패키지를 생성한 후 ‘MemberDto’ 클래스를 만들어주면 됩니다.
package com.spring_project.dto;
public class MemberDto {
private String name;
private String email;
private String organization;
// Getter 메서드
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public String getOrganization() {
return organization;
}
// Setter 메서드
public void setName(String name) {
this.name = name;
}
public void setEmail(String email) {
this.email = email;
}
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 객체를 활용해 코드의 가독성을 높일 수 있습니다.
DTO 객체를 활용한 GET 메서드 구현
DTO 객체를 활용한 GET 메서드 구현을 통해 코드의 양이 줄은 것을 확인할 수 있습니다.
POST API 만들기
POST API는 웹 애플리케이션을 통해 데이터베이스 등의 저장소에 리소스를 저장할 때 사용되는 API입니다. GET API에서는 URL의 경로나 파라미터 변수를 넣어 요청을 보냈지만 POST API에서는 저장하고자 하는 리소스나 값을 HTTP 바디(body)에 담아 서버에 전달합니다.
그래서 URI가 GET API에 비해 간단합니다.
코드 작성을 위해 ‘controller’ 패키지에서 ‘PostController’라는 이름의 컨트롤러 클래스를 생성하고, ‘@RequestMapping’ 어노테이션을 이용해 공통 URL을 설정합니다.
@RequestMapping으로 구현하기
POST API에서 ‘@RequestMapping’을 사용하는 방법은 GET API와 크게 다르지 않습니다.
요청 처리 메서드를 정의할 때 method 요소를 RequestMethod.POST로 설정하는 부분을 제외하면 GET API와 동일합니다.
@RequestBody를 활용한 POST 메서드 구현
일반적으로 POST 형식의 요청은 클라이언트가 서버에 리소스를 저장하는 데 사용합니다. 그러므로 클라이언트의 요청 트래픽에 값이 포함돼 있습니다. 즉, POST 요청에서는 리소스를 담기 위해 HTTP Body에 값을 넣어 전송합니다. 아래과 같이 Talend API Test에서 Body 영역에 값을 입력할 수 있습니다.
위와 같이 Postman에서 Body값을 영역에 값을 입력할 수 있습니다.
Body 영역에 작성되는 값은 일반적으로 JSON(JavaScript Object Notation) 형식으로 전송됩니다.
이렇게 서버에 들어온 요청은 아래와 같이 처리할 수 있습니다.
2번 줄을 보면 ‘@RequestMapping’이 아닌 ‘@PostMapping’을 사용했습니다. 이 어노테이션을 사용하면 method 요소를 정의하지 않아도 됩니다. 그리고 3번 줄에 ‘@RequestBody’라는 어노테이션을 사용했는데, ‘@RequestBody’는 HTTP의 Body 내용을 해당 어노테이션이 지정된 객체에 매핑하는 역할을 합니다.
JSON
자바스크립트의 객체 문법을 따르는 문자 기반의 데이터 포맷이다. 현재는 자바스크립트 외에도 다수의 프로그래밍 환경에서 사용한다. 대체로 네트워크를 통해 데이터를 전달할 때 사용하며, 문자열 형태로 작성되기 때문에 파싱하기도 쉽다는 장점이 있다.
Map 객체는 요청을 통해 어떤 값이 들어오게 될지 특정하기 어려울 때 주로 사용됩니다.
요청 메시지에 들어갈 값이 정해져 있다면 DTO 객체를 매개변수로 삼아 작성할 수 있습니다.
위와 같이 작성하면 MemberDto의 멤버 변수를 요청 메시지의 키와 매핑하여 값을 가져옵니다.
PUT API 만들기
PUT API는 웹 애플리케이션 서버를 통해 데이터베이스 같은 저장소에 존재하는 리소스 값을 업데이트 하는 데 사용합니다. POST API와 비교하면 요청을 받아 실제 데이터베이스에 반영하는 과정(서비스 로직)에서 차이가 있지만 컨트롤러 클래스를 구현하는 방법은 POST API와 거의 동일합니다. 리소스를 서버에 전달하기 위해 HTTP Body를 활용해야 하기 때문입니다.
실습을 위해 controller 패키지에 PutController 클래스를 생성합니다.
@RequestBody를 활용한 PUT 메서드 구현
PUT API는 POST 메서드와 마찬가지로 값을 HTTP Body에 담아 전달합니다.
서버에서는 이 값을 받기 위해 ‘@RequestBody’를 사용합니다.
서버에 어떤 값이 들어올지 모르는 경우에는 Map 객체를 활용해 값을 받을 수 있습니다. 대부분의 경우 API를 개발한 쪽에서 작성한 명세(specification)를 웹 사이트를 통해 클라이언트나 사용자에게 올바른 사용법을 안내합니다. 만약 서버에 들어오는 요청에 담겨 있는 값이 정해져 있는 경우 DTO 객체를 활용해 구현합니다.
첫 번째 메서드인 ‘postMemberDto1’은 리턴 값이 String 타입이고, 두 번째 메서드인 ‘postMemberDto2’는 DTO 객체 타입입니다.
먼저 ‘postMemberDto1’ 메서드로 응답 결과를 확인해보겠습니다.
출력 결과를 보면 보낸 요청 내용이 그대로 결괏값으로 전달된 것을 볼 수 있습니다. toString 메서드로 인해 나름의 형식이 갖춰져 전달됐지만 HEADERS 항목의 content-type을 보면 ‘text/plain’으로서 결괏값으로 일반 문자열이 전달됐음을 확인할 수 있습니다.
이번에는 동일한 값을 담고 URL만 ‘postMemberDto2'로 변경하여 결과를 확인해보겠습니다.
우측의 BODY 값은 Talend API가 정렬해 출력한 결괏값으로서 실제로는 형식만 유지한 채 전달됩니다.
또한 HEADERS 영역의 Content-Type 항목도 member1 메서드로 전달받은 값은 ‘text/plain’이었던 반면 지금은 ‘application/json’ 형식으로 전달된 것을 확인할 수 있습니다.
‘@RestController’ 어노테이션이 지정된 클래스는 ‘@ResponseBody’를 생략할 수 있는데, 이 ‘@ResponseBody’ 어노테이션은 자동으로 값을 JSON과 같은 형식으로 변환해서 전달하는 역할을 수행합니다.
ResponseEntity를 활용한 PUT 메서드 구현
스프링 프레임워크에는 ‘HttpEntity’라는 클래스가 있습니다. ‘HttpEntity는 다음과 같이 헤더(Header)와 Body로 구성된 HTTP 요청과 응답을 구성하는 역할을 수행합니다.
// HttpEntity 클래스
public class HttpEntity<T> {
private final HttpHeaders headers;
@Nullable
private final T body;
...
}
RequestEntity와 ResponseEntity는 HttpEntity를 상속받아 구현한 클래스입니다. 그 중 ResponseEntity는 서버에 들어온 요청에 대해 응답 데이터를 구성해서 전달할 수 있게 합니다. 아래와 같이 ResponseEntity는 HttpEntity로부터 HttpHeaders와 Body를 가지고 자체적으로 HttpStatus를 구현합니다.
// ResponseEntity 클래스
public class ResponseEntity<T> extends HttpEntity<T> {
private final Object status'
..생략..
}
이 클래스를 활용하면 응답 코드 변경은 물론 Header와 Body를 더욱 쉽게 구성할 수 있습니다. 이 클래스는 다른 메서드에서도 모두 사용할 수 있는 클래스입니다.
다음은 메서드의 리턴 타입에 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);
}
3번째 줄에서 메서드의 리턴 타입을 ResponseEntity로 설정하고 4~6번 줄에서 리턴 값을 만듭니다.
status에 넣을 수 있는 값은 다양한데, 지금 사용한 HttpStatus.ACCEPTED는 응답 코드 202를 가지고 있습니다. 즉, 이 메서드를 대상으로 요청을 수행하면 응답 코드가 202로 변경됩니다.
DELETE API 만들기
DELETE API는 웹 애플리케이션 서버를 거쳐 데이터베이스 등의 저장소에 있는 리소스를 삭제할 때 사용합니다. 서버에서는 클라이언트로부터 리소스를 식별할 수 있는 값을 받아 데이터베이스나 캐시에 있는 리소스를 조회하고 삭제하는 역할을 수행합니다. 이때 컨트롤러를 통해 값을 받는 단계에서는 간단한 값을 받기 때문에 GET메서드와 같이 URI에 값을 넣어 요청을 받는 형식으로 구현됩니다.
@PathVariable과 @RequestParam을 활용한 DELETE 메서드 구현
‘@PathVariable’을 이용하면 URI에 포함된 값을 받아 로직을 처리할 수 있습니다.
‘@DeleteMapping’ 어노테이션에 정의한 value의 이름과 메서드의 매개변수 이름을 동일하게 설정해야 삭제할 값이 주입됩니다. 또는 ‘@RequestParam’ 어노테이션을 통해 쿼리스트링 값도 받을 수 있습니다.
REST API 명세를 문서화하는 방법 - Swagger
API를 개발하면 명세를 관리해야 합니다. 명세란 해당 API가 어떤 로직을 수행하는지 설명하고 이 로직을 수행하기 위해 어떤 값을 요청하며, 이에 따른 응답값으로는 무엇을 받을 수 있는지를 정리한 자료입니다.
API는 개발 과정에서 계속 변경되므로 작성한 명세 문서도 주기적인 업데이트가 필요한다. 또한 명세 작업은 번거롭고 시간이 오래걸립니다. 이와 같은 문제를 해결하기 위해 등장한 것이 ‘Swagger’라는 오픈소스 프로젝트입니다.
Swagger를 사용하기 위해서는 먼저 pom.xml 파일에 의존성을 추가해야 합니다.
<dependencies>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
</dependencies>
그 뒤에 config 패키지를 생성한 후, SwaggerConfiguration 클래스를 생성해줍니다.
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket api() {
return new Docket (DocumentationType.SWAGGER_2)
.apiInfo(apiInfo()) Docket
.select() ApiSelectorBuilder
.apis (RequestHandlerSelectors.basePackage ("com.springboot.api"))
.paths (PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Spring Boot Open API Test with Swagger")
.description("")
.version("1.0.0")
.build();
}
}
Swagger 사용을 위한 기본적인 설정은 완료되었습니다.
10번 줄에서는 Swagger에서 스캔할 패키지 범위를 ‘RequestHandlerSelectors.basePackage()’ 메서드를 사용해 설정합니다. 현재 프로젝트의 루트 패키지는 com.springboot.api로 설정 돼있고 하위 패키지와 클래스를 모두 스캔해서 문서를 생성합니다.
인텔리제이 IDEA에서 애플리케이션을 실행한 후 웹 브라우저를 통해 http://localhost:8080/swagger-ui.html로 접속하면 아래와 같이 Swagger 페이지가 출력됩니다.
Swagger를 더 잘 활용하기 위해 이번 장에서 작성한 API 중 ‘@RequestParam’을 활용한 GET 메서드에 대한 명세의 세부 내용을 설정합니다. ‘GetController’에 작성한 메서드를 다음과 같이 수정해보겠습니다.
@ApiOperation(value = "GET 메서드 예제", notes = "@RequestParam을 활용한 GET Method")
@GetMapping(value = "/request1")
public String getRequestParam1(
@ApiParam(value = "이름", required = true) @RequestParam String name,
@ApiParam(value = "이메일", required = true) @RequestParam String email,
@ApiParam(value = "회사", required = true) @RequestParam String organization) {
return name + + email + + organization;
}
- @ApiOperation : 대상 API의 설명을 작성하기 위한 어노테이션
- @ApiParam : 매개변수에 대한 설명 및 설정을 위한 어노테이션입니다. 메서드의 매개변수뿐 아니라 DTO 객체를 매개변수로 사용할 경우 DTO 클래스 내의 매개변수에도 정의할 수 있습니다.
로깅 라이브러리 - Logback
로깅(logging)이란 애플리케이션이 동작하는 동안 상태나 동작 정보를 시간순으로 기록하는 것을 의미합니다.
로깅은 개발 영역 중 ‘비기능 요구사항’에 속한다. 즉, 사용자나 고객에게 필요한 기능은 아니라는 뜻입니다. 하지만 로깅은 디버깅이나 개발 이후 발생한 문제를 해결할 때 원인을 분석하는 데 꼭 필요한 요소입니다.
자바 진영에서 가장 많이 사용되는 로깅 프레임워크는 Logback입니다. Logback이란 log4j 이후에 출시된 로깅 프레임워크로서 slfj를 기반으로 구현됐으며, 과거에 사용되던 log4j에 비해 월등한 성능을 가집니다. 또한 spring-boot-starter-web 라이브러리 내부에 내장돼 있어 별도의 의존성을 추가하지 않아도 사용할 수 있습니다.
Logback의 특징은 다음과 같습니다.
- 크게 5개의 로그 레벨(TRACE, DEBUG, INFO, WARN, ERROR)을 설정할 수 있습니다.
- ERROR : 로직 수행 중 시스템에 심각한 문제가 발생해서 애플리케이션의 작동이 불가능한 경우를 의미합니다.
- WARN : 시스템 에러의 원인이 될 수 있는 경고 레벨을 의미합니다.
- INFO : 애플리케이션의 상태 변경과 같은 정보 전달을 위해 사용합니다.
- DEBUG : 애플리케이션의 디버깅을 위한 메시지를 표시하는 레벨을 의미합니다.
- TRACE : DEBUG 레벨보다 더 상세한 메시지를 표현하기 위한 레벨을 의미합니다.
- 실제 운영 환경과 개발 환경에서 각각 다른 출력 레벨을 설정해서 로그를 확인할 수 있습니다.
- Logback의 설정 파일을 일정 시간마다 스캔해서 애플리케이션을 재기동하지 않아도 설정을 변경할 수 있습니다.
- 별도의 프로그램 지원 없이도 자체적으로 로그 파일을 압축할 수 있습니다.
- 저장된 로그 파일에 대한 보관 기간 등을 설정해서 관리할 수 있습니다.
Logback 설정
Logback 설정일반적으로 클래스패스(classpath)에 있는 설정 파일을 자동으로 참조하므로 Logback 설정 파일은 리소스 폴더 안에 생성합니다. 일반적인 자바 또는 스프링 프로젝트에서는 logback.xml이라는 이름으로 참조하지만 스프링 부트에서는 logback-spring.xml 파일을 참조합니다.
'Backend > Spring Boot' 카테고리의 다른 글
Spring Boot - MVC 모델 (0) | 2023.07.30 |
---|---|
Spring Boot - Spring Data JPA 활용 (0) | 2023.07.29 |
Spring boot - VScode로 시작하기 (0) | 2023.07.16 |
Spring boot - 데이터베이스 연동 (0) | 2023.07.15 |
Spring Boot 개발에 앞서 알면 좋은 기본 지식 (2) | 2023.07.08 |