Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# 우정욱 게시판 CRUD 과제


## TODO List

<hr>

- [x] 4계층 패키지 구조 생성
- [x] 필수 Entity 구현
- [x] 공통 응답 구현
- [x] Pagination 처리 구현


- [x] DTO 및 Mapper 구현
- [x] 게시글, 댓글 작성 및 응답 DTO 구현
- [x] 게시글, 댓글 Mapper 구현

<hr>

- [x] 게시글 작성 API 구현
- [x] 게시글 작성 서비스 구현
- [x] 댓글 작성 서비스 구현

<hr>

- [x] 게시글 조회 API 구현
- [x] 게시글 단건 조회 구현
- [x] 게시글 단건 조회 시 해당 게시글에 대한 댓글 조회
- [x] 댓글 응답 시 Pagination 처리
- [x] 요청 Page Size 가 10/30/50이 아닌 경우 10으로 처리
- [x] 게시글 조회 서비스 구현

<hr>

- [x] 게시글 수정 API 구현
- [x] 댓글 수정 API 구현
- [x] 게시글 수정 서비스 구현

<hr>

- [x] 게시글 삭제 API 구현
- [x] 게시글 삭제 서비스 구현
- [x] 댓글 삭제 API 구현

<hr>

- [x] 글로벌 예외 처리 구현
- [x] 게시글 조회 시 예외 처리 구현
- [x] 삭제된 게시글 조회 시 예외 처리
- [x] 게시글 수정 시 예외 케이스 구현
- [x] 제목, 내용이 비어있다면 예외 처리
- [x] 삭제된 게시글에 대한 예외 처리
- [x] 댓글 수정 시 예외 케이스 구현
- [x] 내용이 비어있다면 예외 처리
- [x] 삭제된 댓글에 대한 예외 처리
- [x] 게시글 삭제 시 예외 케이스 구현
- [x] 게시글이 이미 삭제된 경우 예외 처리
- [x] 존재하지 않는 게시글 삭제 요청 시 예외 처리

<hr>

## 구현 시 어렵거나 이해가 되지 않는 부분
- soft-delete 구현 시, @SQLDelete 와 BaseEntity 에서 delete()를 호출하는 것 중 효율적인 방법과 추가적인 방법이 있는지
- soft-delete 처리 및 데이터 조회 시, @Query 를 사용하여 필터하는 것과 조회 후 예외 처리 중 효율적인 것은 어떤 것인지
- Page 처리 시, CommentRepository 에서 Page 조회를 하였는데, Post Entity 에 존재하는 List 와 중복되어 메모리가 낭비되지는 않는지
- 아직 이론적인 부분이 약하다고 생각함
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
9 changes: 5 additions & 4 deletions src/main/java/io/sparta/board/BoardApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing
public class BoardApplication {

public static void main(String[] args) {
SpringApplication.run(BoardApplication.class, args);
}

public static void main(String[] args) {
SpringApplication.run(BoardApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.sparta.board.application.dto.comment;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class CommentRequestInternalDto {

private String content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.sparta.board.application.dto.comment;

import lombok.Builder;
import lombok.Getter;

import java.time.LocalDateTime;
import java.util.UUID;

@Getter
@Builder
public class CommentResponseInternalDto {

private UUID id;
private LocalDateTime createdAt;
private String content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.sparta.board.application.dto.post;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class PostInternalDto {

private String title;
private String content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.sparta.board.application.dto.post;

import io.sparta.board.application.dto.comment.CommentResponseInternalDto;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.domain.Page;

import java.time.LocalDateTime;
import java.util.UUID;

@Getter
@Builder
public class PostResponseInternalDto {

private UUID id;
private LocalDateTime createAt;
private String title;
private String content;
private Page<CommentResponseInternalDto> comments;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.sparta.board.application.mapper;

import io.sparta.board.application.dto.comment.CommentResponseInternalDto;
import io.sparta.board.application.dto.comment.CommentRequestInternalDto;
import io.sparta.board.domain.model.Comment;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;

import java.util.List;
import java.util.stream.Collectors;

public class CommentMapper {

public static Comment toEntity(CommentRequestInternalDto request) {

return Comment.builder()
.content(request.getContent())
.build();
}

public static CommentResponseInternalDto toResponse(Comment comment) {

return CommentResponseInternalDto.builder()
.id(comment.getId())
.createdAt(comment.getCreatedAt())
.content(comment.getContent())
.build();
}

public static Page<CommentResponseInternalDto> toResponsePage(Page<Comment> comments) {

List<CommentResponseInternalDto> dtoList = comments.stream()
.filter(comment -> !comment.getDeleted())
.map(CommentMapper::toResponse)
.collect(Collectors.toList());

return new PageImpl<>(dtoList, comments.getPageable(), comments.getTotalElements());
}
}
39 changes: 39 additions & 0 deletions src/main/java/io/sparta/board/application/mapper/PostMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.sparta.board.application.mapper;

import io.sparta.board.application.dto.post.PostInternalDto;
import io.sparta.board.application.dto.post.PostResponseInternalDto;
import io.sparta.board.domain.model.Comment;
import io.sparta.board.domain.model.Post;
import org.springframework.data.domain.Page;

public class PostMapper {

public static Post toEntity(PostInternalDto dto) {

return Post.builder()
.title(dto.getTitle())
.content(dto.getContent())
.build();
}

public static PostResponseInternalDto toResponse(Post post) {

return PostResponseInternalDto.builder()
.id(post.getId())
.createAt(post.getCreatedAt())
.title(post.getTitle())
.content(post.getContent())
.build();
}

public static PostResponseInternalDto toResponseWithComments(Post post, Page<Comment> commentPage) {

return PostResponseInternalDto.builder()
.id(post.getId())
.createAt(post.getCreatedAt())
.title(post.getTitle())
.content(post.getContent())
.comments(CommentMapper.toResponsePage(commentPage))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.sparta.board.application.service;

import io.sparta.board.application.dto.comment.CommentRequestInternalDto;
import io.sparta.board.application.dto.comment.CommentResponseInternalDto;
import io.sparta.board.application.mapper.CommentMapper;
import io.sparta.board.domain.model.Comment;
import io.sparta.board.domain.model.Post;
import io.sparta.board.domain.service.CommentDomainService;
import io.sparta.board.domain.service.PostDomainService;
import io.sparta.board.infrastructure.exception.DeletedEntityException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.UUID;

@Service
@RequiredArgsConstructor
public class CommentService {

private final CommentDomainService commentDomainService;

private final PostDomainService postDomainService;


@Transactional
public CommentResponseInternalDto createComment(UUID postId, CommentRequestInternalDto request) {

Post post = postDomainService.findPostById(postId);

if(post.getDeleted()) {
throw new DeletedEntityException("삭제된 게시글 입니다.");
}

Comment comment = CommentMapper.toEntity(request);

comment.addPost(post);

commentDomainService.createComment(comment);

return CommentMapper.toResponse(comment);
}

@Transactional
public CommentResponseInternalDto updateComment(UUID commentId, CommentRequestInternalDto request) {

Comment comment = commentDomainService.findCommentById(commentId);

if(comment.getDeleted()) {
throw new DeletedEntityException("삭제된 댓글입니다.");
}

comment.update(request.getContent());

commentDomainService.update(comment);

return CommentMapper.toResponse(comment);
}

@Transactional
public CommentResponseInternalDto deleteCommentById(UUID commentId) {

Comment comment = commentDomainService.findCommentById(commentId);

if(comment.getDeleted()) {
throw new DeletedEntityException("이미 삭제된 댓글입니다.");
}

commentDomainService.deleteById(commentId);

return CommentMapper.toResponse(comment);
}
}
80 changes: 80 additions & 0 deletions src/main/java/io/sparta/board/application/service/PostService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package io.sparta.board.application.service;

import io.sparta.board.application.dto.post.PostInternalDto;
import io.sparta.board.application.dto.post.PostResponseInternalDto;
import io.sparta.board.application.mapper.PostMapper;
import io.sparta.board.domain.model.Comment;
import io.sparta.board.domain.model.Post;
import io.sparta.board.domain.service.CommentDomainService;
import io.sparta.board.domain.service.PostDomainService;
import io.sparta.board.infrastructure.exception.DeletedEntityException;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.UUID;

@Service
@RequiredArgsConstructor
public class PostService {

private final PostDomainService postDomainService;

private final CommentDomainService commentDomainService;

@Transactional
public PostResponseInternalDto createPost(PostInternalDto internalRequest) {

Post post = PostMapper.toEntity(internalRequest);

postDomainService.createPost(post);

return PostMapper.toResponse(post);
}

@Transactional(readOnly = true)
public PostResponseInternalDto findPostById(UUID postId, Pageable pageable) {

Post post = postDomainService.findPostById(postId);

if(post.getDeleted()) {
throw new DeletedEntityException("삭제된 게시글입니다.");
}

Page<Comment> commentPage = commentDomainService.findAllByPost(post, pageable);

return PostMapper.toResponseWithComments(post, commentPage);
}

@Transactional
public PostResponseInternalDto updatePost(UUID postId, PostInternalDto request) {

Post post = postDomainService.findPostById(postId);

if(post.getDeleted()) {
throw new DeletedEntityException("삭제된 게시글 입니다.");
}

post.update(request.getTitle(), request.getContent());

postDomainService.updatePost(post);

return PostMapper.toResponse(post);
}

@Transactional
public PostResponseInternalDto deletePostById(UUID postId) {

Post post = postDomainService.findPostById(postId);

if(post.getDeleted()) {
throw new DeletedEntityException("이미 삭제된 게시글 입니다.");
}

postDomainService.deletePostById(postId);

return PostMapper.toResponse(post);
}
}
Loading