Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[댓글 기능] 효오 미션 제출합니다. #154

Merged
merged 34 commits into from
Aug 4, 2019

Conversation

hyojaekim
Copy link

No description provided.

hyojaekim and others added 19 commits July 30, 2019 18:39
* feat: thymeleaf 추가, html 파일 templates 디렉토리로 이동

* feat: Test 주석추가

* feat: ArticleController 추가

* feat: Article 클래스 추가

* feat: 게시글 수정, 삭제 기능 구현

* refactor: 코드 리팩토링

* refactor: 함수명 수정

* refactor: Controller 메서드명 수정

* refactor: 코드 리팩토링

* refactor: 매서드 위치 수정

* refactor: 리뷰 반영

* refactor: 리뷰 반영 : setter 메서드명 번경

* refactor: 리뷰 반영, ArticleRepository.class update 메서드 삭제

* refactor: 리뷰 반영, ArticleRepository.class 수정에 따른 테스트 코드 삭제

* refactor: 리뷰 반영, controller ModelAnvView 메서드 내부에서 수정

* refactor: 테스트 코드 수정

* refactor 피드백 반영

* refactor 피드백 반영
* feat: thymeleaf 추가, html 파일 templates 디렉토리로 이동

* feat: Test 주석추가

* feat: ArticleController 추가

* feat: Article 클래스 추가

* [DOCS] Add README.md

* [ADD] Add dependencies

* [ADD] Add properties

* [MOVE] Move views to templates directory

* [ADD] Add properties

* [FEATURE] Implement redirection to article writing page

* [FEATURE] Implement methods for read and create article

* [FEATURE] Implement methods for read article edit page

* [FEATURE] Implement methods for update article

* [FEATURE] implement methods for delete article

* feat: 게시글 수정, 삭제 기능 구현

* [FEATURE] Implement methods for read all articles

* [FEATURE] Apply template to article page

* refactor: 코드 리팩토링

* refactor: 함수명 수정

* refactor: Controller 메서드명 수정

* [REFACTOR] adjust put method to article edit page

- 메소드 타입에 따른 해당 메소드 뷰 적용

* [REFACTOR] replace response from rendering to redirection

* [ADD] Update css and html for bug fix

* [FEATURE] Apply delete specific article

* [REFACTOR] management role from Article to ArticleRepository

* [REFACTOR] update for redirection tests

* [REFACTOR] remove duplicated html

* [REMOVE] unnecessary class and test

* [FEATURE] Create and apply error pages

* [REFACTOR] Move and rename Main controller

* [REFACTOR] Modify controller name

* [REFACTOR] Modify new article id from 0 to 1

* [REFACTOR] Modify repository method name to remove correlation with db

* [REFACTOR] Modify int to AtomicInteger

* [REFACTOR] Apply code alignment

* [REFACTOR] Modify field dependency injection to constructor dependency injection

* [REFACTOR] Modify constructor

세터 제거 및 생성자 private으로 변경.

* refactor: 코드 리팩토링

* refactor: 매서드 위치 수정

* refactor: 리뷰 반영

* refactor: 리뷰 반영 : setter 메서드명 번경

* [REFACTOR] Remove of() static factory method

* [FIX] add id attribute

* [REFACTOR] Apply constant at auto-increment value

* refactor: 리뷰 반영, ArticleRepository.class update 메서드 삭제

* refactor: 리뷰 반영, ArticleRepository.class 수정에 따른 테스트 코드 삭제

* [REFACTOR] Apply code alignment

* refactor: 리뷰 반영, controller ModelAnvView 메서드 내부에서 수정

* refactor: 테스트 코드 수정

* [REFACTOR] Remove console logs

* [REFACTOR] Remove modifyArticle method

* [REFACTOR] Modify Setter name

* [REFACTOR] Modify save method

modify method가 필요없도록 수정

* [REFACTOR] Modify id from int to Long

* [FIX] Fix typing error

* [REFACTOR] Change update request

findArticle(id) 후 해당 Article을 수정하도록 변경

* refactor 피드백 반영

* refactor 피드백 반영

* [ADD] Add db for test

프로덕트 코드에 MySQL, 테스트 코드에 H2 적용

* [FEATURE] Apply JPA

Repository에 CrudRepository 상속받도록 적용.

* [ADD] Add dto for Article Domain Model

* [FEATURE] Implements create and read user

* [FEATURE] Implements login

