-
Notifications
You must be signed in to change notification settings - Fork 7
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
[REFACTOR] Service Layer 리팩토링 #650
Comments
TemplateTagService, TagService 분리📌 어떤 기능을 리팩터링 하나요?
두 도메인에 대한 로직들은 TemplateTagApplicationService 에 두는 것이 더 적절하다고 생각됩니다. AS-IS// TemplateTagService.java
@Transactional
public void createTags(Template template, List<String> tagNames) {
List<String> existingTags = tagRepository.findNameByNamesIn(tagNames);
templateTagRepository.saveAll(
existingTags.stream()
.map(tagRepository::fetchByName)
.map(tag -> new TemplateTag(template, tag))
.toList()
);
List<Tag> newTags = tagRepository.saveAll(
tagNames.stream()
.filter(tagName -> !existingTags.contains(tagName))
.map(Tag::new)
.toList()
);
templateTagRepository.saveAll(
newTags.stream()
.map(tag -> new TemplateTag(template, tag))
.toList()
);
} TO-BE// // TemplateTagApplicationService.java
@Transactional
public void createTemplateTags(Template template, List<String> tagNames) {
List<Tag> tags = tagService.findOrCreateTags(tagNames);
templateTagService.createTemplateTag(template, newTags);
} // TemplateTagService.java
@Transactional
public void createTemplateTags(Template template, List<Tag> tags) {
List<TemplateTag> templateTags = tags.stream()
.map(tag -> new TemplateTag(template, tag))
.collect(Collectors.toList());
templateTagRepository.saveAll(templateTags);
} // TagService.java
@Transactional
public List<Tag> findOrCreateTags(Template template, List<String> tagNames) {
List<Tag> existingTags = tagRepository.findAllByNamesIn(tagNames);
List<Tag> newTags = this.createTags(existingTags, newTagNames);
return existingTags.saveAll(newTags);
} AS-IS// TemplateTagService.java
public List<Tag> getByTemplate(Template template) {
return templateTagRepository.findAllByTemplate(template).stream()
.map(TemplateTag::getTag)
.toList();
} TO-BE// TagService로 이동
public List<Tag> getAllByTemplate(List<TemplateTag> templateTags) {
return templateTags.stream()
.map(TemplateTag::getTag)
.toList();
} |
SourceCodeService에 레거시 코드 제거 및 개선AS-IS
deleteByTemplateId 메서드 n번 호출 & 쿼리 n번
소스 코드 아이디로 삭제하는 것 같지만 템플릿 아이디로 삭제하는 것임 TO-BE
각각 repository 메서드 1번 호출 & 쿼리 n번(deleteAllByTemplateId) 또는 1번(deleteAllByTemplateIdInBatch) 등등이 더 있을 수 있습니다.
🔍 참고할만한 자료(선택)createSourceCodes() 메서드에 @transactional 관련 createSourceCodes에서는 saveAll만 호출하고 있습니다. 따라서 트랜잭션으로 묶이지 않아도 된다고 생각합니다~ |
SourceCodeService 중 문제가 있는 로직 수정각 케이스에 대해 검증 또는 로직 변경이 필요 소스 코드 생성 실패 케이스
소스 코드 수정 성공 케이스
소스 코드 수정 실패 케이스
|
CategoryTemplateApplicationService를 포함한 파사드 서비스, 하위 서비스의 역할과 책임단일 책임 원칙 (SRP)
추상화 수준 (DIP 관련)현재 구조에서는 카테고리 관련 로직 변경 시 파사드 클래스도 수정해야 한다. == 유지보수에 좋지 않다.
요약
즉, CategoryTemplateApplicationService 는 카테고리 서비스에게 카테고리를 가져오도록 요청만 해야 하며, 카테고리 서비스에서 줘도 되는지(권한 체크), 있는지 없는지(fetchBy) 등을 판단해야함 AS-IS@Service
@RequiredArgsConstructor
public class CategoryTemplateApplicationService {
private final CategoryService categoryService;
private final TemplateApplicationService templateApplicationService;
public Long createTemplate(Member member, CreateTemplateRequest createTemplateRequest) {
Category category = categoryService.fetchById(createTemplateRequest.categoryId());
category.validateAuthorization(member);
return templateApplicationService.createTemplate(member, category, createTemplateRequest);
}
public void update(Member member, Long templateId, UpdateTemplateRequest updateTemplateRequest) {
Category category = categoryService.fetchById(updateTemplateRequest.categoryId());
category.validateAuthorization(member);
templateApplicationService.update(member, templateId, updateTemplateRequest, category);
}
} TO-BE@Service
@RequiredArgsConstructor
public class CategoryTemplateApplicationService {
private final CategoryService categoryService;
private final TemplateApplicationService templateApplicationService;
public Long createTemplate(Member member, CreateTemplateRequest createTemplateRequest) {
Category category = categoryService.findById(createTemplateRequest.categoryId(), member.getId());
return templateApplicationService.createTemplate(member, category, createTemplateRequest);
}
public void update(Member member, Long templateId, UpdateTemplateRequest updateTemplateRequest) {
Category category = categoryService.findById(updateTemplateRequest.categoryId(), member.getId());
templateApplicationService.update(member, templateId, updateTemplateRequest, category);
}
} |
|
DB 예외 핸들링이 필요할지 고민
이 부분을 예외 처리를 해 주어야 할지~ |
|
Fixture 정리MemberFixture 두개MemberFixture가 두 개 존재한다 (fixture 패키지, member.fixture 패키지) Fixture 메서드명 통일Fixture 마다 정적 팩토리 메서드 명이 다르다 내가 제안하는 이름은 결국엔 모든 메서드가 정적 팩토리 메서드이니 정팩메 이름 규칙을 따르면 좋을 것 같다.
|
CategoryTemplateApplicationService를 제거테스트를 하기 위해 어떤 것을 테스트 해야하는지, 이 계층의 역할은 무엇인지 고민했습니다. 제가 생각하기에 선택할 수 있는 방법
|
TemplateService로 넘어오는 파라미터 변경AS-ISTemplateService의 public Template createTemplate(Member member, CreateTemplateRequest createTemplateRequest, Category category) {
Template template = new Template(
member,
createTemplateRequest.title(),
createTemplateRequest.description(),
category);
return templateRepository.save(template);
}
TO-BEpublic Template createTemplate(Member member, String title, String description, Category category) {
Template template = new Template(
member,
title,
description,
category);
return templateRepository.save(template);
} |
리스트를 반환하는 JPA 쿼리 메서드명 변경리스트를 반환하는데 AS-ISpublic List<Template> getByMemberId(Long memberId) {
return templateRepository.findByMemberId(memberId);
} TO-BEpublic List<Template> getAllByMemberId(Long memberId) {
return templateRepository.findAllByMemberId(memberId);
} (여기는 몰리가 추가함) 초롱의 이슈와 동일한 이유로 TemplateTagService도 변경이 필요합니다~ AS-IS public List<Tag> getByTemplate(Template template) {
return templateTagRepository.findAllByTemplate(template).stream()
.map(TemplateTag::getTag)
.toList();
} TO-BE public List<Tag> getAllByTemplate(Template template) {
return templateTagRepository.findAllByTemplate(template).stream()
.map(TemplateTag::getTag)
.toList();
} |
중복검사 로직AS-IS현재 여러 템플릿 삭제 시 아래와 같은 코드가 사용되는데, id가 중복되어 있는지 검사하는 로직이 // TemplateApplicationService.java
@Transactional
public void deleteByMemberAndIds(Member member, List<Long> ids) {
thumbnailService.deleteByTemplateIds(ids);
sourceCodeService.deleteByIds(ids);
templateTagService.deleteByIds(ids);
templateService.deleteByMemberAndIds(member, ids);
} // TemplateService.java
@Transactional
public void deleteByMemberAndIds(Member member, List<Long> ids) {
if (ids.size() != new HashSet<>(ids).size()) {
throw new CodeZapException(HttpStatus.BAD_REQUEST, "삭제하고자 하는 템플릿 ID가 중복되었습니다.");
}
ids.forEach(id -> deleteById(member, id));
} 모든 삭제 로직 전에 검증이 우선되어야 한다고 생각하여 아래와 같이 변경을 제안합니다. TO-BE// TemplateApplicationService.java
@Transactional
public void deleteByMemberAndIds(Member member, List<Long> ids) {
if (ids.size() != new HashSet<>(ids).size()) {
throw new CodeZapException(HttpStatus.BAD_REQUEST, "삭제하고자 하는 템플릿 ID가 중복되었습니다.");
}
thumbnailService.deleteByTemplateIds(ids);
sourceCodeService.deleteByIds(ids);
templateTagService.deleteByIds(ids);
templateService.deleteByMemberAndIds(member, ids);
} |
사용되지 않는 메소드
public ExploreTemplatesResponse findAll() {
return ExploreTemplatesResponse.from(thumbnailRepository.findAll());
} 외에도 사용되지 않는 메소드들이 다소 있는 것 같습니다. (여기는 몰리가 추가함) 초롱의 이슈와 동일한 이유로 TemplateTagService도 변경이 필요합니다~ TemplateTagService의 getTemplateIdContainTagIds이 사용되고 있지 않습니다. public List<Long> getTemplateIdContainTagIds(List<Long> tagIds) {
if (tagIds.isEmpty()) {
throw new CodeZapException(HttpStatus.BAD_REQUEST, "태그 ID가 0개입니다. 필터링 하지 않을 경우 null로 전달해주세요.");
}
tagIds.forEach(this::validateTagId);
return templateTagRepository.findAllTemplateIdInTagIds(tagIds, tagIds.size());
}
private void validateTagId(Long tagId) {
if (!tagRepository.existsById(tagId)) {
throw new CodeZapException(HttpStatus.NOT_FOUND, "식별자 " + tagId + "에 해당하는 태그가 존재하지 않습니다.");
}
} |
탬플릿 태그 삭제로 사용되지 않는 태그가 있는 경우일단 TemplateTagService는 TagService 용도로도 사용되고 있습니다. (Tag 목록 조회 등등) ex) AS-IS// TemplateTagService.java
public void deleteByIds(List<Long> templateIds) {
templateIds.forEach(templateTagRepository::deleteAllByTemplateId);
} TO-BE// TemplateTagApplicationService.java
public void deleteByIds(List<Long> templateIds) {
List<Tag> notUsedTags = templateTagService.deleteAllByTemplate(templateIds);
tagService.deleteIfNotUsed(notUsedTags);
}
// TemplateTagService.java
public void deleteAndCollectUnused(List<Long> templateIds) {
// 1. 태그로 템플릿 테그 조회
// 2. 템플릿 태그 삭제 및 태그 목록 반환
// 3. 태그들 중 다른 템플릿에 사용되는 태그들 제외
// 4. 사용되지 않는 태그 목록 반환
}
// TagService.java
public void deleteIfNotUsed(List<Tag> tags) {
// 1. 태그 삭제
} |
(해당 코멘트 관련) |
FindTemplateResponse` 클래스명 변경 제안
해당 DTO가 사용되는 모든 메서드명의 prefix가 get인 반면, 클래스명dml prefix가 Find여서 가독성을 저해합니다. (켬미가 이어씀) find vs get vs fetch 메서드명 통일조회 메서드의 prefix를 통일하면 좋을 것 같아요
MemberCategoryApplicationService
CategoryService
MemberService
TemplateTagService
TemplateApplicationService
MemberTemplateApplicationService
SourceCodeService
TemplateService
ThumbnailService
|
SourceCode 삭제 or 조회
|
엔티티로 조회하는 기능 수정
|
📌 어떤 기능을 리팩터링 하나요?
추가로 다른 도메인에서도 Service Layer 리팩토링이 필요한 부분이 있다면 코멘트로 이를 남겨주시고 이슈 본문의 TODO에 추가해주세요.
TODO
The text was updated successfully, but these errors were encountered: