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

유저 탈퇴시 정상적으로 데이터 삭제되지 않는 버그 개선 / 구조 개선 / 테스트 코드 추가 / 일부 authentication 처리 로직 개선 #109

Merged
merged 22 commits into from
Feb 22, 2023

Conversation

akalswl14
Copy link
Member

@akalswl14 akalswl14 commented Feb 22, 2023

구현 내용

API 수정

  • [DELETE] /user 유저 탈퇴 API : 버그 수정
    • URI의 오타 수정 ( /user/ -> /user )
    • jwtService를 UserService.delete 메소드의 파라미터로 받지 않도록 개선
    • User 엔티티는 다른 엔티티와의 개인 소유 관계가 아니기 때문에, cascade 삭제가 부적합한 것으로 보여, 수동적으로 삭제하도록 개선
    • 해당 유저의 카테고리 데이터를 먼저 삭제하고, 카테고리 삭제시 cascade 삭제로 전시, 작품, 태그가 삭제되도록 함
    • 카테고리 데이터가 삭제된 이후, 유저 데이터를 삭제함
  • 유저 API의 런칭 목적 authentication 처리 로직 교체
    • 기존에는 개발상 편의를 위해 단순히 1L으로 처리하였음
    • 런칭을 위해, 테스트 계정이 아닌 요청은 정상적으로 처리하도록 메소드 적용하여 처리

테스트

  • 통합 테스트 base class 추가
  • 테스트 상 편의를 위해, User 엔티티의 id를 입력하는 생성자 추가
  • 테스트 상 편의를 위해, 테스트용 유저 정적 변수 선언
  • Category Repository Unit Test 추가
  • 유저 기반 카테고리를 삭제하는 CategoryRepository.deleteAllByUser메소드를 검증하는 레포지토리 테스트 추가
  • 카테고리를 삭제하면 전시데이터도 삭제되는지에 대해, CategoryService.delete 메소드를 검증하는 테스트 추가
  • 기존의 User Service Test를 단위 테스트로 처리하도록 변경
  • UserService.updateUserName 메소드의 서비스 단위 테스트 추가
  • 유저 삭제 API의 통합 테스트 추가
  • UserRepository의 단위 테스트 추가
  • UserRepository.delete를 검증하는 레포지토리 단위 테스트 추가

기타

  • 일부 예외의 메세지 오타 수정

관련 이슈

close #108
references with #86

구현 내용

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

@akalswl14 akalswl14 self-assigned this Feb 22, 2023
@@ -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)
Copy link
Member Author

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로 처리하는 것이 좋다고 판단하였습니다.

Copy link
Member

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)
Copy link
Member Author

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);
Copy link
Member Author

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));
}

Copy link
Member Author

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;
}
Copy link
Member Author

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");
}
Copy link
Member Author

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 {
Copy link
Member Author

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 {
Copy link
Member Author

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);
}
Copy link
Member Author

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를 잘 호출하는지 확인하는 테스트 추가 필요
Copy link
Member Author

Choose a reason for hiding this comment

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

mockito에 spy를 쓰면 정상적으로 호출하였는지, 적용할 수 있을 텐데 런칭 코앞이라 못했습니다 흑흑

@akalswl14 akalswl14 added 버그 버그 수정 기능 구현 기능 구현 테스트 테스트 관련 기능 수정 허걱 labels Feb 22, 2023
@akalswl14 akalswl14 merged commit 544dfd9 into develop Feb 22, 2023
@akalswl14
Copy link
Member Author

@le2sky 코드 리뷰리뷰 요청요청 이요~! 🤩🤩🤩

Copy link
Member

@le2sky le2sky left a 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)
Copy link
Member

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) {
Copy link
Member

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);
Copy link
Member

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", "유효하지 않은 파이어베이스 식별자입니다."),
Copy link
Member

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;
}
Copy link
Member

Choose a reason for hiding this comment

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

Base Class 만드신거 너무 좋은거 같아요! 제 생각에는 BaseIntegrationTest는 테스트 대상이 아니기 때문에 abstract가 맞을거 같아요!

@akalswl14 akalswl14 deleted the feature/109_v2 branch April 27, 2023 05:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
기능 구현 기능 구현 기능 수정 허걱 버그 버그 수정 테스트 테스트 관련
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants