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

[Spring 지하철 노선도 관리 - 3단계] 다니(이다은) 미션 제출합니다. #141

Merged
merged 68 commits into from
May 21, 2021

Conversation

da-nyee
Copy link

@da-nyee da-nyee commented May 11, 2021

안녕하세요, 닉! 다니입니다 🙌

이번 3단계는 저번 1, 2단계처럼 페어 프로그래밍을 했어요!
혼자보다 페어로 하니까 프로그램 설계나 코드에 관해 더 고민하고 의견을 나눌 수 있었습니다 👍

레이어간 의존 관계에 대해 고민하다 질문이 하나 생겼어요 !

서비스에서 여러 DAO를 의존하는 구조 vs 서비스에서 여러 서비스를 의존하는 구조

두 구조 중에 어느 구조가 더 바람직한 건가요??

저는 처음에 전자로 구현하다 중복 코드가 많아지는 것 같아 나중에 후자로 변경했습니다 !
현재 구조는 아래와 같은 모습이에요.

layer_dependency

이렇게 의존하니까 중복 코드가 없어지는 장점이 있었지만, 구조가 다소 복잡해지는 단점도 존재했어요.
이런 구조도 괜찮은 건가요?? 닉의 생각이 궁금해요!

코드에서 부족한 부분 전부 지적해주세요~!
피드백 기다리고 있겠습니다 ✨

매번 귀한 시간 내주셔서 감사합니다 🙇‍♀️❣️

da-nyee and others added 30 commits May 9, 2021 01:19
- 정렬된 상행역 -> 하행역 목록 반환
Copy link

@wbluke wbluke left a comment

Choose a reason for hiding this comment

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

3단계도 잘 진행해주셨네요 다니! 👍
질문에 대한 답변 포함해서 몇 가지 코멘트 남겼으니 확인 부탁드려요 🙂

@@ -1,4 +1,4 @@
package wooteco.subway.line;
package wooteco.subway.section;

public class SectionRequest {
private Long upStationId;
Copy link

Choose a reason for hiding this comment

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

java bean validation의 @NotNull 등을 사용해서 validation을 해보면 어떨까요? :)


@PostMapping("/{lineId}/sections")
public ResponseEntity<SectionResponse> createSection(@RequestBody SectionRequest sectionRequest, @PathVariable Long lineId) {
lineService.validateId(lineId);
Copy link

Choose a reason for hiding this comment

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

Controller에서는 최소한의 요청, 응답, 그리고 request 값에 대한 유효성 체크만 가져가고,
실제 lineId가 존재하는지에 대한 validation은 서비스 레이어에서 하는게 좋을 것 같아요 :)

Comment on lines 72 to 75
public void validateId(Long lineId) {
lineDao.findById(lineId)
.orElseThrow(LineNonexistenceException::new);
}
Copy link

Choose a reason for hiding this comment

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

목적과 실제 행위가 정확하게 일치하지는 않는 것 같아요.
단건 조회를 통한 예외 체크보다는 DAO에서 존재성을 체크하는 기능을 별도로 제공하면 어떨까요? (exists 쿼리 or limit 1 쿼리)

Comment on lines 33 to 39
public boolean isUpStation(Long id) {
return upStationId.equals(id);
}

public boolean isDownStation(Long id) {
return downStationId.equals(id);
}
Copy link

Choose a reason for hiding this comment

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

파라미터의 id가 어떤 id인지 나타내주면 좋을 것 같아요 :)


import static java.util.stream.Collectors.toList;

public class Sections {
Copy link

Choose a reason for hiding this comment

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

핵심 비즈니스 로직을 담고 있는 객체인데 단위테스트가 없네요 ㅎㅎ 추가해보면 좋겠어요 :)

Comment on lines 29 to 36
Section section = new Section(
sectionRequest.getUpStationId(), sectionRequest.getDownStationId(), sectionRequest.getDistance());
Sections sections = new Sections(sectionDao.findByLineId(lineId));
sections.validate(section);
Optional<Section> overlappedSection = sectionDao.findBySameUpOrDownId(lineId, section);
Section newSection = sectionDao.save(lineId, section);
overlappedSection.ifPresent(updateIntermediate(newSection));
return newSection;
Copy link

Choose a reason for hiding this comment

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