* [FEATURE] Implement modify user

* [FEATURE] Implement delete user

* [FEATURE] Add logback dependency

* [FEATURE] Implement interceptor for session

* [FEATURE] Implement logout

* [FEATURE] Implement exception handler

* [FEATURE] Implement login dto and exception

* [FEATURE] Test Controller

* [REFACTOR] Move WebMvcConfig Class to config package

* [ADD] Add User Entity Tests

* [ADD] Add Article Service

* [REFACTOR] Remove unnecessary classes

* [REFACTOR] Remove unnecessary classes

* [ADD] Add static factory method at UserDto

* [ADD] Add tests for UserService

* [REFACTOR] remove duplicates

* [REFACTOR] remove unnecessary exceptions

* [ADD] Add error message for session interceptor

* Revert "[게시글 생성/조회/수정/삭제] 지노 미션 제출합니다. (woowacourse#45)"

This reverts commit 72e0dc0.

* refactor

* refactor 피드백 반영

* refactor 피드백 반영
import java.util.function.Function;
import java.util.stream.Collectors;

public class Converter<T, U> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오오! 좋네요 ㅎㅎ 💯

user -> new UserDto(
user.getEmail(),
user.getName(),
user.getPassword()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인자가 달라질 경우에는 어떻게 작성하는 것이 좋을까요?

@Transactional
public Long save(ArticleDto articleDto, String email) {
User user = userRepository.findByEmail(email)
.orElseThrow(IllegalArgumentException::new);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

customException을 작성하여 좀더 상세하게 예외상황을 전달하는 것이 좋을거 같아요.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

급하게 하느라 누락된 부분을 다시 반영하였습니다.!

Article article = new Article(articleDto.getTitle(),
articleDto.getCoverUrl(),
articleDto.getContents(),
user);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Article은 converter를 사용하지 않네요?

articleDto.getCoverUrl(),
articleDto.getContents(),
user);
return articleRepository.save(article).getId();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

시그니처를 맞춰주는 게 좋을거 같아요. ArticleService는 id를 리턴하고 Usersevice는 void 리턴인데요.
일반적으로 save를 하면 persistence entity를 리턴하니,
Service에서는 dto를 받아서 dto로 리턴하는 것은 어떨까요

return ArticleDto.of(article);
}

@Transactional

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금처럼 모든 method가 사용된다면 Class에 붙여두고 readOnly만 별도로 설정해줘도 되겠네요.

Article article = articleRepository.findById(articleId)
.orElseThrow(() -> new NotExistArticleIdException("존재하지 않는 Article 입니다."));

return ArticleDto.of(article);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메서드를 분리하여 재사용하는 것은 어떨까요

    @Transactional(readOnly = true)
    public ArticleDto findById(Long articleId) {
        return ArticleDto.of(findById(articleId));
    }

    @Transactional
    public UserDto findAuthor(long articleId) {
        User user = findById(articleId).getUser();
        return new UserDto(user.getEmail(),
                user.getName(),
                user.getPassword());
    }

    @Transactional(readOnly = true)
    public Article findById(Long articleId) {
        return articleRepository.findById(articleId)
                .orElseThrow(() -> new NotExistArticleIdException("존재하지 않는 Article 입니다."));
    }

@Transactional
public void modify(Long articleId, ArticleDto articleDto, String email) {
User user = userRepository.findByEmail(email)
.orElseThrow(IllegalArgumentException::new);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UserRepository를 직접 의존 주입하기 보다는, UserService에 메시지를 보내서 받는 것은 어떨까요

public List<ArticleDto> findAll() {
return articleRepository.findAll().stream()
.map(ArticleDto::of)
.collect(Collectors.toList());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SpringData의 pageable을 활용하는 것은 어떨까요

}

@Transactional
public void checkAuthor(Long articleId, String email) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

input과 output이 명확해야 테스트하기가 용이합니다.
void 리턴하는 메서드가 비즈니스 로직에 해당할 경우 한번 고민해보세요.
권한체크와 관련한 단위 테스트 코드도 같이 작성해보는 것도 좋겠네요.

}

