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

[BE] refactor: 구조화된 템플릿 사용 #980

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from

Conversation

Kimprodp
Copy link
Contributor

@Kimprodp Kimprodp commented Nov 24, 2024

  • resolves #

🚀 어떤 기능을 구현했나요 ?

  • 도메인 리팩터링 이전 PR에서 '구조화된 템플릿' 부분만 분리하여 PR 올립니다.
  • 분리하면서 조금더 내용을 보완했어요. (검증, 클래스 분리 등)
  • 자세한 내용은 이전 작업에 대한 디스커션 참고 부탁합니다. 도메인 리팩터링 실천하기 #974
  • 간략하게 설명하면, 서비스 특성에 따라 템플릿 패키지는 외부에서 사용하는 패키지 입니다.
  • 하지만 템플릿 패키지의 엔티티들은 id를 가지고 분리되어 있어서 다른 패키지에서 엔티티를 직접 사용하고, 이 과정에서 많 참조가 생기게 됩니다.
  • 따라서 템플릿 Repo를 이용하지 않고, 각 도메인들이 연결되어 캡슐화된 클래스를 하나 만들고 외부에서 사용하도록 합니다.
  • 이 클래스를 사용하는 것은 다음 PR에서 보여드릴게요. 그거까지 같이하면 분량이 너무 커질듯 합니다. (디스커션에 나와있긴 합니다.)

🔥 어떻게 해결했나요 ?

  • 구조화된 템플릿을 생성함에 있어서 검증 작업이 많아 생성 클래스를 따로 분리했습니다.
  • 구조화된 템플릿자체에는 제공기능만 있고, 외부에서 사용되기 때문에 생성자를 막고 단순히 정보만 제공할 예정입니다.
  • 생성 클래스는 템플릿 패키지에서만 사용 가능하며, 나중에 기능이 확장되어도 그에 따른 템플릿 생성만 내부에서 수정하거나 추가하면 됩니다.

📝 어떤 부분에 집중해서 리뷰해야 할까요?

  • 도메인이 따로 분리되어 있어서 검증하지 않았던 부분을 하나로 묶는 객체가 생기면서 조금 더 꼼꼼하게 검증할 수 있게 되었어요.
  • 검증 부분이 괜찮은지, 놓친건 없는지 잘 확인해주세요.

📚 참고 자료, 할 말

  • 머지되면 다음 작업 들어갈게요.
  • 따로 분리해서 작업하니 더 꼼꼼하게 보게 되네요. 산초 의견 고맙습니다.

Copy link

github-actions bot commented Nov 24, 2024

Test Results

200 tests  +40   197 ✅ +40   5s ⏱️ ±0s
 68 suites + 9     3 💤 ± 0 
 68 files   + 9     0 ❌ ± 0 

Results for commit 4bf7a00. ± Comparison against base commit 0522c5f.

♻️ This comment has been updated with latest results.

@Kimprodp
Copy link
Contributor Author

Kimprodp commented Nov 24, 2024

중간에 워딩 하나 바꾼게 있어서 여러 패키지를 좀 건드렸어요. 참고 부탁합니다. (checkBox -> checkbox로 통일했습니다)
그냥 template 패키지만 중점적으로 보면 됩니다.

Copy link
Contributor

@donghoony donghoony left a comment

Choose a reason for hiding this comment

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

이해하면서 한 번 끊고 자세한 건 다음 코멘트 때 물어볼게요!

}
}

public List<Long> getSectionIds() {
Copy link
Contributor

Choose a reason for hiding this comment

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

이쪽도 N+1 주의해야 할 거예요 ⚠️

Copy link
Contributor Author

Choose a reason for hiding this comment

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

지금 동작으로는 EAGER 로딩 시, 자동으로 JOIN 사용해서 N+1은 발생하지 않아요.
대신 Section을 로딩할 때 N+1이 발생했는데요. 이 부분은 FETCH JOIN을 사용하면 해결되긴 합니다.

별개로 이번 PR이 머지되고 이제 다른 곳에서 StructuredTemplate을 사용하게 되면, 템플릿 자원들을 DB 호출하는 곳은 StructuredTemplateService에서 구조화된 템플릿을 만들 때 밖에 없습니다.
그래서 템플릿, 섹션, 질문, 옵션그룹, 옵션아이템을 가져올 때 필요한 것들을 한번에 즉시 로딩 해도 상관없다 생각이 들어요. 이렇게 되면 직접 작성한 쿼리에는 모두 FETCH JOIN 붙이고 잘 동작되는지 확인이 필요합니다.

만약 보수적으로 접근하고 싶다면, LAZY 하고 @Fetch(FetchMode.JOIN)과 FETCH JOIN을 병행하는 방법이 있습니다.

저는 둘 다 상관없긴 합니다. 우선은 코드 수정이 적은 즉시로딩에 FETCH JOIN을 붙이는 쪽으로 수정할게요, 의견 주세용

}
}