어떤 일들을 하고 있는지 한눈에 파악하기가 어려운데요.
공백 라인도 의미를 가지기 때문에, 맥락에 따라 공백 라인을 넣어 자신의 의도를 표현하는 연습도 같이 해보면 좋을 것 같아요.
그렇게 나누다가 작은 묶음을 메서드로 분리할 수 있다면 더 좋고요!

Comment on lines 16 to 17
private final StationService stationService;
private final SectionDao sectionDao;
Copy link

Choose a reason for hiding this comment

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

레이어간 의존 관계에 대해 고민하다 질문이 하나 생겼어요 !

서비스에서 여러 DAO를 의존하는 구조 vs 서비스에서 여러 서비스를 의존하는 구조

두 구조 중에 어느 구조가 더 바람직한 건가요??

정답이 없는 문제이긴 한데요 ㅎㅎ
저는 기본적으로 서비스에서 여러 DAO를 의존하는 구조 를 지향하고,
가끔 너무 공통적인 타 도메인 서비스의 로직이 반복되거나 하는 경우는 다른 서비스를 가져와서 쓰기도 합니다 :)

DAO(혹은 Repository)는 최소 단위의 DB Access 로직만 제공하기 때문에 서비스 레이어보다 각 API의 기능이 명확하고요.
같은 레이어끼리 참조하다보면 오히려 도메인별로 레이어를 나눈게 무색할 정도로 스파게티 코드가 될 위험이 있다고 생각하기 때문이에요 ㅎㅎ

Comment on lines 30 to 36
ExtractableResponse<Response> response = RestAssured.given().log().all()
.body(sectionRequest)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.when()
.post("/lines/1/sections")
.then().log().all()
.extract();
Copy link

Choose a reason for hiding this comment

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

이 부분 공통으로 추출해볼 수 있을 것 같아요 :)

import static org.assertj.core.api.Assertions.assertThat;

@DisplayName("지하철 구간 관련 기능")
@Sql("/data.sql")
Copy link

Choose a reason for hiding this comment

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

저는 data.sql 을 사용한 테스트를 지양하는데요 ㅎㅎ
이 파일을 의존해 테스트가 작성된다면, 나중에는 해당 파일에 대한 강한 의존성으로 여러 테스트를 수정하기도, 추가하기도 어렵습니다.
지금의 Section 인수테스트를 보면서도 test fixture가 보이지 않아서, 어떤 내용을 테스트하고자 하는지 파악하기가 어려워보여요 ㅎㅎ
test fixture는 웬만하면 각 메서드에, 그게 어렵다면 각 클래스에 생성해주는 것이 좋습니다 :)

Copy link
Author

Choose a reason for hiding this comment

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

Copy link
Author

Choose a reason for hiding this comment

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

기존 data.sql 대신 @BeforeEach로 매 테스트 실행 전에 필요한 데이터를 세팅하도록 했습니다 !
이런 방식으로 test fixture를 생성하면 되나요??


private final SectionRequest sectionRequest = new SectionRequest(2L, 3L, 10);

@DisplayName("구간을 생성한다.")
Copy link

Choose a reason for hiding this comment

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

  • 기존 구간의 상행역을 포함한 추가
  • 기존 구간의 하행역을 포함한 추가

이와 같이 더 상세한 케이스로 나눠볼 수 있을 것 같아요 :)

@da-nyee
Copy link
Author

da-nyee commented May 18, 2021

안녕하세요, 닉! 다니입니다 🙌

먼저 코드 리뷰 감사합니다!! 덕분에 많이 학습할 수 있었습니다 !
이번 리팩토링은 경로 조회 / 로그인 미션과 겹쳐 생각보다 더 늦어졌습니다 💧

저번에 레이어간 의존 관계에 대해 질문을 드렸는데요!
코드를 수정하다보니 자연스레 서비스에서 여러 DAO를 의존하는 구조로 변경하게 됐습니다.

현재 구조는 아래 그림과 같아요 !

layer_architecture

이렇게 구조를 변경하니까 제가 생각했던 것과 달리 훨씬 깔끔하게 코드를 구성할 수 있었어요. 중복 코드도 따로 없었어요 !
말씀하신 것처럼 같은 레이어별로 의존하면 오히려 스파게티 코드가 될 가능성이 생길 거 같아요. 답변 감사합니다!

리팩토링 하면서 궁금했던 점은 👀 이모지와 함께 코멘트로 남겨뒀어요! 확인해주시면 감사하겠습니다!

이번에도 부족한 부분 마구마구 지적해주세요! 피드백 기다리고 있겠습니다 ~!