@Transactional
public void findCommentWriter(Long commentId, String email) {
Copy link

@woowahanCU woowahanCU Jul 31, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CommentWriter를 찾는다기보다는 수정권한에 대한 체크를 담당하고 있네요.
그리고 이 메서드는 단일 책임 원칙 위반으로 보입니다.
아래 로직은 메서드로 분리하여 재사용하는 것은 어떨까요

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

권한에 대한 체크를 하는 의미를 담기 위해서 메소드 명을 변경하였습니다.

commentDto.setId(comment.getId());

given(userRepository.findByEmail(user.getEmail())).willReturn(Optional.of(user));
given(articleRepository.findById(article.getId())).willReturn(Optional.of(article));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CommentService가 각 repository와 연관관계가 맺어져있는데요. 결합도를 낮출 수는 없을까요?

userRepository.save(user);
userRepository.save(user);

verify(userRepository, times(1)).save(user);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예외가 발생하지 않네요.
times(2)로 둬야 테스트가 성공합니다.

void 올바른_User_생성_테스트() {
String email = "aa@naver.com";
String name = "zino";
String password = "password";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

email, password의 제약을 잡지는 못하고 있네요.
User 생성자에서 유효성을 검증해야 좋을거 같아요.

webTestClient.post().uri("/articles")
.body(BodyInserters.fromFormData("title", "title")
.with("coverUrl", "coverUrl")
.with("contents", "contents"))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get, post 요청 등을 메서드 분리하거나 공통된 URI를 상수처리하여 중복을 없앨 수 있을거 같아요
body에 들어가는 부분도 메서드로 분리하면 재사용할 수 있지 않을까요

    private <T> BodyInserters.FormInserter<String> mapBy(Class<T> classType, String... parameters) {
        BodyInserters.FormInserter<String> body = BodyInserters.fromFormData(Strings.EMPTY, Strings.EMPTY);

        for (int i = 1; i < classType.getDeclaredFields().length; i++) {
            body.with(classType.getDeclaredFields()[i].getName(), parameters[i - 1]);
        }
        return body;
    }

given(articleRepository.findById(article.getId())).willReturn(Optional.of(article));
given(CommentRepository.save(comment)).willReturn(comment);

assertThat(commentService.save(commentDto, user.getEmail(), article.getId())).isEqualTo(commentDto.getId());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

article에 Id가 없어 NPE가 발생하지 않을까요

webTestClient.post().uri("/articles")
.body(BodyInserters.fromFormData("title", "title")
.with("coverUrl", "coverUrl")
.with("contents", "contents"))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

email이 없어서 NPE가 발생하네요.

void writeForm_test() {
webTestClient.get().uri("/writing")
.exchange()
.expectStatus().isOk();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interceptor에서 체킹하고 있어서 이 테스트는 깨지네요.

Copy link

@woowahanCU woowahanCU left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요! 리뷰어 CU입니다.
짧은 시간내에 깔끔하게 잘 작성하셨네요. 👍
몇가지 피드백 남겼으니 확인해보시고 궁금한 부분있으시면 DM 남겨주세요.


@NotBlank(message = "비밀번호를 작성해주세요.")
@Pattern(regexp = "^([a-zA-Z0-9!@#$%^&*]{8,})$", message = "8자리 이상의 글자, 숫자, 특수문자를 입력해야합니다.")
private String password;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재는 8자리 이상이기만 하면 통과되네요.
email validator를 보시면 ConstraintValidator를 상속받아서 구현한 것을 볼 수 있습니다.
password의 경우에도 ConstraintValidator를 상속받아서 customValidator를 작성할 수 있어요.
그럼 도메인 모델의 구현코드에서 상당부분 해당 로직을 숨기고 비즈니스 로직만 표현이 가능해지겠네요.
참고만 해두세용 :)

https://www.baeldung.com/registration-password-strength-and-rules

String password = "pass";

assertThat(validator.validate(new User(email, name, password)).isEmpty()).isFalse(); }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

하나의 테스트는 하나의 개념만 담는 것이 좋다고 생각합니다.
현재는 email에 대한 유효성 위반인지 password에 대한 유효성 위반인지 해당 테스트로 확인이 불가하네요.
아래와 같이 작성하는 것은 어떨까요

    @Test
    void 올바르지_않은_password_User_생성() {
        String email = "woowahanCU@woowahan.com";
        String name = "cu";
        String password = "p";

        assertThat(getValidator(email, name, password))
                .extracting(ConstraintViolation::getMessage)
                .containsOnly("정규 표현식 \"^([a-zA-Z0-9!@#$%^&*]{8,})$\" 패턴과 일치해야 합니다.");
    }
...
    private Set<ConstraintViolation<User>> getValidator(String email, String name, String password) {
        return validator.validate(new User(email, name, password));
    }

createArticle(webTestClient, articleDto, sessionId, result);
});
});
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전체 테스트를 하면 사실상 처음 테스트에서 요청하여 저장된 값에 대해서만 테스트하는 구조네요.
이 후 테스트에서 수행하는 initialWork에서 추가했던 값은 사용하지 않을거 같아요.
auto increment때문에 이런 구성을 하셨을까요..

