블로그 기획하고 API 만들기 (1)

·2023년 12월 2일
0

Spring Boot

목록 보기
8/21
post-thumbnail

💪 이제 실전이다!

스프링 부트에 대한 사전 지식은 어느 정도 공부했으니 이제 블로그 개발을 구현해 볼 것이다. 그럼 가 보자고! 👏


👏 블로그 기획하고 API 만들기 그 첫 번째

1. 엔티티 구성

컬럼명자료형null 허용설명
idBIGINTN기본키일련번호, 기본키
titleVARCHAR(255)N게시물의 제목
contentVARCHAR(255)N내용

만들 엔티티와 매핑되는 테이블 구조는 위와 같다.

Article.java

package me.ansoohyeon.springbootdeveloper.domain;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity // 엔티티로 지정
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class Article {

    @Id // id 필드를 기본키로 지정
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 기본키를 자동으로 1씩 증가
    @Column(name = "id", updatable = false)
    private Long id;

    @Column(name = "title", nullable = false) // 'title'이라는 not null 컬럼과 매핑
    private String title;

    @Column(name = "content", nullable = false)
    private String content;

    @Builder // 빌더 패턴으로 객체 생성
    public Article(String title, String content){
        this.title = title;
        this.content = content;
    }
}

@Builder 어노테이션이란?

롬복에서 지원하는 어노테이션으로, 이 어노테이션을 생성자 위에 입력하면 빌더 패턴 방식으로 객체를 생성할 수 있어 편리하다.
빌더 패턴을 사용하면 객체를 유연하고 직관적으로 생성할 수 있기 때문에 개발자들이 애용하는 디자인 패턴이다. 즉, 빌더 패턴을 사용하면 어느 필드에 어떤 값이 들어가는지 명시적으로 파악할 수 있다.

// 빌더 패턴을 사용하지 않았을 때
new Article("abc", "def");
// 빌더 패턴을 사용했을 때
Article.builder()
	.title("abc")
    .content("def")
    .build();

1-1. 레포지토리 생성

이제 레포지토리를 만들어 준다.

BlogRepository.java

package me.ansoohyeon.springbootdeveloper.repository;

import me.ansoohyeon.springbootdeveloper.domain.Article;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BlogRepository extends JpaRepository<Article, Long> {
}

2. 블로그 글 작성을 위한 API 구현

전체적인 구조는 이러하다. (빨간 줄은 일단 무시.. 😅)

2-1. 서비스 메소드 코드 작성

AddArticleRequest.java

package me.ansoohyeon.springbootdeveloper.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import me.ansoohyeon.springbootdeveloper.domain.Article;

@NoArgsConstructor
@AllArgsConstructor
@Getter
public class AddArticleRequest {

    private String title;
    private String content;

    public Article toEntity(){ // 생성자를 사용해 객체 생성
        return Article.builder()
                .title(title)
                .content(content)
                .build();
    }

}

BlogService.java

package me.ansoohyeon.springbootdeveloper.service;

import lombok.RequiredArgsConstructor;
import me.ansoohyeon.springbootdeveloper.domain.Article;
import me.ansoohyeon.springbootdeveloper.dto.AddArticleRequest;
import me.ansoohyeon.springbootdeveloper.repository.BlogRepository;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor // final이 붙거나 @NotNull이 붙은 필드의 생성자 추가
@Service
public class BlogService {

    private final BlogRepository blogRepository;

    // 블로그 글 추가 메소드
    public Article save(AddArticleRequest request){
        return blogRepository.save(request.toEntity());
    }

}

2-2. 컨트롤러 메소드 코드 작성

BlogApiController.java

package me.ansoohyeon.springbootdeveloper.controller;

import lombok.RequiredArgsConstructor;
import me.ansoohyeon.springbootdeveloper.domain.Article;
import me.ansoohyeon.springbootdeveloper.dto.AddArticleRequest;
import me.ansoohyeon.springbootdeveloper.service.BlogService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@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);
    }
}

2-3. API 실행 테스트

application.yml (들여쓰기 중요!!!)

spring:
  jpa:
    # 전송 쿼리 확인
    show-sql: true
    properties:
      hibernate:
        format_sql: true

    # 테이블 생성 후에 data.sql 실행
    defer-datasource-initialization: true

  datasource:
    url: jdbc:h2:mem:testdb // H2 콘솔 활성화

  h2:
    console:
      enabled: true

이제 스프링 부트 서버를 실행하고, 포스트맨으로 가 보자. 포스트맨에서 POST 방식으로 아까 지정한 주소 (http://localhost:8080/api/articles)를 입력한다. 그리고 Body 탭에서 raw - JSON을 선택하고 아래와 같이 작성한다.

{
	"title" : "제목",
    "content" : "내용"
}

그리고 'Send' 버튼을 클릭하면 응답 값이 반환된다. 데이터가 제대로 삽입된 것 같다! H2 데이터베이스에 잘 저장되었는지 확인해 보자.

웹 브라우저에서 http://localhost:8080/h2-console 에 접속해 보자. JDBC URL을 jdbc:h2:mem:testdb로 입력 후 Connect 한다. 그러면 스프링 부트 서버 안에 내장되어 있는 H2 데이터베이스에 접속하고 데이터를 확인할 수 있다.

전체 조회하는 SQL문을 입력하고 Run을 클릭하면 정말로 아까 삽입한 데이터가 제대로 들어가 있는 모습이다! (개인적으로 직접 DML을 입력하던 경험이 많은 나로서 너무 신기했음 👀)

이처럼 애플리케이션을 실행하면 자동으로 생성한 엔티티 내용을 바탕으로 테이블이 생성되고, 내가 요청한 POST 요청에 의해 데이터가 실제로 저장된 것을 볼 수 있다.

2-4. 테스트 코드 작성

이제 반복 작업을 줄여 줄 테스트 코드를 작성해 보자.

BlogApiController의 클래스 이름에 마우스를 놓고 Alt+Enter로 테스트를 생성한다. 그리고 테스트할 코드를 작성해 준다.

BlogApiControllerTest.java

package me.ansoohyeon.springbootdeveloper.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import me.ansoohyeon.springbootdeveloper.domain.Article;
import me.ansoohyeon.springbootdeveloper.dto.AddArticleRequest;
import me.ansoohyeon.springbootdeveloper.repository.BlogRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.util.List;

@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();
    }

    @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);
    }
}

테스트 코드를 실행해 보니 코드가 잘 동작하는 것 같다! 👌


다음 포스팅에서는 블로그 글 목록 조회를 위한 API를 구현해 볼 것이다.

profile
풀스택 개발자 기록집 📁

0개의 댓글