항상 귀한 시간 내주셔서 감사합니다 🙇‍♀️❣️

@da-nyee
Copy link
Author

da-nyee commented May 19, 2021

@da-nyee
Copy link
Author

da-nyee commented May 19, 2021

📚 다니의 학습 로그

[Spring] Bean Validation - 3

내용

태그

{Spring}, {Bean Validation}, {validation}, {annotation}

Copy link

@wbluke wbluke left a comment

Choose a reason for hiding this comment

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

피드백 반영 고생 많으셨어요 다니! 👍
수정해주신 부분 중에 한두가지 추가로 말씀드릴 내용이 있어서 코멘트 남겼어요.
확인해 주시고 재요청 부탁드려요 🙂

@Service
@Transactional(readOnly = true)
Copy link

Choose a reason for hiding this comment

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

이와 반대로 readOnly를 조회전용 API에만 적용하는 방법도 있으니 참고해 주세요 :)

Comment on lines 8 to 20
public class Section {
@NotNull
@Positive
private Long id;
@NotNull
@Positive
private Long upStationId;
@NotNull
@Positive
private Long downStationId;
@NotNull
@Positive
private int distance;
Copy link

Choose a reason for hiding this comment

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

음 이런 도메인에는 java bean validation을 사용하지 않는 것이 좋습니다.
도메인 레이어는 프로젝트 전반적으로 쓰이기 때문에 순수한 자바 객체로 유지하는 것이 좋은데요, 지금 같은 경우는 validation 라이브러리에 의존하는 구조가 되어버립니다.
말씀드린 validation은 request DTO에서 최초 검증하는 용도로만 사용해 주세요!

Comment on lines 3 to 11
public class SectionTestFixture {
public static Section 강남_역삼 = new Section(1L, 2L, 10);
public static Section 역삼_선릉 = new Section(2L, 3L, 6);
public static Section 강남_선릉 = new Section(1L, 3L, 16);
public static Section 선릉_삼성 = new Section(3L, 4L, 8);
public static Section 교대_강남 = new Section(5L, 1L, 10);
public static Section 교대_서초_불가능1 = new Section(5L, 6L, 10);
public static Section 교대_서초_불가능2 = new Section(5L, 6L, 14);
public static Section 합정_당산 = new Section(10L, 11L, 12);
Copy link

Choose a reason for hiding this comment

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

fixture를 잘 만들어 주셨군요 👍
추가적으로, 저는 fixture가 메서드마다, 그게 어렵다면 클래스마다 있어야 한다고 생각하는데요.
이렇게 별도의 파일로 존재할 경우 테스트를 작성할 때 항상 이 내용을 모두 인지하고 있어야만 하고,
테스트를 읽는 사람도 해당 파일까지 번갈아 확인하면서 내용을 파악해야 하기 때문에 허들이 생깁니다.
(data.sql과 크게 다르지 않을 수 있어요 ㅎㅎ)

그리고 static으로 fixture를 만들면 공통으로 해당 인스턴스를 사용하는 다수의 테스트에서 어떤 동시성 이슈가 있을지 예측할 수 없습니다.
한쪽에서 fixture를 수정하는 테스트를 했는데 다른 쪽에서 영향을 받을 수 있겠죠.
고로 로컬 변수나 클래스 필드로 만들어주는 것이 좋습니다 :)

Copy link

Choose a reason for hiding this comment

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

테스트를 작성할 때 제가 가장 중시하는 원칙 중 하나는
테스트는 애플리케이션을 설명하는 문서 라는 말입니다.
프로젝트를 처음 보는 사람이 이 테스트 문서를 읽었을 때 애플리케이션이 어떻게 동작하겠구나를 대략적으로라도 예측할 수 있도록
좋은 테스트 케이스와 명확한 명세로 정리하는 것이 중요하다고 생각합니다 :)

@da-nyee
Copy link
Author

da-nyee commented May 21, 2021

안녕하세요, 닉! 다니입니다 🙌

추가 피드백 반영해서 다시 제출합니다 ~!
이번에도 잘 부탁드립니다 ✨

귀한 시간 내주셔서 감사합니다 🙇‍♀️❣️

Copy link

@wbluke wbluke left a comment

Choose a reason for hiding this comment

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

추가 피드백 반영까지 긴 시간 고생 많으셨어요 👏
남은 미션도 화이팅입니다 🙂

@wbluke wbluke merged commit 047645b into woowacourse:da-nyee May 21, 2021
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.

None yet

3 participants