ADD Comment domain, ADD Article.existsById()
- comment 테스트용 DB DML 추가 - Article 모듈에 existsById() 추가
This commit is contained in:
@@ -7,4 +7,6 @@ public interface ArticleReader {
|
||||
Article findByTitle(String title);
|
||||
|
||||
Optional<Article> findById(Long articleId);
|
||||
|
||||
boolean existsById(Long articleId);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
56
src/main/java/com/yam/app/comment/domain/Comment.java
Normal file
56
src/main/java/com/yam/app/comment/domain/Comment.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
13
src/main/java/com/yam/app/comment/domain/CommentReader.java
Normal file
13
src/main/java/com/yam/app/comment/domain/CommentReader.java
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
25
src/main/resources/mapper/xml/CommentCommandMapper.xml
Normal file
25
src/main/resources/mapper/xml/CommentCommandMapper.xml
Normal 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>
|
||||
25
src/main/resources/mapper/xml/CommentQueryMapper.xml
Normal file
25
src/main/resources/mapper/xml/CommentQueryMapper.xml
Normal 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>
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user