SpringBoot는 schema.sql, data.sql을 사용하여 DB를 초기화할 수 있어요. 이를 통해 더미데이터를 구성하여도 좋을거 같구요
https://docs.spring.io/spring-boot/docs/current/reference/html/howto-database-initialization.html#howto-initialize-a-database-using-spring-jdbc

유지보수 등의 이슈가 고민된다면 ApplicationRunner 등을 활용하여 Application이 구동되는 시점에 Repository 등의 의존성을 주입받은 Bean을 활용하여 데이터를 추가하는 방법도 있겠네요

https://yuien.tistory.com/entry/스프링-시작시점에서-프로그램-동작할-수-있게-하는-방법
https://www.daleseo.com/spring-boot-runners/

@Transactional
@Component
public class DataLoader {
    
    @Autowired
    private UserRepository userRepository;

    public void loadData() {
        loadUserData();
    }

    public void loadUserData() {
        ...
        userRepository.save(user);
    }
}

...

@Component
public class DataLoaderBootstrap implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    private DataLoader dataLoader;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        dataLoader.loadData();
    }
}


public class ControllerTestUtils {

public static String getSessionId(EntityExchangeResult<byte[]> postUserResponse) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link

@woowahanCU woowahanCU left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

피드백 반영 잘해주셨네요! 👍
몇가지 피드백 남겼으니 확인해보시고 궁금한 점 있으면 DM 남겨주세요.
그리고 저번에 잘못 피드백 남겼던 부분이 있었는데요. 이상하다 싶으면 알려주세요 😅
이번 미션은 여기서 Merge할게요~

@woowahanCU woowahanCU merged commit d6bc333 into woowacourse:hyojaekim Aug 4, 2019
vsh123 pushed a commit to hyojaekim/jwp-blog that referenced this pull request Aug 6, 2019
* revert: Project revert

* [게시글 생성/조회/수정/삭제] 지노 미션 제출합니다. (woowacourse#45)

* feat: thymeleaf 추가, html 파일 templates 디렉토리로 이동

* feat: Test 주석추가

* feat: ArticleController 추가

* feat: Article 클래스 추가

* feat: 게시글 수정, 삭제 기능 구현

* refactor: 코드 리팩토링

* refactor: 함수명 수정

* refactor: Controller 메서드명 수정

* refactor: 코드 리팩토링

* refactor: 매서드 위치 수정

* refactor: 리뷰 반영

* refactor: 리뷰 반영 : setter 메서드명 번경

* refactor: 리뷰 반영, ArticleRepository.class update 메서드 삭제

* refactor: 리뷰 반영, ArticleRepository.class 수정에 따른 테스트 코드 삭제

* refactor: 리뷰 반영, controller ModelAnvView 메서드 내부에서 수정

* refactor: 테스트 코드 수정

* refactor 피드백 반영

* refactor 피드백 반영

* [게시판 회원 기능] 지노 미션 제출합니다. (woowacourse#79)

* feat: thymeleaf 추가, html 파일 templates 디렉토리로 이동

* feat: Test 주석추가

* feat: ArticleController 추가

* feat: Article 클래스 추가

* [DOCS] Add README.md

* [ADD] Add dependencies

* [ADD] Add properties

* [MOVE] Move views to templates directory

* [ADD] Add properties

* [FEATURE] Implement redirection to article writing page

* [FEATURE] Implement methods for read and create article

* [FEATURE] Implement methods for read article edit page

* [FEATURE] Implement methods for update article

* [FEATURE] implement methods for delete article

* feat: 게시글 수정, 삭제 기능 구현

* [FEATURE] Implement methods for read all articles

* [FEATURE] Apply template to article page

* refactor: 코드 리팩토링

* refactor: 함수명 수정

* refactor: Controller 메서드명 수정

* [REFACTOR] adjust put method to article edit page

- 메소드 타입에 따른 해당 메소드 뷰 적용

* [REFACTOR] replace response from rendering to redirection

* [ADD] Update css and html for bug fix

* [FEATURE] Apply delete specific article

* [REFACTOR] management role from Article to ArticleRepository

* [REFACTOR] update for redirection tests

* [REFACTOR] remove duplicated html

* [REMOVE] unnecessary class and test

* [FEATURE] Create and apply error pages

* [REFACTOR] Move and rename Main controller

* [REFACTOR] Modify controller name

* [REFACTOR] Modify new article id from 0 to 1

* [REFACTOR] Modify repository method name to remove correlation with db

* [REFACTOR] Modify int to AtomicInteger

* [REFACTOR] Apply code alignment

* [REFACTOR] Modify field dependency injection to constructor dependency injection

* [REFACTOR] Modify constructor

세터 제거 및 생성자 private으로 변경.

* refactor: 코드 리팩토링

* refactor: 매서드 위치 수정

* refactor: 리뷰 반영

* refactor: 리뷰 반영 : setter 메서드명 번경

* [REFACTOR] Remove of() static factory method

* [FIX] add id attribute

* [REFACTOR] Apply constant at auto-increment value

* refactor: 리뷰 반영, ArticleRepository.class update 메서드 삭제

* refactor: 리뷰 반영, ArticleRepository.class 수정에 따른 테스트 코드 삭제

* [REFACTOR] Apply code alignment

* refactor: 리뷰 반영, controller ModelAnvView 메서드 내부에서 수정

* refactor: 테스트 코드 수정

* [REFACTOR] Remove console logs

* [REFACTOR] Remove modifyArticle method

* [REFACTOR] Modify Setter name

* [REFACTOR] Modify save method

modify method가 필요없도록 수정

* [REFACTOR] Modify id from int to Long

* [FIX] Fix typing error

* [REFACTOR] Change update request

findArticle(id) 후 해당 Article을 수정하도록 변경

* refactor 피드백 반영

* refactor 피드백 반영

* [ADD] Add db for test

프로덕트 코드에 MySQL, 테스트 코드에 H2 적용

* [FEATURE] Apply JPA

Repository에 CrudRepository 상속받도록 적용.

* [ADD] Add dto for Article Domain Model

* [FEATURE] Implements create and read user

* [FEATURE] Implements login

* [FEATURE] Implement modify user

* [FEATURE] Implement delete user

* [FEATURE] Add logback dependency

* [FEATURE] Implement interceptor for session

* [FEATURE] Implement logout

* [FEATURE] Implement exception handler

* [FEATURE] Implement login dto and exception

* [FEATURE] Test Controller

* [REFACTOR] Move WebMvcConfig Class to config package

* [ADD] Add User Entity Tests

* [ADD] Add Article Service

* [REFACTOR] Remove unnecessary classes

* [REFACTOR] Remove unnecessary classes

* [ADD] Add static factory method at UserDto

* [ADD] Add tests for UserService

* [REFACTOR] remove duplicates

* [REFACTOR] remove unnecessary exceptions

* [ADD] Add error message for session interceptor

* Revert "[게시글 생성/조회/수정/삭제] 지노 미션 제출합니다. (woowacourse#45)"

This reverts commit 72e0dc0.

* refactor

* refactor 피드백 반영

* refactor 피드백 반영

* [add] comment 클래스

* refactor

* refactor

* refactor

* add articles user 관계 추가

* delete ArticleConverter 삭제

* add 작성자만 글 수정 삭제 기능 추가

* add comment 추가 삭제 수정 기능 추가

* refactor 주석 제거, 어노테이션 추가

* refactor 불 필요 코드 제거

* refactor NotExistUserIdException class 수정

* add NotExistCommentIdException class 수정

* fix 예외처리 클래스 수정

* refactor 접근제어자 변경

* add comment 작성자만 추가, 삭제, 수정 기능 추가

* add comment test 작성중..

* refactor

* refactor

* refactor 예외 누락된 부분 custom exception 적용

* refactor Transactional 공통된 부분 적용

* refactor redirect 리팩토링

* refactor Path 중복제거

* refactor

* refactor 불필요한 부분 제거

* refactor 댓글 생성 테스트 추가

* refactor

* refactor Service

* refactor 도메인 리팩토링

* refactor 변수명 rename

* refactor controller 리팩토링

* refactor 모든 테스트 추가
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants