이 글은 GDSC Ewha 서버 파트의 2024 솔루션 챌린지를 위해 진행한 Springboot 미니 프로젝트를 설명하는 글입니다. GDSC Ewha의 활동을 더 보고 싶다면 여기를 참고해주세요.
프로젝트의 깃허브 레포지토리 링크
RestAPI란?
API란 Client-Server간 데이터를 주고받는 스팩을 의미합니다. 예를 들어서 FE에서 특정 API Call을 한다면, BE에서는 이 API 스펙에 맞게 데이터를 조회하거나, 삭제하는 일 등을 진행합니다.
이 중 RestAPI는 RESTful한 아키텍처를 따르는 API를 뜻합니다. RESTful한 아키텍처의 특징은 다음과 같습니다.
- HTTP URI(Uniform Resource Identifier)를 통해 자원(Resource)을 명시
- HTTP Method(POST, GET, PUT, DELETE, PATCH 등)를 사용
- 해당 자원(URI)에 대한 CRUD Operation을 적용
예를 들어서, “/user/create 리소스에 대해서 POST Method를 적용하고 싶다”라는 형식의 메세지를 FE에서 주고, BE는 이 요청을 처리하는 아키텍처를 바로 RestAPI라고 합니다.
RestAPI 만들기
RestController 만들기
프론트엔드와 REST API를 통해서 통신할 것이기 때문에, Controller를 바로 RestController로 만들어 주겠습니다. 기존에는 @Controller 어노테이션 썼다면, 이제는 @RestController 어노테이션를 통해서 REST API를 간단하게 만들 수 있습니다. 또한 return 값도 이제 html 탬플릿이 아니라 ResponseEntity를 통해 HTTP 응답으로 전달해주어야 합니다.
@RestController의 기본적인 구조는 다음과 같습니다.
@RestController //필수: Rest API로 만듬
@RequiredArgsConstructor
public class Controller {
private final SampleService sampleService;
//GetMapping으로 조회
@GetMapping("/API 엔드포인트 1")
public ResponseEntity<SampleEntity> getData(@RequestBody String data){
// sampleService를 사용해 DB에서 데이터 조회
return new ResponseEntity<>(sampleEntity, HttpStatus.OK);
}
//PostMapping으로 데이터 저장, 변경 및 삭제
@PostMapping("/API 엔드포인트 2")
public ResponseEntity<Void> postData(@RequestBody String data){
// sampleService를 사용해 DB에서 데이터 저장, 변경 및 삭제
return new ResponseEntity<>(HttpStatus.CREATED);
}
}
GetMapping, 즉 조회가 일어날 때는 사용자가 조회할 데이터와, HTTP 상태 중 OK를 반환하고, PostMapping으로 데이터 저장이 일어날 때는 HTTP 상태 중 CREATED를 반환하는 것을 알 수 있습니다(물론 추가적으로 다른 데이터를 줄 수도 있습니다).
이러한 반환 값은 자동으로 JSON으로 변환됩니다. 즉, 프론트와 벡은 서로 JSON 타입의 데이터를 주고 받습니다.
한 가지 더 중요한 점은 @RequiredArgsConstructor입니다. 이 어노테이션은 final이 붙은 필드의 생성자를 만듭니다. 즉, service의 생성자를 만듭니다.
@RequestBody
이 어노테이션은 무엇을 의미하는 걸까요? 이를 알기 위해서 먼저 HTTP 메세지의 구조를 알아보겠습니다.
HTTP는 위와 같이 Header와 Body로 나누어져 있습니다. Body는 요청이나 응답에서 전달할 실제 데이터를 의미하고, Header는 Body의 데이터를 해석할 수 있는 정보(데이터 유형(html, json), 데이터 길이, 압축 정보 등등)를 제공하는 역할입니다.
이 Body에 클라이언트와 서버 간 주고받을 데이터를 넣어야 합니다. 예를 들어, 클라이언트에서 {"data":"hello"}라는 데이터를 주고, 서버에서는 이 데이터를 받아서 Entity에 넣은 다음 DB에 저장한다고 가정하겠습니다. 이럴 경우 Key가 data이고, Value가 String인 데이터가 반드시 클라이언트에게서 전달 받아야 DB에 저장 가능합니다.
@RequestBody String data는 바로 이 HTTP 요청에서, Body에 Key가 data이고, Value가 String인 데이터를 반드시 넣을 것을 요청(request)한다는 어노테이션입니다. 따라서 API를 만들 때는 반드시 컨트롤러에 이 어노테이션을 전달받아야 합니다.
참고: 서버 사이드 랜더링vs클라이언트 사이드 랜더링
Controller와 RestController의 차이에 대해서 좀 더 자세하게 알아보겠습니다.
- @Controller 어노테이션이 부착된 클래스는 주로 View를 반환하는 데 사용됩니다. HTML 페이지, JSON, XML 등의 다양한 미디어 타입의 View를 생성하고 반환할 수 있습니다. 메소드들은 주로 데이터를 가공하고 뷰를 나타내는 템플릿을 선택하여 클라이언트에게 반환합니다.
- @RestController 어노테이션이 부착된 클래스는 주로 데이터를 반환하는 데 사용됩니다. 주로 JSON 또는 XML 형식의 데이터를 반환하며, 데이터는 주로 HTTP 응답 본문(body)에 포함됩니다. 메소드가 객체나 컬렉션을 반환하면 이는 자동으로 JSON 또는 XML로 변환되어 클라이언트에게 반환됩니다.
즉 위의 @Controller와 @RestController는 서버 사이드 랜더링/클라이언트 사이드 랜더링이라는 차이가 있습니다.
- 서버 사이드 랜더링: 서버에서 뷰 템플릿 엔진을 사용하여 HTML을 생성하고, 클라이언트에게 전달하는 것을 의미합니다. 즉, 서버에서 뷰를 렌더링하여 클라이언트에게 전달합니다. @Controller는 데이터를 받아와 HTML 페이지를 랜더링한 후, 클라이언트에게 이 랜러링된 HTML 페이지 전체를 전달하기 때문에 서버 사이드 랜더링입니다.
- 클라이언트 사이드 랜더링: 주로 API 엔드포인트를 정의하고, 클라이언트에게 JSON 또는 XML 형식의 데이터를 반환합니다. 클라이언트는 이 데이터를 받아와서 자체적으로 화면을 구성하고 랜더링합니다. @RestController는 API 엔드포인트가 호출될 경우 JSON 형식의 데이터를 반환하고 있고, 이 데이터를 받아 화면을 랜더링 하는 것은 프론트엔드의 몫이기 때문에 클라이언트 사이드 랜더링입니다.
프론트엔드과 백엔드가 따로 개발을 하기 위해서는 클라이언트 사이드 랜더링을 써야 합니다. 즉, 클라이언트에서는 화면을 구성하고 랜더링하고, 백엔드에서는 데이터를 조회하고 저장하는 로직을 담당합니다. 이 둘 사이에서 데이터를 주고받는 것은 바로 API를 통해서 가능합니다.
API를 통해 프론트엔드 개발자와 백엔드 개발자는 서로 API 스펙만 잘 정의하고, 각자 해야 하는 개발에 집중할 수 있습니다.
Swagger-UI로 API 확인하기
여러분들은 API를 만들었으므로, 이제 이 API를 확인해보고 싶을 때가 있을 겁니다. 컨트롤러 파일을 여러 개 만들 것이므로 어떤 API가 완성되었는지 한번에 보고 싶을 때도 있습니다. 따라서 API 코드만으로 바로 API 스팩 문서를 만들어주는 Swagger-UI 오픈소스를 사용해서 API를 확인하고자 합니다.
Swagger-UI 설치
먼저, build.gradle에 다음과 같이 종속성을 추가합니다. Spring Boot 3.0 이상 버전을 쓸 경우 반드시 다음 종속성을 추가해 주어야 합니다.
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
다음으로는 application.yml을 수정해야 합니다.
springdoc:
packages-to-scan: # 여기에 스캔할 패키지 디렉토리(전체 디렉토리)를 적으세요.
default-consumes-media-type: application/json;charset=UTF-8
default-produces-media-type: application/json;charset=UTF-8
swagger-ui:
path: /
disable-swagger-default-url: true
display-request-duration: true
operations-sorter: alpha
위의 내용 중 몇몇 중요한 설정의 의미는 다음과 같습니다.
- packages-to-scan: Swagger-UI가 스캔해서 문서화할 패키지의 경로를 지정합니다. 이 부분만 가장 상위 패키지로 수정해주시면 됩니다.
- default-consumes-media-type 및 default-produces-media-type: 각각 요청과 응답에 대한 기본 미디어 타입을 설정합니다. 여기에서는 JSON 형식의 미디어 타입을 사용하며, 문자 인코딩은 UTF-8로 지정됩니다.
- path: Swagger UI의 경로를 지정합니다. 여기에서는 루트 경로(/)에 Swagger UI를 설정합니다.
- operations-sorter: Swagger UI에서 오퍼레이션을 어떤 순서로 표시할지 지정합니다. 여기서는 알파벳 순으로 정렬합니다.
마지막으로 Config라는 이름의 Package를 만들고, 그 안에 SwaggerConfig라는 Java Class를 만들어줍니다. Class 내용은 다음과 같이 구성합니다.
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SwaggerConfig {
/*Info 객체를 생성하여 API 문서의 기본 정보를 설정
* 기본 정보: API 문서의 제목, 버전, 설명*/
private Info apiInfo() {
return new Info()
.title("제목을 작성해주세요.")
.version("0.0.1")
.description("설명을 작성해주세요.");
}
/*OpenAPI 객체를 생성하여 API 문서의 전반적인 구성을 설정
* 앞서 설정한 Info 객체를 지정*/
@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.components(new Components())
.info(apiInfo());
}
}
참고: Spring의 @Configuration과 @Bean이란?
- @Configuration: 이 어노테이션이 부여된 클래스에서는 @Bean 어노테이션을 사용하여 빈을 정의할 수 있습니다. 이 클래스 내에서 정의된 빈들은 스프링 컨테이너에 의해 관리됩니다. 즉, 이 클래스는 애플리케이션의 구성(configuration)을 담당합니다. 정리하자면, @Configuration은 1개 이상 Bean을 등록하고 있음을 명시하는 어노테이션입니다.
- @Bean: 이 어노테이션은 해당 객체가 빈으로 등록되도록 지정합니다. 빈은 인스턴스화된 객체를 의미하며, 스프링 컨테이너에 등록된 객체를 스프링 빈이라고 합니다. Java 프로그래밍을 처음 배울 때는 Class를 생성하고 new를 통해 원하는 객체를 직접 생성해 사용했었습니다. 하지만 Spring에서는 직접 new를 이용하여 생성한 객체가 아니라, Spring에 의하여 관리당하는 자바 객체를 사용합니다. 이렇게 Spring에 의하여 생성되고 관리되는 자바 객체를 Bean이라고 합니다. @Configuration 뿐만 아니라, 기존에 사용했었던 어노테이션인 @Controller, @RestController, @Service, @Repository는 모두 스프링 빈 등록 대상입니다. 이 어노테이션이 존재하는 클래스는 컴포넌트 스캔(Component Scan)의 기본 대상이 되며, 컴포넌트 스캔을 통해서 스프링 빈으로 등록됩니다. 따라서 Controller Class를 직접 new Controller();처럼 생성하지 않아도 사용 가능하게 되는 것입니다.
@Tag와 @RequestMapping, @Operation
Swagger-UI를 사용하기 위해서는 API에 특정 코드를 덧붙여야 합니다. 다음과 같이 @Tag와 @RequestMapping, @Operation를 설정합니다.
/*Class 위의 어노테이션*/
@Tag(name = "API 그룹의 이름을 적어주세요.", description = "API 그룹의 설명을 적어주세요.")
/*메서드 위의 어노테이션*/
@Operation(summary = "요약을 적어주세요.", description = "API 설명을 적어주세요.")
- @Tag: API 문서에 특정 그룹이나 카테고리를 정의할 때 사용됩니다. name 속성은 API 그룹의 이름을 나타내고, description 속성은 해당 그룹에 대한 간단한 설명을 제공합니다.
- @Operation: 특정 메소드가 OpenAPI 문서에서 하나의 연산(Operation)으로 나타낼 때 사용됩니다. 메소드에 대한 간단한 설명이나 요약을 제공할 수 있으며, 태그, 매개변수, 응답 등에 대한 세부 정보를 설정할 수도 있습니다.
즉, 이제 RestController는 다음과 같이 변경됩니다.
@RestController //필수: Rest API로 만듬
@Tag(name = "API 그룹의 이름을 적어주세요.", description = "API 그룹의 설명을 적어주세요.")
@RequiredArgsConstructor
public class Controller {
private final SampleService sampleService;
//GetMapping으로 조회
@GetMapping("/API 엔드포인트 1")
@Operation(summary = "요약을 적어주세요.", description = "API 설명을 적어주세요.")
public ResponseEntity<SampleEntity> getData(){
// sampleService를 사용해 DB에서 데이터 조회
return new ResponseEntity<>(sampleEntity, HttpStatus.OK);
}
//PostMapping으로 데이터 저장, 변경 및 삭제
@PostMapping("/API 엔드포인트 2")
@Operation(summary = "요약을 적어주세요.", description = "API 설명을 적어주세요.")
public ResponseEntity<Void> postData(){
// sampleService를 사용해 DB에서 데이터 저장, 변경 및 삭제
return new ResponseEntity<>(HttpStatus.CREATED);
}
}
이제 Swagger-UI의 세팅이 끝났습니다. 벡앤드 서버가 실행 중이라면(GCP VM에서 실행 등) 프론트엔드 개발자가 Swagger-UI를 통해 API 문서를 확인할 수 있습니다. 만약 아직 로컬 컴퓨터에서만 작업 중이여서 프론트엔드 개발자가 API 문서를 확인할 수 없다고 해도, 벡앤드 개발자가 한 페이지에서 바로 모든 API 목록을 확인 가능하므로 노션 등에 API 명세서를 만들 때 더 쉽게 업데이트를 할 수 있습니다.
Local PC에서 실행 중일 때는 다음 경로로 접근하면 Swagger-UI 문서를 확인할 수 있습니다.
http://localhost:8080/swagger-ui/index.html
Postman으로 API 테스트하기
위에서 만든 Swagger-UI에서는 실제 데이터를 가지고 API를 테스트하는 것도 가능합니다. 하지만 Postman을 사용하면 하나의 Workspace를 만들어서, 프론트엔드 개발자와 백엔드 개발자가 서로 같은 데이터로 API를 테스트해볼 수 있습니다.
먼저, Postman 프로그램 또는 웹 브라우저에 들어간 후, 상단의 Workspace에서 Create Workspace를 누릅니다.
Blank workspace를 선택하고, 팀 이름을 적절히 넣은 다음, “Team”을 선택합니다.
워크 스페이스가 생성된 다음에는 상단의 Invite를 눌러서 팀원들의 postman 이메일로 초대합니다. 이제 팀원들은 자신의 Postman 프로그램 또는 웹 브라우저에서 API를 테스트할 수 있습니다.
API 테스트
Postman에서는 다음과 같이 API를 테스트할 수 있습니다.
아직 아무런 컬렉션이 없다면, 우측 상단에서 컬렉션 생성
컬렉션의 메뉴 중 Add request를 선택
새로운 리퀘스트는 다음과 같이 설정합니다.
이때, API 경로는 다음과 같습니다.
- local 컴퓨터에서 백엔드 서버 실행 중인 경우: localhost:8080/리소스경로 (예시: localhost:8080/member)
- GCP VM에 백엔드 서버 배포한 경우: http://VM의공개IP.110:8080/리소스경로 (예시: http://700.000.000.000:8080/member)
중요! API 명세서
프론트와 백은 정확하게 API 명세서대로 개발해야 하므로, API 명세서를 잘 작성해야 합니다. API 명세서는 클라이언트가 어떤 것을 요청(request)하는지, 그에 대한 답변으로 서버는 무엇을 답변(reponse)하는지 정확히 명시되어야 합니다. 아래와 같은 형식으로 API 하나하나 당 자세히 적어주시면 됩니다.
[POST] 사용자의 data 저장하는 API
사용자가 전달하는 data를 받아서 DB에 저장 후, id 값을 반환하는 POST API 입니다.
Request 파라미터
이름 | 유형 | 필수/선택 | 설명 |
data | String | 필수 | 사용자가 저장할 데이터입니다. |
Reponse 파라미터
이름 | 유형 | 필수/선택 | 설명 |
data_id | Long | 선택 | 사용자 데이터를 DB에 저장한 후, 잘 저장되었는지 확인하기 위해 200 OK 이외에도 id 값을 반환합니다. 필수는 아닙니다. |
API 명세서 만들기
이제 실제로 API 명세서를 만들어보겠습니다. 이번 프로젝트에서는 이런 API를 만들려고 합니다.
(저는 지금 아주 간단하게만 명세서를 작성해보았습니다. 실제 명세서는 위와 같이 자세하게 적기 바랍니다.)
[👩💻프로필]
- [GET] 내 프로필 조회: Profile 객체 반환
- [POST] 내 프로필 사진 업로드
[🖼️포스트]
- [GET] 모든 포스트 조회: List<Post> 객체 반환
- [DELETE] 포스트 삭제: 기존 Post 객체 삭제
[👥유저]
- [POST] 유저 회원가입: email, password, age, name, address 값을 받아 Member 객체 생성
- [GET] 유저 로그인: email, password 일치하는지 확인
[👟서클]
- [POST] 써클 설명 수정
실제 서비스를 위해서는 더 많은 API가 있지만, 이번 미니 프로젝트 용으로는 위의 8개만 만들겠습니다. 좀 더 자세한 명세서는 실제 API를 만들면서 확인해보겠습니다.
다음 주차
이번 주에는 RestAPI가 무엇인지, 어떻게 Swagger-UI로 관리할 수 있는지 배우고, 앞으로 만들어야 할 API를 명세서로 작성해보았습니다. 이제 다음 주부터는 실제 API를 만들어보겠습니다.
'🗄️Backend > [GDSC] SpringBoot 프로젝트' 카테고리의 다른 글
Spring Boot 단기 프로젝트: 6. Make API(Auth, Circle) (0) | 2024.02.08 |
---|---|
Spring Boot 단기 프로젝트: 5. Make API(Profile, Posts) (0) | 2024.02.08 |
Spring Boot 단기 프로젝트: 4. Connect to GCP Cloud Storage & make Dto (0) | 2024.02.08 |
Spring Boot 단기 프로젝트: 2. Build Entity & Connect to GCP Cloud SQL (0) | 2024.02.08 |
Spring Boot 단기 프로젝트: 1. Build RDB & Class Diagram (1) | 2024.02.08 |