-
Notifications
You must be signed in to change notification settings - Fork 0
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
유저 탈퇴시 정상적으로 데이터 삭제되지 않는 버그 개선 / 구조 개선 / 테스트 코드 추가 / 일부 authentication 처리 로직 개선 #109
Conversation
@@ -35,7 +35,7 @@ public class Category extends BaseEntity { | |||
@Column(nullable = false) | |||
private String name; | |||
|
|||
@OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true) | |||
@OneToMany(mappedBy = "category", cascade = CascadeType.REMOVE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CascadeType을 Remove로 통일하였습니다.
현재는 CascadeType.Persist 등을 이용한 코드가 없고, 삭제를 목적으로 cascadetype을 사용했으므로, Remove로 처리하는 것이 좋다고 판단하였습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
적절한 판단이라고 생각해요👍👍 처음에 ALL로 둔 이유는 상위 객체의 메서드를 이용해서 하위 객체를 저장할 수도 있겠다 싶겠다는 맥락이 있었는데요.. 지금 저희 코드를 보면 Remove만 주는 것이 좋아보이네요!
@@ -44,7 +44,7 @@ public class Exhibit extends BaseEntity { | |||
@JoinColumn(name = "category_id", nullable = false) | |||
private Category category; | |||
|
|||
@OneToMany(mappedBy = "exhibit", cascade = CascadeType.ALL, orphanRemoval = true) | |||
@OneToMany(mappedBy = "exhibit", cascade = CascadeType.REMOVE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CascadeType을 Remove로 통일하였습니다.
현재는 CascadeType.Persist 등을 이용한 코드가 없고, 삭제를 목적으로 cascadetype을 사용했으므로, Remove로 처리하는 것이 좋다고 판단하였습니다.
@@ -27,5 +27,8 @@ public interface CategoryRepository extends JpaRepository<Category, Long> { | |||
int countCategoriesByUser(@Param("user") User user); | |||
|
|||
List<Category> findCategoriesByUserOrderBySequence(User user); | |||
|
|||
@Modifying(clearAutomatically = true) | |||
void deleteAllByUser(User user); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
특정 유저에 대한 카테고리 데이터를 모두 삭제하는 레포지토리 메소드 입니다. cascade 설정이 되어있어, 해당 메소드가 처리될때, 해당하는 전시, 작품, 태그도 삭제됩니다.
// Long userId = Long.parseLong(authentication.getName()); | ||
Long userId = 1L; | ||
|
||
Long userId = getUserId(authentication); | ||
return ResponseEntity.ok().body(userThumbnailService.getUserThumbnail(userId)); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
기존에는 개발상 편의를 위해 단순히 1L으로 처리하였으나, 런칭을 위해, 테스트 계정이 아닌 요청은 정상적으로 처리하도록 메소드 적용하였습니다.
user.name = name; | ||
user.profileImage = profileImage; | ||
return user; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
테스트상 편의를 위해 id를 받아 User를 생성할 수 있도록 생성자를 추가하였습니다. 빌더패턴을 써도 좋지 않을까 하는 생각이 들긴했으나, 시간상 생략하였습니다.
public static final User TEST_USER = User.create("test-uid", "test-name", "test-profile"); | ||
public static final User TEST_SAVED_USER = User.create(1L, "test-uid", "test-name", | ||
"test-profile"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
테스트상 편의를 위해 유저를 static으로 선언하였습니다. 1L의 id를 갖는 다는 것을 보장하게 하고 싶었습니다.
|
||
@DataJpaTest | ||
@AutoConfigureTestDatabase(replace = Replace.NONE) | ||
public class UserRepositoryTest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
User Repository Unit Test를 추가하였습니다.
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Transactional | ||
@SpringBootTest | ||
@ExtendWith(MockitoExtension.class) | ||
class UserServiceTest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
기존의 테스트를 유닛테스트로 변경하였습니다.
|
||
assertThat(em.find(User.class, user.getId()).getName()).isEqualTo("sample-name-2"); | ||
assertThatThrownBy(() -> userService.updateUserName(user.getId(), "new-name")).isInstanceOf( | ||
UserNotFoundException.class); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일단은 시간상 유저 닉네임 변경 서비스만 유닛테스트로 추가하였습니다.
|
||
assertThat(captor.getValue()).isEqualTo(expectedUid); | ||
} | ||
// TODO: delete 서비스 메소드가, jwtService.withDraw를, FirebaseAuth.deleteUser를 잘 호출하는지 확인하는 테스트 추가 필요 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mockito에 spy를 쓰면 정상적으로 호출하였는지, 적용할 수 있을 텐데 런칭 코앞이라 못했습니다 흑흑
@le2sky 코드 리뷰리뷰 요청요청 이요~! 🤩🤩🤩 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
확인했습니다! 저는 처음에 컨트롤러 테스트를 servie에 대한 통합 테스트로 대체해도 괜찮지 않을까? 생각했었는데 민지님 코드 보고 생각이 많이 달라졌어요!
- controller => 통합 테스트
- service => 단위 테스트
지금 서비스 테스트 코드 중에서 단위 테스트로 분리할 수 있는 경우가 있거든요? 예를 들어 , "~~~하면 예외를 발생.." 처럼 예외 발생 검증 코드는 단위 테스트로 분리가 가능할 것 같아요! 다른 예로는 기존 delete 테스트는 controller 통합 테스트 + service#delete의 단위 테스트로 분리해서 생각할 수 있겠네요! 덕분에 시야가 넓어졌어요 🙇♂️🙇♂️
@@ -35,7 +35,7 @@ public class Category extends BaseEntity { | |||
@Column(nullable = false) | |||
private String name; | |||
|
|||
@OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true) | |||
@OneToMany(mappedBy = "category", cascade = CascadeType.REMOVE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
적절한 판단이라고 생각해요👍👍 처음에 ALL로 둔 이유는 상위 객체의 메서드를 이용해서 하위 객체를 저장할 수도 있겠다 싶겠다는 맥락이 있었는데요.. 지금 저희 코드를 보면 Remove만 주는 것이 좋아보이네요!
@@ -42,10 +45,12 @@ public CreateUserResponseDto register(String uid, String username, String pictur | |||
} | |||
|
|||
@Transactional | |||
public void delete(Long id, JwtService jwtService) { | |||
public void delete(Long id) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
controller에서 이미 jwtService를 주입받고 있어서 service를 전달해줬던 맥락이 있었습니다! 저도 이부분이 찜찜하긴 했는데요... controller의 jwtService에 대한 코드를 userService 내부로 이동시키면 좀 더 좋을 것 같다는 생각이 들었어요!
userRepository.deleteById(id); | ||
|
||
categoryRepository.deleteAllByUser(user); | ||
userRepository.delete(user); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이렇게 구현하셨군요 고생하셨습니다! 😀😀 참고로 처음에 회원 탈퇴할 때, 연관 데이터 삭제를 구현하지 않았던 맥락을 설명드리자면요... (일단 user에서 casecade로 지우는게 어려운지 몰랐었음..!)
아래 두가지 방법에 대해서 :
- user에서 영속성 전이(첫 시도, 복잡하고 잘안됨)
- user에서 다른 패키지에 삭제 요청(현재)
어떤 문제가 있냐면, 패키지간 의존성 사이클
이 발생해요! categoryService -> userService -> categoryService.. 비슷한 맥락으로 userThumnailService도 현재 패키지간의 의존성 사이클이 발생합니다!
지금 당장으로는 개선할 방법이 없어보이지만, 저번에 공유드렸던 우아한객체지향 세미나에서 이러한 문제를 이벤트를 사용해서 처리했던 걸 봤어요!
@@ -22,7 +22,7 @@ public enum ErrorCode { | |||
FIREBASE_INVALID_TOKEN(401, "F004", "JWT 토큰 파싱에 실패했습니다."), | |||
FIREBASE_REVOKED_TOKEN(401, "F005", "취소된 토큰입니다."), | |||
FIREBASE_NOT_FOUND_USER(404, "F006", "존재하지 않는 파이어베이스 사용자입니다."), | |||
FIREBASE_INVALID_UID(401, "F007", "유요하지 않은 파이어베이스 식별자입니다."), | |||
FIREBASE_INVALID_UID(401, "F007", "유효하지 않은 파이어베이스 식별자입니다."), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
창피하네요.. 바꿔주셔서 감사합니다.. ㅎㅎㅎ
protected MockMvc mvc; | ||
@Autowired | ||
protected ObjectMapper objectMapper; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Base Class 만드신거 너무 좋은거 같아요! 제 생각에는 BaseIntegrationTest는 테스트 대상이 아니기 때문에 abstract가 맞을거 같아요!
구현 내용
API 수정
테스트
기타
관련 이슈
close #108
references with #86
구현 내용