private void validateNoDuplicates(List<Long> sectionIds) {
Copy link
Contributor

Choose a reason for hiding this comment

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

긍정문을 활용해서 validateUniqueSectionIds ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

둘 다 긍정문 아닌가요?? 헷갈리네..
메서드명이 조금 직관적이였으면 해서 이 부분은 다른 분들 의견 반영해서 나머지도 통일할게요~
@nayonsoso @skylar1220

Copy link
Contributor

Choose a reason for hiding this comment

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

둘 다 긍정문 아닌가요?

validateNoDuplicates 는 "중복이 없는지 검증"이고
validateUniqueSectionIds 는 "값이 유일한지 검증"이므로
중복이 '없는지'가 부정의 의미를 담고있다는 말 같아요😊

저는 두 의견 다 일리가 있다고 생각되는데, 중복이 없는것을 검사하는게 더 의미를 강조한다고 느껴져서 validateNoDuplicates 에 한표요!

import reviewme.template.domain.OptionGroup;
import reviewme.template.domain.exception.OptionGroupNotExistException;

@Getter
Copy link
Contributor

Choose a reason for hiding this comment

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

필요한가요 ? 다른 클래스에서는 없길래 질문합니당

Copy link
Contributor Author

Choose a reason for hiding this comment

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

안쓰는데 뺀다는걸 빼먹었슴다 반완

Comment on lines 23 to 27
this.template = template;
this.sections = sections;
this.questions = questions;
this.optionGroups = null;
this.optionItems = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

생성자 체이닝해주세요! this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

반영했스니다!


public class StructuredTemplateCreator {

public StructuredTemplate create(Template template, List<Section> sectionList, List<Question> questionList,
Copy link
Contributor

Choose a reason for hiding this comment

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

List 접두어 대신 s 복수형으로 가시죠! (변수명이 타입에 의존함)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

        Sections sections = new Sections(sectionList);
        Questions questions = new Questions(questionList);
        OptionGroups optionGroups = new OptionGroups(optionGroupList);
        OptionItems optionItems = new OptionItems(optionItemList);

요 부분에서 네이밍이 겹쳐서 저렇게 해놓긴 했는데,, 아이디어 없을까요

Copy link
Contributor

Choose a reason for hiding this comment

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

Sections, Questions와 같이 일급 컬렉션이 외부에서 사용되지 않는다면, 외부에서는 -s로 끝나는 게 Collection이라 List로 넣는다는 게 자연스러울 것 같긴 해요

@Slf4j
public class StructuredTemplateInvisibleSectionFoundException extends BadRequestException {

private static final String ERROR_MESSAGE = "서버 내부에서 문제가 발생했어요. 서버에 문의해주세요.";
Copy link
Contributor

Choose a reason for hiding this comment

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

INTERNAL_SERVER_ERROR_MESSAGE ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

반완!

Copy link
Contributor

@nayonsoso nayonsoso left a comment

Choose a reason for hiding this comment

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

테테테드 왕건이 rc 드립니다~

@@ -53,10 +53,10 @@ public void setup() {
long growthOptionId = optionItemRepository.save(new OptionItem("🌱성장 마인드셋 (예: 새로운 분야나 잘 모르는 분야에 도전하는 마음, 꾸준한 노력으로 프로젝트 이전보다 성장하는 모습)",categoryOptionGroupId,5, OptionType.CATEGORY )).getId();

// 커뮤니케이션 능력 섹션
long checkBoxCommunicationQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "커뮤니케이션, 협업 능력에서 어떤 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
long checkboxCommunicationQuestionId = questionRepository.save(new Question(true, QuestionType.CHECKBOX, "커뮤니케이션, 협업 능력에서 어떤 부분이 인상 깊었는지 선택해주세요.", null, 1)).getId();
Copy link
Contributor

Choose a reason for hiding this comment

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

checkbox 통일 좋네요 👍

}
}

private void validateNoDuplicates(List<Long> sectionIds) {
Copy link
Contributor

Choose a reason for hiding this comment

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

둘 다 긍정문 아닌가요?

validateNoDuplicates 는 "중복이 없는지 검증"이고
validateUniqueSectionIds 는 "값이 유일한지 검증"이므로
중복이 '없는지'가 부정의 의미를 담고있다는 말 같아요😊

저는 두 의견 다 일리가 있다고 생각되는데, 중복이 없는것을 검사하는게 더 의미를 강조한다고 느껴져서 validateNoDuplicates 에 한표요!


public DuplicateQuestionIdException(List<Long> questionIds) {
super("섹션에는 중복된 질문이 있을 수 없어요.");
log.info("Duplicate question ID found during create Section - questionIds: {}", questionIds);
Copy link
Contributor

Choose a reason for hiding this comment

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

[사소]
문법적으로 during 뒤에는 v-ing 가 와야한다고 알고 있어요,
그래서 during create → while creating 으로 변경하면 좋겠네요!

그리고 "while + 현재분사(-ing)" 구조가 동작이 진행되는 동안 발생한 사건을 표현할 때 더 자연스럽게 사용된다고 하네요 ㅎㅎ

Comment on lines +15 to +27
StructuredTemplate(Template template, Sections sections, Questions questions, OptionGroups optionGroups,
OptionItems optionItems) {
this.template = template;
this.sections = sections;
this.questions = questions;
this.optionGroups = optionGroups;
this.optionItems = optionItems;
}

StructuredTemplate(Template template, Sections sections, Questions questions) {
this(template, sections, questions, null, null);
}

Copy link
Contributor

Choose a reason for hiding this comment

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

생성자 체이닝을 할 때는 기본 생성자(모든 파리미터를 가진 생성자)를 가장 아래에 쓴다고 알고 있어요, '함수는 호출 순서대로' 나열하는 것이 기본이기 때문에 그렇다고 하네요!
예를 들어 a 함수가 b 함수를 호출하면 a -> b 순으로 나열하는 것처럼요~

Comment on lines +19 to +22
public class StructuredTemplateCreator {

public StructuredTemplate create(Template template, List<Section> sectionList, List<Question> questionList,
List<OptionGroup> optionGroupList, List<OptionItem> optionItemList) {
Copy link
Contributor

@nayonsoso nayonsoso Nov 25, 2024

Choose a reason for hiding this comment

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

StructuredTemplateCreator 에 대해 대왕 피드백이 있습니다🤴🏻
StructuredTemplateCreator를 빈으로 등록하고,
레포지토리들을 주입받아서 이 안에서 StructuredTemplate 을 만들었으면 합니다.

'템플릿 생성에 필요한 재료를 찾는 것이 StructuredTemplateService 의 역할일까?' 하면 아니라고 생각합니다.
테드가 적은 지난 디스크립션에 저도 공감을 했는데, 그 의도랑 현재 코드가 맞지 않는 것 같습니다.

제가 이해한 바로는, StructuredTemplate 를 조회를 위한 VO로 사용을 하되,
이 객체가 조회와 관련된 모든 일을 하기에는 책임이 무거워지므로
이를 분산하기 위해서 객체를 분리한다고 했었던 것 같아요.
그리고 그 의도로 탄생한 것이 StructuredTemplateService 맞죠?
또한 이 VO 를 생성하는 로직도 복잡하므로 이를 생성하는 책임은 StructuredTemplateCreator 로 분산했다고 생각했습니다.

이런 책임들을 생각한다면, StructuredTemplateCreator가 long templateId를 인자로 받고,
StructuredTemplate 생성에 필요한 재료들을 찾아와서 직접 생성을 하는 것이 적절하다 생각합니다. StructuredTemplateService 에는 비지니스 로직이 남긴 조회가 들어가고요.
지금의 코드에서는 생성 책임이 분산되었다 생각합니다.


그리고 이것에 이어서, 저는 StructuredTemplateCreator 내부의 검증 중 대부분이 없어도 된다고 생각합니다.
만약 제가 위에 코멘트한대로 StructuredTemplateCreator 가 repository 에서 직접 엔티티를 조회해온다면
정합성이 보장이 된 데이터 들일 것입니다. 따라서 추가적인 정합성 검증은 거치지 않아도 된다고 생각합니다.
이것이 왕건이 RC가 될 것 같아 조심스러운데요... 여기에 대한 테드의 생각도 듣고 싶습니당~

Comment on lines +9 to +18
public class Sections {

private final List<Section> sections;

public Sections(List<Section> sections) {
validateSections(sections);
this.sections = sections.stream()
.distinct()
.toList();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

사실 이런 일급 컬렉션에 대해서 처음에는 '이렇게까지 할 필요가 있나?!' 싶었는데,
나중에 코드가 붙으면 매우 유용하게 사용될 것 같아 칭찬 스티커 하나 드립니다👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

3 participants