안녕하세요. 오늘부터는 스프링 부트를 활용하여 블로그 API를 만들어 보도록 하겠습니다.

개발 환경은 아래와 같습니다.

  • 스프링부트 2.4.5
  • spring-boot-starter-web
  • spring-data-jap, h2, mysql
  • validation, lombok
  • junit5

1. application.yml 파일에 database 정보 입력하기

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/board-rest?autoReconnect=true&useUnicode=true&serverTimezone=UTC&characterEncoding=UTF8
    username: root
    password: ghd9413
    driver-class-name: com.mysql.cj.jdbc.Driver

  jpa:
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        show_sql: true
        format_sql: true
        use_sql_comments: true
  jackson:
    property-naming-strategy: SNAKE_CASE

저는 mysql로 진행하였습니다. 

property-naming-strategy는 기본값이 CamelCase인데 저는 SnakeCase를 선호하여 변경하였습니다.

 

2. 게시물 가져오기 API - Controller, Service, Repository, Entity, PostDTO

- controller

@RestController
@RequiredArgsConstructor
public class PostController {
    private final PostService postService;

    @GetMapping("post/{id}")
    @ResponseStatus(value = HttpStatus.OK)
    public SuccessResponse getPost(@PathVariable(name = "id") Long id) {
        PostDTO post = postService.getPost(id);

        return SuccessResponse.success(post);
    }
}

 

- service

@Service
@RequiredArgsConstructor
public class PostService {
    private final PostRepository postRepository;

    public PostDTO getPost(Long postId) {
        Optional<Post> byId = postRepository.findById(postId);

        Post findPost = byId.orElseThrow(() -> new PostNotFound("해당 포스트가 존재하지 않습니다."));

        return PostDTO.builder()
                .id(findPost.getId())
                .title(findPost.getTitle())
                .contents(findPost.getContents())
                .createdAt(findPost.getCreateAt().toString())
                .build();
    }
 }

 

- repository

@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
}

 

- entity

@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    private String contents;

    private LocalDateTime createAt;
}

 

- postDTO

@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
public class PostDTO {
    @NotBlank
    private String title;
    @NotBlank
    private String contents;

    private Long viewCount;

    private String createdAt;
}

API를 만들기 앞서 클라이언트와 통신할때 사용할 PostDTO를 만들도록 하겠습니다.

Client - Controller - Service 간에는 PostDTO로 작업 하였습니다.

@NoArgsConstructor와 @AllArgsConstructor에 PROTECTED를 한 이유는 PostDTO를 만들 때 Builder를 사용할 것이기 때문에 생성자를 통한 생성은 막기 위함입니다.

title과 contents에 @NotBlank를 한 이유는 validation을 통해 값을 필수로 받기 위함입니다.

3. 게시물 등록 API - Controller, Service

-controller

 @PostMapping("/post")
    @ResponseStatus(value = HttpStatus.CREATED)
    public SuccessResponse createPost(@Valid @RequestBody PostDTO postDTO) {
        PostDTO post = postService.createPost(postDTO);

        return SuccessResponse.success(post);
    }

 

- service

public PostDTO createPost(PostDTO postDTO) {
        Post post = Post.builder()
                .title(postDTO.getTitle())
                .contents(postDTO.getContents())
                .createAt(LocalDateTime.now())
                .build();

        Post save = postRepository.save(post);

        PostDTO postDTOResponse = PostDTO.builder()
                .id(save.getId())
                .build();


        return postDTOResponse;
    }

 

4. Response 양식 만들기

- SuccessResponse

@Getter
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SuccessResponse<T> {
    private String message;
    private T data;

    public static <T> SuccessResponse success(T data) {

        SuccessResponse responseUtil = SuccessResponse.builder()
                .message("success")
                .data(data)
                .build();

        return responseUtil;

    }
}

- ErrorResponse

@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ErrorResponse {
    private String message;
    @Builder.Default
    private List<CustomError> errors = new ArrayList<>();

    public List<CustomError> addError(CustomError error) {
        this.errors.add(error);
        return this.errors;
    }
}

- CustomError

@Data
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CustomError {
    private String field;
    private String message;
}

Response할때 PostDTO를 바로 return해도 되지만 클라이언트와 통신을 생각하면 고정된 Response 양식이 필요하다고 생각하여 만들었습니다.

@JsonInclude(JsonInclude.Include.NON_NULL)은 필드중 null값이 있을경우 해당 필드는 제외하고 리턴합니다.

SuccessResponse의 경우 message에는 success가, data에는 결과 데이터가 들어가있으며

ErrorResponse의 경우 message에는 error가, erorrs에는 에러가 들어있습니다! 

errors를 Array로 한 이유는 게시글 등록처럼 @Valid가 달린경우 제목과 내용을 둘다 적지 않으면 에러가 2개이기 때문에 한번에 보여주고자 하기 위함입니다.

다음 시간에는 테스트 코드를 작성하여 확인해보도록 하겠습니다.

진행 코드는 아래에서 확인할 수 있습니다.

sug5806/board-project-rest (github.com)

+ Recent posts