ADD Comment domain, ADD Article.existsById()

- comment 테스트용 DB DML 추가
- Article 모듈에 existsById() 추가
This commit is contained in:
JiwonDev
2021-10-08 15:23:37 +09:00
committed by Jiwon
parent 21e6162d11
commit feadb3f255
18 changed files with 513 additions and 2 deletions

View File

@@ -7,4 +7,6 @@ public interface ArticleReader {
Article findByTitle(String title);
Optional<Article> findById(Long articleId);
boolean existsById(Long articleId);
}

View File

@@ -8,9 +8,8 @@ import org.mybatis.spring.SqlSessionTemplate;
public final class MybatisArticleRepository implements ArticleReader, ArticleRepository {
private final SqlSessionTemplate template;
private static final String SAVE_FQCN = "com.yam.app.article.domain.ArticleRepository.save";
private final SqlSessionTemplate template;
public MybatisArticleRepository(SqlSessionTemplate template) {
this.template = template;
@@ -34,4 +33,10 @@ public final class MybatisArticleRepository implements ArticleReader, ArticleRep
public Optional<Article> findById(Long articleId) {
return template.getMapper(ArticleReader.class).findById(articleId);
}
@Override
public boolean existsById(Long articleId) {
return template.getMapper(ArticleReader.class).existsById(articleId);
}
}

View File

@@ -0,0 +1,11 @@
package com.yam.app.comment.domain;
import com.yam.app.common.EntityNotFoundException;
public final class ArticleNotFoundException extends EntityNotFoundException {
public ArticleNotFoundException(Long id) {
super("Article could not be found, (id : %s)", id);
}
}

View File

@@ -0,0 +1,56 @@
package com.yam.app.comment.domain;
import static java.time.LocalDateTime.now;
import com.yam.app.common.EntityStatus;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Getter
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(of = "id")
public final class Comment {
private Long id;
private String content;
private LocalDateTime createdAt;
private LocalDateTime modifiedAt;
private EntityStatus status = EntityStatus.ALIVE;
private Long articleId;
private Long memberId;
public Comment(String content, LocalDateTime createdAt, LocalDateTime modifiedAt,
Long articleId, Long memberId) {
this.content = content;
this.createdAt = createdAt;
this.modifiedAt = modifiedAt;
this.articleId = articleId;
this.memberId = memberId;
}
public static Comment of(String content, Long articleId, Long memberId) {
return new Comment(content, now(), now(), articleId, memberId);
}
public void update(String content) {
this.content = content;
this.modifiedAt = now();
}
public void delete() {
this.status = EntityStatus.DELETED;
}
public boolean isAlive() {
return this.status == EntityStatus.ALIVE;
}
void setId(Long id) {
this.id = id;
}
}

View File

@@ -0,0 +1,11 @@
package com.yam.app.comment.domain;
import com.yam.app.common.EntityNotFoundException;
public final class CommentNotFoundException extends EntityNotFoundException {
public CommentNotFoundException(Long id) {
super("Comment could not be found, It may have been deleted.(id : %s)", id);
}
}

View File

@@ -0,0 +1,54 @@
package com.yam.app.comment.domain;
import com.yam.app.article.domain.ArticleReader;
public final class CommentProcessor {
CommentReader commentReader;
CommentRepository commentRepository;
ArticleReader articleReader;
public CommentProcessor(CommentReader commentReader,
CommentRepository commentRepository, ArticleReader articleReader) {
this.commentReader = commentReader;
this.commentRepository = commentRepository;
this.articleReader = articleReader;
}
public void create(String content, Long articleId, Long memberId) {
if (!articleReader.existsById(articleId)) {
throw new ArticleNotFoundException(articleId);
}
commentRepository.save(Comment.of(content, articleId, memberId));
}
public void update(String content, Long commentId) {
Comment comment = commentReader.findById(commentId)
.orElseThrow(() -> new CommentNotFoundException(commentId));
if (!articleReader.existsById(comment.getArticleId())) {
throw new ArticleNotFoundException(comment.getArticleId());
}
if (!comment.isAlive()) {
throw new CommentNotFoundException(commentId);
}
comment.update(content);
commentRepository.update(comment);
}
public void delete(Long commentId) {
Comment comment = commentReader.findById(commentId)
.orElseThrow(() -> new CommentNotFoundException(commentId));
if (!articleReader.existsById(comment.getArticleId())) {
throw new ArticleNotFoundException(comment.getArticleId());
}
comment.delete();
commentRepository.delete(comment);
}
}

View File

@@ -0,0 +1,13 @@
package com.yam.app.comment.domain;
import java.util.List;
import java.util.Optional;
public interface CommentReader {
Optional<Comment> findById(Long commentId);
List<Comment> findByArticleId(Long articleId);
boolean existsById(Long commentId);
}

View File

@@ -0,0 +1,11 @@
package com.yam.app.comment.domain;
public interface CommentRepository {
void save(Comment entity);
void update(Comment entity);
void delete(Comment entity);
}

View File

@@ -0,0 +1,29 @@
package com.yam.app.comment.infrastructure;
import com.yam.app.article.domain.ArticleReader;
import com.yam.app.comment.domain.CommentProcessor;
import com.yam.app.comment.domain.CommentReader;
import com.yam.app.comment.domain.CommentRepository;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CommentModuleConfiguration {
@Bean
public CommentReader commentReader(SqlSessionTemplate template) {
return new MybatisCommentRepository(template);
}
@Bean
public CommentRepository commentRepository(SqlSessionTemplate template) {
return new MybatisCommentRepository(template);
}
@Bean
public CommentProcessor commentProcessor(CommentReader commentReader,
CommentRepository commentRepository, ArticleReader articleReader) {
return new CommentProcessor(commentReader, commentRepository, articleReader);
}
}

View File

@@ -0,0 +1,63 @@
package com.yam.app.comment.infrastructure;
import com.yam.app.comment.domain.Comment;
import com.yam.app.comment.domain.CommentReader;
import com.yam.app.comment.domain.CommentRepository;
import java.util.List;
import java.util.Optional;
import org.mybatis.spring.SqlSessionTemplate;
public final class MybatisCommentRepository implements CommentReader, CommentRepository {
private static final String SAVE_FQCN = "com.yam.app.comment.domain.CommentRepository.save";
private static final String UPDATE_FQCN = "com.yam.app.comment.domain.CommentRepository.update";
private static final String DELETE_FQCN = "com.yam.app.comment.domain.CommentRepository.delete";
private final SqlSessionTemplate template;
public MybatisCommentRepository(SqlSessionTemplate template) {
this.template = template;
}
@Override
public void save(Comment entity) {
int result = template.insert(SAVE_FQCN, entity);
if (result != 1) {
throw new RuntimeException(
String.format("There was a problem saving the object : %s", entity));
}
}
@Override
public void update(Comment entity) {
int result = template.update(UPDATE_FQCN, entity);
if (result != 1) {
throw new RuntimeException(
String.format("There was a problem updating the object : %s", entity));
}
}
@Override
public void delete(Comment entity) {
int result = template.update(DELETE_FQCN, entity);
if (result != 1) {
throw new RuntimeException(
String.format("There was a problem soft deleting the object : %s", entity));
}
}
@Override
public Optional<Comment> findById(Long commentId) {
return template.getMapper(CommentReader.class).findById(commentId);
}
@Override
public List<Comment> findByArticleId(Long articleId) {
return template.getMapper(CommentReader.class).findByArticleId(articleId);
}
@Override
public boolean existsById(Long commentId) {
return template.getMapper(CommentReader.class).existsById(commentId);
}
}

View File

@@ -3,6 +3,12 @@
<mapper namespace="com.yam.app.article.domain.ArticleReader">
<select id="existsById" parameterType="Long" resultType="boolean">
SELECT COUNT(*)
FROM ARTICLE
WHERE id = #{id}
</select>
<select id="findByTitle" parameterType="String" resultMap="onlyArticle">
SELECT a.id AS article_id,
a.title AS article_title,

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yam.app.comment.domain.CommentRepository">
<insert id="save" parameterType="com.yam.app.comment.domain.Comment">
INSERT INTO COMMENT(content, created_at, modified_at, status, article_id, member_id)
VALUES (#{content}, #{createAt}, #{modifiedAt}, #{status}, #{articleId}, #{memberId})
</insert>
<update id="update" parameterType="com.yam.app.comment.domain.Comment">
UPDATE COMMENT
SET content = #{content},
modified_at = #{modifiedAt}
WHERE id = #{id}
</update>
<update id="delete" parameterType="com.yam.app.comment.domain.Comment">
UPDATE COMMNET
SET status = #{status}
WHERE id = #{id}
</update>
</mapper>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yam.app.comment.domain.CommentReader">
<select id="findById" parameterType="Long" resultType="com.yam.app.comment.domain.Comment">
SELECT *
FROM COMMENT
WHERE id = #{id}
</select>
<select id="findByArticleId" parameterType="Long" resultType="com.yam.app.comment.domain.Comment">
SELECT *
FROM COMMENT
WHERE article_id = #{articleId}
</select>
<select id="existsById" parameterType="Long" resultType="boolean">
SELECT COUNT(*)
FROM COMMENT
WHERE id = #{id}
</select>
</mapper>

View File

@@ -27,3 +27,6 @@ insert into article_tag(article_id, tag_id)
values (1, 1),
(1, 2),
(1, 3);
INSERT INTO comment(content, created_at, modified_at, status, article_id, member_id)
VALUES ('sample content1', now(), now(), 'ALIVE', 1, 1);

View File

@@ -30,4 +30,9 @@ public final class FakeArticleRepository implements ArticleRepository, ArticleRe
entity.setId(idGenerator.incrementAndGet());
data.put(entity.getId(), entity);
}
@Override
public boolean existsById(Long articleId) {
return data.containsKey(articleId);
}
}

View File

@@ -0,0 +1,95 @@
package com.yam.app.comment.domain;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import com.yam.app.article.domain.Article;
import com.yam.app.article.domain.FakeArticleRepository;
import java.util.Arrays;
import java.util.Collection;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
@DisplayName("댓글 작성 도메인 서비스")
final class CommentProcessorTest {
@TestFactory
@DisplayName("댓글 시나리오")
Collection<DynamicTest> comment_scenarios() {
final var fakeCommentRepository = new FakeCommentRepository();
final var fakeArticleRepository = new FakeArticleRepository();
final var processor = new CommentProcessor(fakeCommentRepository,
fakeCommentRepository, fakeArticleRepository);
final var memberId = 1L;
final var articleId = 1L;
final var commentId = 1L;
final var deletedCommentId = 2L;
final var wrongCommentId = 3L;
fakeArticleRepository.save(Article.write(1L, "title", "content", "image.png"));
fakeArticleRepository.save(Article.write(2L, "title", "content", "image.png"));
fakeCommentRepository.save(Comment.of("sample content", articleId, memberId));
fakeCommentRepository.save(Comment.of("deleted content", articleId, memberId));
fakeCommentRepository.save(Comment.of("article not found", 4321L, memberId));
return Arrays.asList(
DynamicTest.dynamicTest("댓글 작성에 성공한다.",
() -> {
// Act
processor.create("hello", 2L, memberId);
var comments = fakeCommentRepository.findByArticleId(2L);
// Assert
assertThat(comments.get(0).getId()).isEqualTo(4L);
}),
DynamicTest.dynamicTest("댓글 수정에 성공한다.",
() -> {
// Act
var modifiedAt = fakeCommentRepository.findById(commentId).get()
.getModifiedAt();
processor.update("new content", commentId);
var comment = fakeCommentRepository.findById(commentId).get();
// Assert
assertThat(comment.getContent()).isEqualTo("new content");
assertThat(comment.getModifiedAt().isAfter(modifiedAt)).isTrue();
}),
DynamicTest.dynamicTest("댓글 작성시 유효한 게시글이 존재하지 않는 경우 예외를 반환한다.",
() -> {
// Act & Assert
assertThatExceptionOfType(ArticleNotFoundException.class)
.isThrownBy(() -> processor.create("sample content", 1234L, memberId));
}),
DynamicTest.dynamicTest("댓글 수정시 유효한 게시글이 존재하지 않는 경우 예외를 반환한다.",
() -> {
// Act & Assert
assertThatExceptionOfType(ArticleNotFoundException.class)
.isThrownBy(() -> processor.update("new content", wrongCommentId));
}),
DynamicTest.dynamicTest("댓글 삭제시 유효한 게시글이 존재하지 않는 경우 예외를 반환한다.",
() -> {
// Act & Assert
assertThatExceptionOfType(ArticleNotFoundException.class)
.isThrownBy(() -> processor.delete(wrongCommentId));
}),
DynamicTest.dynamicTest("댓글 수정시 유효한 댓글이 존재하지 않는 경우 예외를 반환한다.",
() -> {
// Act & Assert
assertThatExceptionOfType(CommentNotFoundException.class)
.isThrownBy(() -> processor.update("new content", 1234L));
}),
DynamicTest.dynamicTest("삭제된 댓글을 수정하려고 하는 경우 예외를 반환한다.",
() -> {
// Act & Assert
processor.delete(deletedCommentId);
assertThatExceptionOfType(CommentNotFoundException.class)
.isThrownBy(
() -> processor.update("sample content", deletedCommentId));
})
);
}
}

