0. API와 REST API
API란?
네트워크에서 API는 프로그램 간에 상호작용하기 위한 매개체를 말한다.
예를 들어, 우리가 웹 사이트에 방문하는 상황에 적용해서 생각을 해보면,
웹 사이트 주소를 입력해서 '구글 메인 화면을 보여줘'라고 요청을 하면 API는 이 요청을 받아서 서버에 가져다준다.
그러면 서버는 API가 준 요청을 처리해 결과물을 만들고 이것을 다시 API로 전달한다.
그러면 API는 최종 결과물을 브라우저에 보내주고 우리는 화면을 볼 수 있게 되는 구조이다.
REST API란?
REST API는 웹의 장점을 최대한 활용하는 API이다, "URL의 설계 방식"
REST : Representational State Transfer -> 자원을 이름으로 구분해 자원의 상태를 주고받는 API 방식.
REST API의 특징
- Statelessness (무상태성):
서버는 각 클라이언트 요청을 별개의 독립적인 것으로 취급하며, 이전 요청의 정보를 저장하지 않는다.
모든 필요한 정보는 각 요청에 포함되어야 합니다. - Client-Server Architecture (클라이언트-서버 구조):
클라이언트와 서버가 분리되어 있으며, 클라이언트는 사용자 인터페이스를 담당하고 서버는 데이터 저장 및 처리 로직을 담당한다. - Uniform Interface (인터페이스 일관성):
API 설계는 일관된 방식으로 이루어지며, 자원(Resource)에 대한 접근은 표준화된 URI를 통해 이루어진다.
HTTP 메서드(GET, POST, PUT, DELETE 등)를 사용하여 자원을 조작한다. - Cacheable (캐시 가능성):
응답은 캐시가 가능해야 하며, 이를 통해 클라이언트 성능을 향상할 수 있다.
서버 응답은 캐시 가능 여부를 명시한다. - Layered System (계층화 시스템):
API는 여러 계층으로 구성될 수 있으며, 클라이언트는 중간 서버와 직접 통신하는지 여부를 알지 못한다.
이를 통해 시스템 확장성 및 보안이 향상된다.
REST API의 장점과 단점
장점
- URL만 보고도 무슨 행동을 하는 API인지 명확하게 알 수 있다
- 무상태성의 특징으로 클라이언트와 서버의 역할이 명확하게 분리된다.
- HTTP 표준을 사용하는 모든 플랫폼에서 사용할 수 있다.
단점
- HTTP 메서드, 즉, GET, POST와 같은 방식의 개수에 제한이 있다
- 설계를 하기 위해 공식적으로 제공되는 표준 규약이 없다
그럼에도 REST API는 주소와 메서드만 보고 요청의 내용을 파악할 수 있다는 강력한 장점이 있어 많은 개발자가 사용한다.
REST API를 사용하는 방법
규칙 1. URL에는 동사를 쓰지 말고, 자원을 표시해야 한다
URL 설계 예
- /student/1 - 적합
- get-student?student_id=1 - 부적합
규칙 2. 동사는 HTTP 메서드로
주로 사용하는 HTTP 메서드는 POST, GET, PUT, DELETE이다.
각각 만들고, 읽고, 업데이트하고, 삭제하는 역할을 담당하며 이를 묶어서 CRUD라고 부른다.
설명 | 적합한 HTTP 메서드와 URL |
id가 1인 블로그 글을 조회하는 API | GET/article/1 |
블로그 글을 추가하는 API | POST/articles |
블로그 글을 수정하는 API | PUT/articles/1 |
블로그 글을 삭제하는 API | DELETE/articles/1 |
1. 블로그 개발을 위한 엔티티 구성하기
1-1. build.gradle 세팅
plugins {
id 'java'
id 'org.springframework.boot' version '3.0.2'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'me.jhzlo'
version = '1.0-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// 스프링 데이터 JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2' // 인메모리 데이터베이스
compileOnly 'org.projectlombok:lombok' // 롬복
annotationProcessor 'org.projectlombok:lombok'
}
test {
useJUnitPlatform()
}
1-2. 엔티티 구성하기
컬럼명 | 자료형 | null 허용 | 키 | 설명 |
id | BIGINT | N | 기본키 | 일렬번호, 기본키 |
title | VARCHAR(255) | N | 게시물의 제목 | |
content | VARCHAR(255) | N | 내용 |
2. 퍼시스턴스 계층 구성
2-1. Article.java
DB에 접근할 때, 사용할 객체인 Article DAO를 생성하고 실제 DB에 접근하도록 코드 작성
DAO : 데이터베이스와 연결되고 데이터를 조회하고 수정하는 데 사용하는 객체
다음과 같이 springbootdeveloper 하위 경로에 domain 패키지를 생성하고 Article.java를 생성한다.
@Entity // 엔티티로 지정
public class Article {
@Id // id 필드를 기본키로 지정
@GeneratedValue(strategy = GenerationType.IDENTITY) // 기본키를 자동으로 1씩 증가
@Column(name = "id" , updatable = false)
private Long id;
@Column(name= "title", nullable = false)
private String title;
@Column(name="content", nullable = false)
private String content;
@Builder // 빌더 패턴으로 객체 생성
public Article(String title, String content){
this.title = title;
this.content = content;
}
protected Article() {// 기본생성자
}
// 게터
public Long getId(){
return id;
}
public String getTitle(){
return title;
}
public String getContent(){
return content;
}
}
- @Builder
롬복에서 지원하는 애너테이션,
생성자 위에 입력하면 빌더 패턴 방식으로 객체를 생성할 수 있다.
빌더 패턴이란?
객체를 유연하고 직관적으로 생성할 수 있는 패턴이다.
>> 어느 필드에 어떤 값이 들어가는지 명시적으로 파악할 수 있다.
// 빌더 패턴을 사용하지 않았을 때
new Article("abc", "def");
// 빌더 패턴을 사용했을 때
Article.builder()
.title("abc")
.content("def")
.build();
예를 들어, Article 객체를 생성할 때 title에는 abc를, content에는 def값으로 초기화한다고 가정
빌더 패턴을 사용하지 않으면 abc는 어느 필드에 들어가는 값인지, def는 어느 필드에 들어가는 값인지 파악하기 어렵다.
하지만, 빌더패턴을 사용하면 어느 필드에 어느 값이 매칭되는지 바로 보이므로 객체 생성 코드의 가독성이 높다.
- 롬복 사용하기
롬복 @Getter 애너테이션을 사용하지 않으면 게터 메서드들을 정의해줘야 한다.
롬복 @NoArgsConstructor 애너테이션을 사용함으로써 기본 생성자를 정의하지 않아도 된다.
// 기본 생성자
protected Artilce(){
}
// 게터
public Long getId(){
return id;
}
public String getTitle(){
return title;
}
public String getContent(){
return content;
}
이러한 부분을 롬복 애너테이션을 사용함으로써
@Entity // 엔티티로 지정
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Article {
@Id // id 필드를 기본키로 지정
@GeneratedValue(strategy = GenerationType.IDENTITY) // 기본키를 자동으로 1씩 증가
@Column(name = "id" , updatable = false)
private Long id;
@Column(name= "title", nullable = false)
private String title;
@Column(name="content", nullable = false)
private String content;
@Builder // 빌더 패턴으로 객체 생성
public Article(String title, String content){
this.title = title;
this.content = content;
}
}
위와 같이 코드를 반복해 입력할 필요가 없어져서 가독성이 향상된다.
2-2. BlogRepository.java
Article이라는 table에 접근하여 Article이라는 클래스에 매핑하는 구현체
위와 같이, repository 패키지를 생성하고 BlogRepository.java를 생성한다.
public interface BlogRepository extends JpaRepository<Article, Long> {
}
JpaRepository 클래스를 상속받을 때 엔티티 Article과 엔티티의 PK 타입 Long을 인수로 넣어준다.
>> JpaRepository에서 제공하는 여러 메서드를 사용할 수 있다.
3. 블로그 글 작성 API 구현
- 컨트롤러 : BlogController.java
- 서비스 : BlogService.java
- 리포지토리 : BlogRepository.java
3-1. 서비스 메서드 코드 작성하기
먼저 블로그에 글을 추가하는 코드를 서비스 계층에 작성할 것이다.
서비스 계층에서 요청을 받을 객체인 AddArticleRequest 객체를 생성하고,
BlogService 클래스를 생성한 다음에 블로그 글 추가 메서드인 save()를 구현할 것이다.
3-1-1. AddArticleRequest.java
@NoArgsConstructor // 기본 생성자 추가
@AllArgsConstructor // 모든 필드 값을 파라미터로 받는 생성자 추가
@Getter
public class AddArticleRequest {
private String title;
private String content;
public Article toEntity(){
return Article.builder()
.title(title)
.content(content)
.build();
}
}
dto 패키지를 컨트롤러에서 요청한 본문을 받을 객체인 AddArticleRequest.java 파일을 생성한다.
DTO: 계층끼리 데이터를 교환하기 위해 사용하는 객체, 단순하게 데이터를 옮기기 위해 사용하는 전달자
toEntity()는 빌더 패턴을 사용해 DTO를 엔티티로 만들어주는 메서드이다.
이 메서드는 추후에 블로그 글을 추가할 때 저장할 엔티티로 변환하는 용도로 사용할 것이다.
3-1-2. BlogService.java
@RequiredArgsConstructor // final이 붙거나 @NotNull이 붙은 필드의 생성자 추가
@Service // 빈으로 등록
public class BlogService {
private final BlogRepository blogRepository;
// 블로그 글 추가 메서드
public Article save(AddArticleRequest request){
return blogRepository.save(request.toEntity());
}
}
- @RequiredArgsConstructor
빈을 생성자로 생성하는 롬복에서 지원하는 애너테이션,
final 키워드나 @NotNull이 붙은 필드로 생성자를 만들어준다. - @Service
해당 클래스를 빈으로 서블릿 컨테이너에 등록해 준다 - save() 메서드
JpaRepository에서 지원하는 저장 메서드 save()로 AddArticleRequest 클래스에 저장된 값들을 artivle 데이터베이스에 저장한다.
BlogRepository의 구성
CrudRepository -(상속)-> JpaRepository -(상속)-> BlogRepository
CrudRepository에 save()가 선언되어 있다.
3-2. 컨트롤러 메서드 코드 작성하기
URL에 매핑하기 위한 컨트롤러 메서드를 추가할 것이다.
컨트롤러 메서드에는 URL 매핑 애너테이션
@GetMapping, @PostMapping, @PutMapping, @DeleteMapping 등을 사용할 수 있다.
블로그 글을 생성하는 api를 생성하기 위해서는
/api/articles에 POST 요청이 오면 @PostMapping을 이용해 요청을 매핑한 뒤,
블로그 글을 생성하는 BlogService의 save() 메서드를 호출한 뒤,
생성된 블로그 글을 반환하는 작업을 할 addArticle() 메서드를 작성할 것이다.
3-2-1. BlogApiController.java
controller 패키지를 생성하고 BlogApiContorller.java를 생성한다.
@RequiredArgsConstructor
@RestController // HTTP Response Body에 객체 데이터를 JSON 형식으로 반환하는 컨트롤러
public class BlogApiController {
private final BlogService blogService;
// HTTP 메서드가 POST일 때 전달받은 URL과 동일하면 메서드로 매핑
@PostMapping("api/articles")
// @RequestBody로 요청 본문 값 매핑
public ResponseEntity<Article> addArticle(@RequestBody AddArticleRequest request) {
Article savedArticle = blogService.save(request);
// 요청한 자원이 성공적으로 생성되었으며 저장된 블로그 글 정보를 응답 객체에 담아 전송
return ResponseEntity.status(HttpStatus.CREATED)
.body(savedArticle);
}
}
- @RestController
해당 애너테이션을 클래스에 붙이면 HTTP 응답으로 객체 데이터를 Json 형식으로 반환한다. - @PostMapping()
HTTP 메서드가 POST일 때 요청받은 URL과 동일한 메서드와 매핑한다.
위의 코드의 경우 api/articles의 URL을 addArticle() 메서드에 매핑한다. - @RequestBody
HTTP를 요청할 때 응답에 해당하는 값을 @RequestBodty 애너테이션이 붙은 대상 객체인
AddArticleRequest에 매핑한다. - 비즈니스 계층에서 정의하였던 save() 메서드를 savedArticle에 정의하고,
블로그 글 정보를 응답 객체에 담아 전송한다.
4. API 실행 테스트
4-1. application.yml 설정
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
defer-datasource-initialization: true
datasource:
url: jdbc:h2:mem:testdb
h2:
console:
enabled: true
4-2. API 테스트
서버를 우선 실행시키고,
포스트맨을 실행하고 위와 같이 요청을 보낸다.
데이터를 확인하기 위해 h2-console을 이용할 것인데, 위와 같이 url을 입력한 후 connect를 눌러 로그인한다.
(JDBC URL은 jdbc:h2:mem:testdb로 수정해 준다.)
그리고 다음과 같이 select문을 활용하여 조회하면 post로 서버에 요청한 값이 Article table에 들어갔음을 확인할 수 있다.
5. 테스트 코드 작성
BlogApiController 클래스에 alt+Enter를 누르고 Create Test를 눌러서 테스트 코드 파일을 생성한다.
@SpringBootTest // 테스트용 애플리케이션 컨텍스트
@AutoConfigureMockMvc // MockMvc 생성 및 자동 구성
class BlogApiControllerTest {
@Autowired
protected MockMvc mockMvc;
@Autowired
protected ObjectMapper objectMapper; // 직렬화, 역직렬화를 위한 클래스
@Autowired
private WebApplicationContext context;
@Autowired
BlogRepository blogRepository;
@BeforeEach // 테스트 실행 전 실행하는 메서드
public void mockMvcSetup(){
this.mockMvc = MockMvcBuilders.webAppContextSetup(context)
.build();
blogRepository.deleteAll();
}
}
- ObjectMapper
클래스로 만든 객체는 자바 객체를 JSON 데이터로 변환하는 직렬화
또는 반대로 JSON 데이터를 자바에서 사용하기 위해 자바 객체로 변환하는 역직렬화할 때 사용하는 클래스이다.
블로그 글 생성 API를 테스트하는 코드 작성하기
Given | 블로그 글 추가에 필요한 요청 객체를 만든다. |
When | 블로그 글 추가 API에 요청을 보낸다. 이때 요청 타입은 JSON이며, given 절에서 미리 만들어둔 객체를 요청 본문으로 함께 보낸다. |
Then | 응답 코드가 201 Created인지 확인한다. Blog를 전체 조회해 크기가 1인지 확인하고, 실제로 저장된 데이터와 요청 값을 비교한다. |
@DisplayName("addArticle: 블로그 글 추가에 성공한다.")
@Test
public void addArticle() throws Exception{
// given
final String url = "/api/articles";
final String title = "title";
final String content = "content";
final AddArticleRequest userRequest = new AddArticleRequest(title, content);
// 객체 JSON으로 직렬화
final String requestBody = objectMapper.writeValueAsString(userRequest);
// when
// 설정한 내용을 바탕으로 요청 전송
ResultActions result = mockMvc.perform(post(url)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.content(requestBody));
// then
result.andExpect(status().isCreated());
List<Article> articles = blogRepository.findAll();
assertThat(articles.size()).isEqualTo(1); // 크기가 1인지 검증
assertThat(articles.get(0).getTitle()).isEqualTo(title);
assertThat(articles.get(0).getContent()).isEqualTo(content);
}
- writeValueAsString() 메서드를 사용해서 객체를 JSON으로 직렬화해준다.
- MockMvc를 사용해 HTTP 메서드, URL, 요청 본문, 요청 타입 등을 설정한 뒤 설정한 내용을 바탕으로 테스트 요청을 보낸다.
- assertThat() 메서드로 블로그 글의 개수가 1인지 확인한다.
테스트 코드가 잘 작동되는지 확인한다.
<다음 글>
https://jhzlo.tistory.com/31
'JAVA > SpringBoot 3' 카테고리의 다른 글
[스프링 부트 3] 템플릿 엔진, 타임리프란? (0) | 2024.08.12 |
---|---|
[스프링 부트 3] 블로그 만들기 - 2 (글 목록 조회 / 단일 조회 / 삭제 / 수정 API 구현) (0) | 2024.08.08 |
[스프링 부트 3] ORM, JPA, 하이버네이트 (0) | 2024.07.30 |
[스프링 부트 3] 테스트 코드란? / 테스트 코드 작성하기 (0) | 2024.07.29 |
[스프링 부트 3] 구조 이해하기 (프레젠테이션, 비즈니스, 퍼시스턴스 계층) (0) | 2024.07.25 |