왜? 분리하였을까
일반 유저와 소셜 유저를 한 테이블에서 관리한다면,
password는 소셜 유저가 사용하지 않는 필드이기 때문에, 소셜 유저를 DB 저장한다면 null이나 임의의 값으로 저장해야한다.
마찬가지로 social_type(네이버, 카카오, 구글)은 일반 회원 유저가 사용하지 않은 필드이기 때문에, 일반 유저를 DB 저장한다면 null이나 임의의 값으로 저장해야한다.
과연 이게 옳은 설계인가? 라는 의문이 들었다.
그래서 아래 그림과 같이 일반 유저 테이블과 소셜 유저 테이블로 나눠보았다.
공통 필드인 user_id, email, username을 가진 유저(user) 추상화 클래스를 일반 유저 클래스(normal_user)와 소셜 유저 클래스(social_user)가 상속받고, 각자 필요한 필드(password, social_type 등...)들을 가지게 하였다.
문제점 각자가 불필요한 필드를 가지지 않게 설계하였지만 두 개의 테이블로 나누었기 때문에 발생한 문제들이 있다.
- user_id만으로 소셜 사용자인지 일반 사용자인지 구분할 수 없다.
- 게시글이나 댓글에 어떤 유저와 연관관계를 맺어야 하는지 모르기 때문에, 단순히 user_id라는 외래키만을 둘 수 밖에 없다.(JPA의 연관관계를 이용할 수 없다.) -> 쿼리로 해결해야 한다.
먼저 1번의 문제점은 id 값을 Long이 아닌 String으로 바꾸고, `normal_1', 'social_2' 이런식으로 id 값을 할당해 앞 문자열을 가지고 소셜 사용자인지 일반 사용자인지 구분할 수 있게 하였다.
문자열 뒤에 붙는 숫자는 시퀀스 테이블을 구현해 객체를 생성할 때, sql을 통해 받아오도록 하여 설정하였다.(MySQL에는 시퀀스 테이블을 지원하지 않아 테이블과 프로시저, 펑션을 이용해 시퀀스 테이블을 구현하였다.)
2번의 문제점은 전 프로젝트를에서, 일부러 연관관계를 형성하지 않고 쿼리만을 이용해서 프로젝트를 개발하여서 조금 더 수월하게 sql을 작성할 수 있었다.
여러 소셜 로그인 서비스가 추가될 때마다, 계속해서 메서드를 추가해야 되기 때문에 너무 한 곳에 집중되는 구조인 것 같아서 팩토리 패턴을 사용해 구현하였다. 지금은 카카오밖에 없지만 추후에 네이버, 구글 등 여러 소셜 서비스가 추가될 때, 이 패턴은 빛을 발할 것 같다.
이번에 회원가입 검증이 추가되면서 단순 길이나 패턴 검증을 넘어 아래와 같은 검증이 추가되었다.
- 비밀번호에 닉네임이 포함이 되지 않는지
- 비밀번호 확인이 비밀번호와 일치하는지 하지만 추가 검증들은 @Valid에서 기본적으로 제공하는 어노테이션으로 해결할 수 없었다.
그러면 컨트롤러받은 DTO를 또 다시 검증하기???? 내 생각엔 @Valid를 통해 검증하고 컨트롤러에 또 다시 검증 로직을 넣어야 하는 구조가 맘에 들지 않았다.(여기저기서 검증하는 느낌?) 그래서 한 곳에서(@Valid)에서 모든 검증을 완료하기 위해 @Valid에 추가 검증에 대한 어노테이션을 구현하였다.
- @PasswordMatchConfirmPassword: 비밀번호와 비밀번호 확인이 일치하는지
- @PasswordNotContainUsername: 비밀번호에 닉네임이 포함이 되지 않는지 그 외
- @PossibleSocialType : 수용 가능한 소셜 서비스인지
이 프로젝트를 진행하면서 여러 고민을 많이 한 탓에 모든 기능을 완벽하게 구현하지 못했다. 하지만 그보다 더 값진 경험들을 많이 한 것 같아서 뜻깊은 프로젝트였다. 팀 프로젝트라면하지 못했을 내용들을 과감하게 했던 것 같다. 그리고 언제나 가장 가장 제일로 어려운 건 이름짓기인건 변함이 없는 것 같다.
-
💬 12/12 - 회원 가입 API
- 닉네임, 비밀번호, 비밀번호 확인을 request에서 전달받기
- 닉네임은 최소 3자 이상, 알파벳 대소문자(a
z, AZ), 숫자(0~9)로 구성하기 - 비밀번호는 최소 4자 이상이며, 닉네임과 같은 값이 포함된 경우 회원가입에 실패로 만들기
- 비밀번호 확인은 비밀번호와 정확하게 일치하기
- 데이터베이스에 존재하는 닉네임을 입력한 채 회원가입 버튼을 누른 경우 "중복된 닉네임입니다." 라는 에러메세지를 response에 포함하기
- 회원 가입 버튼을 누르기 전, 같은 닉네임이 존재하는지 "확인" 버튼을 눌러 먼저 유효성 검증부터 할 수 있도록 해보기
- (챌린지 과제) 데이터베이스에 비밀번호를 평문으로 저장하는 것이 아닌, 단방향 암호화 알고리즘을 이용하여 암호화 해서 저장하도록 하기
- (챌린지 과제) 회원 가입 시, 이메일 혹은 SNS로 인증 번호를 전달 받고 5분 이내에 해당 인증 번호를 검증해야 회원 가입에 성공하도록 해보기 (redis TTL 특징을 좀 더 파악하기 위함.)
-
💬 12/13 - 로그인 API
- 닉네임, 비밀번호를 request에서 전달받기
- 로그인 버튼을 누른 경우 닉네임과 비밀번호가 데이터베이스에 등록됐는지 확인한 뒤, 하나라도 맞지 않는 정보가 있다면 "닉네임 또는 패스워드를 확인해주세요."라는 에러 메세지를 response에 포함하기
- 로그인 성공 시, 로그인에 성공한 유저의 정보를 JWT를 활용하여 클라이언트에게 Body로 전달하기
-
💬 12/14 - 전체 게시글 목록 조회 API
- 제목, 작성자명(nickname), 작성 날짜를 조회하기
- 작성 날짜 기준으로 내림차순 정렬하기
- (챌린지 과제) 전체 조회가 아닌 페이징 조회를 할 수 있도록 해보기
- (챌린지 과제) 페이징 + 커스텀 정렬 기능 구현하기 -> 사용자가 입력한 key와 정렬 기준을 동적으로 입력 받아, 해당 기준에 맞게 데이터를 제공. (예. 작성자명 오름차순 정렬 and 작성 날짜 오름차순 정렬된 결과를 상위 5개만 출력)
-
💬 12/15 - 게시글 작성 API
- 토큰을 검사하여, 유효한 토큰일 경우에만 게시글 작성 가능
- 제목(500자 까지 입력 가능), 작성 내용을 입력하기(5000자 까지 입력 가능)
- (챌린지 과제) 이미지 업로드 가능
-
💬 12/18 - 게시글 조회 API
- 제목, 작성자명(nickname), 작성 날짜, 작성 내용을 조회하기
-
💬 12/19 - 게시글 수정 API
- 토큰을 검사하여, 해당 사용자가 작성한 게시글만 수정 가능
-
💬 12/20 - 게시글 삭제 API
- 토큰을 검사하여, 해당 사용자가 작성한 게시글만 삭제 가능
- (챌린지 과제) 수정된지 90일이 지난 데이터는 자동으로 지우는 스케줄러 기능을 개발해보기. (데이터 삭제 및 백업도 굉장히 중요한 기능)
- LocalTime(+09:00)의 스케줄러가 동작하는 현재 일시 (2023-12-11T11:11:23) 기준으로 90일이 지난 데이터를 지운다.
-
💬 12/21 - 댓글 작성 API
- 게시글과 연관 관계를 가진 댓글 테이블 추가
- 토큰을 검사하여, 유효한 토큰일 경우에만 게시글 작성 가능
- 작성 내용을 입력하기
- 게시글에 대한 좋아요
-
💬 12/22 - 게시글과 댓글 목록 조회 API, 댓글 수정/삭제 API
- 댓글 목록 조회
- (챌린지 과제) 전체 조회가 아닌 페이징 조회를 할 수 있도록 해보기
- (챌린지 과제) 페이징 + 커스텀 정렬 기능 구현하기 -> 사용자가 입력한 key와 정렬 기준을 동적으로 입력 받아, 해당 기준에 맞게 데이터를 제공. (예. 작성자명 오름차순 정렬 and 작성 날짜 오름차순 정렬된 결과를 상위 5개만 출력)
- 게시글 조회 API 호출시 해당 게시글의 댓글 목록도 응답
- 토큰을 검사하여, 해당 사용자가 작성한 댓글만 수정/삭제 가능
- (챌린지 과제) 게시글이 삭제될 때 연관된 댓글도 같이 지우도록 스케줄러 코드 기능 추가
- 댓글 목록 조회








