728x90
1. 도입: 유저 생성은 단순하지 않다
우리 서비스는 회원가입과 동시에 유저 프로필이 생성되어야 한다.
그런데 이 프로필은 Auth 모듈에서 만들어지는 게 아니라, Kafka 이벤트를 수신해서 생성된다.
또한 프로필 삭제에도 두 가지 방식이 필요했다:
- Soft Delete → 유저가 탈퇴했지만 이력은 남겨야 할 때
- Hard Delete → 유저 데이터를 완전히 삭제해야 할 때 (예: 개인정보 삭제 요청)
2. 유저 프로필 생성 흐름 (Kafka 기반)
흐름도
회원가입 완료
→ Kafka: UserCreatedEvent 발행
→ user-profile-service: 이벤트 수신
→ UserProfile 생성
→ 관심사/장르 기본 세팅 (optional)
이벤트 예시: UserCreatedEvent
public class UserCreatedEvent {
private Long userId;
private String email;
private String profileImageUrl;
}
수신 후 생성 처리
@Transactional
public void createProfile(UserCreatedEvent event) {
if (userProfileRepository.existsById(event.getUserId())) return;
UserProfile profile = UserProfile.builder()
.userId(event.getUserId())
.nickname("user_" + UUID.randomUUID().toString().substring(0, 6))
.email(event.getEmail())
.profileImageUrl(event.getProfileImageUrl())
.createdAt(LocalDateTime.now())
.build();
userProfileRepository.save(profile);
}
참고: nickname은 최초 생성 시 임시 UUID 기반 자동 발급
3. 삭제 정책: Soft vs Hard
Soft Delete가 필요한 이유
- 유저가 일시적으로 탈퇴한 경우
- 신고/정지에 따른 비공개 처리
- 변경 이력이나 활동 기록을 보존하고 싶을 때
@Transactional
public void softDelete(Long userId) {
UserProfile profile = findActiveProfile(userId);
if (profile.isDeleted()) throw new UserProfileException(ALREADY_DELETED);
profile.setDeletedAt(LocalDateTime.now());
}
Hard Delete가 필요한 이유
- 개인정보 삭제 요청 (GDPR/ISMS 대응)
- 테스트 계정 등 완전한 제거 필요
- 연관 테이블까지 완전 삭제 필요
@Transactional
public void hardDelete(Long userId) {
userInterestRepository.deleteByUserId(userId);
userGenreRepository.deleteByUserId(userId);
userProfileRepository.deleteById(userId);
}
4. 관심사 / 장르 구조 설계
연관 구조 흐름
UserProfile
├── UserInterest (관심사 enum)
└── UserGenre (장르 enum)
각 유저는 다수의 관심사와 장르를 가질 수 있다.
이를 enum 기반으로 설계함으로써 고정 도메인에 대한 빠른 필터링과 조회를 가능하게 했다.
UserInterest
@Entity
public class UserInterest {
@Id
@GeneratedValue
private Long id;
private Long userId;
@Enumerated(EnumType.STRING)
private Interest interest; // ex: DRUM, VOCAL, REACT
private LocalDateTime createdAt;
}
UserGenre는Genreenum만 바꿔서 동일하게 구성
5. 삭제 시 연관 데이터도 함께 처리
Hard delete를 수행할 경우에는 반드시
user_interest,user_genre도 함께 삭제되어야 한다.
이때, 두 가지 방식 중 하나를 선택해야 한다:
① Cascade 설정 (JPA 연관 자동 삭제)
@OneToMany(mappedBy = "userId", cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserInterest> interests;
장점: 자동 처리
단점: mappedBy가 Long userId라면 OneToMany 매핑 불가 → 객체 참조로 바꿔야 함
② 명시적 삭제 처리 (우리가 선택한 방식)
@Transactional
public void hardDelete(Long userId) {
userInterestRepository.deleteByUserId(userId);
userGenreRepository.deleteByUserId(userId);
userProfileRepository.deleteById(userId);
}
장점: 쿼리 명확, 실제 삭제되는 대상 예측 가능
단점: 중복 삭제 로직 필요
6. 실무 기준 삭제 정책 비교표
| 항목 | Soft Delete | Hard Delete |
|---|---|---|
| 이력 보존 | 적합 | 불가능 |
| GDPR 대응 | 불가능 | 적합 |
| 테스트 계정 삭제 | 불가능 | 삭제됨 |
| 관심사/장르 삭제 처리 | 불필요 | 명시적 처리 필요 |
우리는 유저 요청/운영 정책에 따라 둘 다 제공하기로 했다.
7. 정리 및 다음 편 예고
이번 글에서는 유저 프로필의 생성/삭제 흐름과 연관 테이블 관리 방식을 다뤘다.
Kafka 기반 이벤트 처리, soft/hard delete 정책 차이, 관심사/장르 연관 처리까지
실전 서비스 수준의 설계/구현 기준으로 정리했다.
다음 편 예고:
3편. "프로필 수정 기능과 변경 이력 추적 전략"
- 어떤 필드를 수정 가능하게 할 것인가
UpdatableProfileColumnenum 설계- 변경 이력(
UserProfileHistory) 저장 전략 - 감사(Audit) 기반 설계 고민
728x90
'BindProject' 카테고리의 다른 글
| [Backend] 유저 프로필 모듈 구현4편 - DSL 기반 검색 기능과 응답 모델 설계 (1) | 2025.06.23 |
|---|---|
| [Backend] 유저 프로필 모듈 구현 3편. 유저 프로필 변경 이력 추적 설계와 구현 (0) | 2025.06.22 |
| [Backend] 유저 프로필 모듈 구현 1편. 유저 프로필 도입과 설계 구조: Enum 기반 프로필 시스템의 선택과 대안 비교 (0) | 2025.06.22 |
| [Backend] 유저 활동 로그 수집 모듈 개발기 5편 : 마무리 회고 (0) | 2025.06.22 |
| [Backend] 유저 활동 로그 수집 모듈 개발기 4편 : 로그 수집 서비스 구현 (0) | 2025.06.22 |