View File

@@ -0,0 +1,53 @@
package com.yam.app.comment.domain;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
public final class FakeCommentRepository implements CommentRepository, CommentReader {
private final Map<Long, Comment> data = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong();
@Override
public Optional<Comment> findById(Long commentId) {
return data.values().stream()
.filter(c -> c.getId().equals(commentId))
.findAny();
}
@Override
public List<Comment> findByArticleId(Long articleId) {
return data.values().stream()
.filter(c -> c.getArticleId().equals(articleId))
.collect(Collectors.toList());
}
@Override
public boolean existsById(Long commentId) {
return data.containsKey(commentId);
}
@Override
public void save(Comment entity) {
entity.setId(idGenerator.incrementAndGet());
data.put(entity.getId(), entity);
}
@Override
public void update(Comment entity) {
var comment = data.get(entity.getId());
comment.update(entity.getContent());
data.put(entity.getId(), comment);
}
@Override
public void delete(Comment entity) {
var comment = data.get(entity.getId());
comment.delete();
data.put(entity.getId(), comment);
}
}

View File

@@ -0,0 +1,44 @@
package com.yam.app.comment.infrastructure;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import com.yam.app.comment.domain.CommentReader;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
@SpringBootTest
@ActiveProfiles("test")
@Disabled
final class MybatisCommentRepositoryTest {
@Autowired
CommentReader commentReader;
@Test
void findById() {
var comment = commentReader.findById(1L);
if (comment.isPresent()) {
assertThat(comment.get()).isNotNull();
} else {
fail("Comment Could not find");
}
}
@Test
void findByArticleId() {
var comments = commentReader.findByArticleId(1L);
assertThat(comments).extracting("articleId", Long.class)
.containsOnly(1L);
}
@Test
void existsById() {
var isPresent = commentReader.existsById(1L);
assertThat(isPresent).isTrue();
}
}