728x90
-
1. 왜 변경 이력이 필요한가?
실무 시나리오 예시
- 유저가 닉네임을 공격적인 단어로 바꾸고 신고당함
→ 이전 닉네임을 알아야 조치 가능 - 성별/지역 정보를 바꾸고 반복적으로 다른 유저를 속이는 경우
→ 수정 이력 기반으로 사용자 패턴 확인 - 운영자가 사용자 지원을 위해 변경 기록을 조회해야 할 때
2. 핵심 설계 구조
필드 추적 기준은 enum으로 정의
우리는 수정 가능한 필드를 코드 상에서 명확하게 열거(enum) 하기로 했다.
이 방식은 추적 가능성과 가독성을 높여주며, 이후 검색 기능에도 도움이 된다.
public enum UpdatableProfileColumn {
NICKNAME("닉네임"),
INTRODUCTION("소개글"),
PROFILE_IMAGE("프로필 이미지"),
LOCATION("지역"),
GENDER("성별"),
PHONE_NUMBER("전화번호"),
EMAIL("이메일");
private final String displayName;
// ...생략
}
변경 이력 엔티티 설계
@Entity
@Table(name = "user_profile_history")
public class UserProfileHistory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
@Enumerated(EnumType.STRING)
private UpdatableProfileColumn fieldChanged;
private String oldValue;
private String newValue;
private LocalDateTime changedAt;
private String changedBy; // "USER" or "SYSTEM"
public static UserProfileHistory of(Long userId, UpdatableProfileColumn column, String oldValue, String newValue, String by) {
return new UserProfileHistory(null, userId, column, oldValue, newValue, LocalDateTime.now(), by);
}
}
3. 서비스 구현 흐름
구조 개요
유저가 수정 요청
→ 변경 필드 비교
→ 기존값 ≠ 신규값 → 변경 이력 저장
→ 프로필 엔티티 반영
서비스 코드 (핵심 발췌)
@Transactional
public void updateProfile(Long userId, UserProfileUpdateRequest request) {
UserProfile profile = findActiveProfile(userId);
if (request.getNickname() != null && !request.getNickname().equals(profile.getNickname())) {
validateNickname(request.getNickname());
saveHistory(userId, NICKNAME, profile.getNickname(), request.getNickname());
profile.setNickname(request.getNickname());
}
// ... 성별, 지역, 이미지, 소개글, 번호도 동일하게 처리
profile.setUpdatedAt(LocalDateTime.now());
}
이력 저장 로직
private void saveHistory(Long userId, UpdatableProfileColumn column, String oldValue, String newValue) {
historyRepository.save(UserProfileHistory.of(userId, column, oldValue, newValue, "USER"));
}
실제 사용자는
"USER"로 저장하고, Kafka 기반 등 시스템 자동 갱신은"SYSTEM"으로 처리 가능
4. 닉네임 유효성 검사 + 욕설 필터
잘못된 닉네임 방지
- 2~20자 제한
- 공백 금지
- 금지어 포함 방지 (욕설, 성적인 단어 등)
private void validateNickname(String nickname) {
if (nickname.length() < 2) throw new UserProfileException(NICKNAME_TOO_SHORT);
if (nickname.length() > 20) throw new UserProfileException(NICKNAME_TOO_LONG);
if (nickname.contains(" ")) throw new UserProfileException(NICKNAME_CONTAINS_SPACES);
wordFilterService.validate(nickname); // 욕설 감지
}
wordFilterService는 퍼블릭 모듈에 위치한 재사용 가능한 욕설 필터 서비스
5. 변경 이력 조회 API 설계 (예시)
쿼리 조건 예시
userId기준으로 조회- 특정 필드(
NICKNAME,EMAIL등)만 조회 - 변경 시점으로 필터링
DSL 기반
UserProfileQueryRepository로 구현 가능
6. 실무 기준 설계 이점 요약
| 요소 | 설계 결정 | 장점 |
|---|---|---|
| 필드 구분 | UpdatableProfileColumn |
수정 대상 명확, 추적 용이 |
| 이력 저장 방식 | DB 엔티티로 명시적 저장 | 감사(Audit) 용이 |
| 수정자 기록 | "USER"/"SYSTEM" 명시 |
자동 수정과 사용자 구분 가능 |
| 닉네임 검사 | 길이, 공백, 금지어 모두 검사 | UX + 보안 강화 |
| DSL 조회 기반 | 이력 분석 및 모니터링 용이 | 유저 행동 분석 가능 |
7. 정리 및 다음 편 예고
이번 글에서는 유저 프로필 변경에 대한 정확한 필드 추적 방식,
DB 기반 감사 로그 저장, 닉네임 유효성 및 욕설 필터링 처리까지 다뤘다.
변경 이력 추적은 단순 UX를 넘어서, 보안·운영·데이터 분석을 위한 핵심 시스템이라는 점을 기억하자.
다음 편 예고:
4편. “유저 탐색 및 필터링 기능 구현 - 닉네임, 지역, 장르, 성별 기반 DSL 설계”
- 검색 조건 DSL 구성
- 닉네임/지역/장르/성별 필터링 처리
- 페이징과 정렬 전략
- 배치 연동을 위한 DTO 설계
728x90
'BindProject' 카테고리의 다른 글
| [Backend] 유저 프로필 모듈 구현4-1편 N+1 문제 완전 정복: 유저 프로필 조회 최적화 여정 (1) | 2025.06.23 |
|---|---|
| [Backend] 유저 프로필 모듈 구현4편 - DSL 기반 검색 기능과 응답 모델 설계 (1) | 2025.06.23 |
| [Backend] 유저 프로필 모듈 구현 2편. 유저 프로필 도입과 설계 구조: 유저 프로필 생성/삭제 처리와 관심사/장르 연관 구조 설계 (1) | 2025.06.22 |
| [Backend] 유저 프로필 모듈 구현 1편. 유저 프로필 도입과 설계 구조: Enum 기반 프로필 시스템의 선택과 대안 비교 (0) | 2025.06.22 |
| [Backend] 유저 활동 로그 수집 모듈 개발기 5편 : 마무리 회고 (0) | 2025.06.22 |