Skip to content

Commit 794d236

Browse files
authored
[volume-1] 회원가입, 내 정보 조회, 포인트 조회, 포인트 충전 기능 구현 (#22)
* Feature/user (#1) * test: User 단위테스트 추가 * feat: User 도메인 구현 * test: 회원 가입 통합테스트 추가 * feat: 회원가입 서비스 로직 구현 * test: 회원가입 E2E 테스트 추가 * feat: 회원가입 API 구현 * test: gender필드를 저장할 수 있도록 테스트 코드 수정 * refactor: User도메인에 성별 필드 추가 * test: 회원 정보 조회 통합 테스트 작성 * feat: 회원 정보 조회 서비스 로직 구현 * test: 회원 정보 조회 E2E 테스트 작성 * feat: 회원 정보 조회 API 추가 * Feature/point (#2) * test: 회원가입 관련 테스트 코드가 SignUpFacade를 참조하도록 수정 * refactor: 회원가입을 처리하는 SignUpFacade 구현 * test: 포인트 조회 통합테스트 추가 * feat: 포인트 조회 서비스 로직 구현 * test: 포인트 조회 E2E 테스트 코드 추가 * feat: 포인트 조회 API 로직 추가 * test: 포인트 충전 단위 테스트 추가 * feat: 포인트 충전 도메인 로직 추가 * test: 포인트 충전 테스트 코드 추가 * feat: 포인트 충전 서비스 로직 추가 * test: 포인트 충전 E2E 테스트 코드 추가 * feat: 포인트 충전 API 추가 * docs: 회원가입, 내 정보 조회, 포인트 조회, 포인트 충전 기능 관련 docstring 추가 (#3)
1 parent d5ec397 commit 794d236

28 files changed

Lines changed: 1857 additions & 12 deletions
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.loopers.application.signup;
2+
3+
import com.loopers.domain.point.PointService;
4+
import com.loopers.domain.user.Gender;
5+
import com.loopers.domain.user.User;
6+
import com.loopers.domain.user.UserService;
7+
import jakarta.transaction.Transactional;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.stereotype.Component;
10+
11+
/**
12+
* 회원가입 파사드.
13+
* <p>
14+
* 회원가입 시 사용자 생성과 포인트 초기화를 조율하는
15+
* 애플리케이션 서비스입니다.
16+
* 트랜잭션 경계를 관리하여 데이터 일관성을 보장합니다.
17+
* </p>
18+
*
19+
* @author Loopers
20+
* @version 1.0
21+
*/
22+
@RequiredArgsConstructor
23+
@Component
24+
public class SignUpFacade {
25+
private final UserService userService;
26+
27+
private final PointService pointService;
28+
29+
/**
30+
* 회원가입을 처리합니다.
31+
* <p>
32+
* 사용자를 생성하고 초기 포인트(0)를 부여합니다.
33+
* 전체 과정이 하나의 트랜잭션으로 처리됩니다.
34+
* </p>
35+
*
36+
* @param userId 사용자 ID
37+
* @param email 이메일 주소
38+
* @param birthDateStr 생년월일 (yyyy-MM-dd)
39+
* @param gender 성별
40+
* @return 생성된 사용자 정보
41+
* @throws com.loopers.support.error.CoreException 유효성 검증 실패 또는 중복 ID 존재 시
42+
*/
43+
@Transactional
44+
public SignUpInfo signUp(String userId, String email, String birthDateStr, Gender gender) {
45+
User user = userService.create(userId, email, birthDateStr, gender);
46+
pointService.create(user, 0L);
47+
return SignUpInfo.from(user);
48+
}
49+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.loopers.application.signup;
2+
3+
import com.loopers.domain.user.Gender;
4+
import com.loopers.domain.user.User;
5+
6+
import java.time.LocalDate;
7+
8+
/**
9+
* 회원가입 결과 정보를 담는 레코드.
10+
* <p>
11+
* User 도메인 엔티티로부터 생성된 불변 데이터 전송 객체입니다.
12+
* </p>
13+
*
14+
* @param id 사용자 엔티티 ID
15+
* @param userId 사용자 ID
16+
* @param email 이메일 주소
17+
* @param birthDate 생년월일
18+
* @param gender 성별
19+
* @author Loopers
20+
* @version 1.0
21+
*/
22+
public record SignUpInfo(Long id, String userId, String email, LocalDate birthDate, Gender gender) {
23+
/**
24+
* User 엔티티로부터 SignUpInfo를 생성합니다.
25+
*
26+
* @param user 변환할 사용자 엔티티
27+
* @return 생성된 SignUpInfo
28+
*/
29+
public static SignUpInfo from(User user) {
30+
return new SignUpInfo(
31+
user.getId(),
32+
user.getUserId(),
33+
user.getEmail(),
34+
user.getBirthDate(),
35+
user.getGender()
36+
);
37+
}
38+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.loopers.domain.point;
2+
3+
import com.loopers.domain.BaseEntity;
4+
import com.loopers.domain.user.User;
5+
import com.loopers.support.error.CoreException;
6+
import com.loopers.support.error.ErrorType;
7+
import jakarta.persistence.*;
8+
import lombok.AccessLevel;
9+
import lombok.Getter;
10+
import lombok.NoArgsConstructor;
11+
12+
/**
13+
* 포인트 도메인 엔티티.
14+
* <p>
15+
* 사용자의 포인트 잔액을 관리하며, 포인트 충전 기능을 제공합니다.
16+
* User와 일대일 관계를 맺고 있습니다.
17+
* </p>
18+
*
19+
* @author Loopers
20+
* @version 1.0
21+
*/
22+
@Entity
23+
@Table(name = "point")
24+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
25+
@Getter
26+
public class Point extends BaseEntity {
27+
@OneToOne(fetch = FetchType.LAZY, optional = false)
28+
@JoinColumn(
29+
name = "user_id",
30+
referencedColumnName = "id",
31+
foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)
32+
)
33+
private User user;
34+
35+
@Column(name = "balance", nullable = false)
36+
private Long balance;
37+
38+
/**
39+
* Point 인스턴스를 생성합니다.
40+
*
41+
* @param user 포인트 소유자
42+
* @param balance 초기 잔액 (null인 경우 0으로 초기화)
43+
*/
44+
public Point(User user, Long balance) {
45+
this.user = user;
46+
this.balance = balance != null ? balance : 0L;
47+
}
48+
49+
/**
50+
* Point 인스턴스를 생성하는 정적 팩토리 메서드.
51+
*
52+
* @param user 포인트 소유자
53+
* @param balance 초기 잔액
54+
* @return 생성된 Point 인스턴스
55+
*/
56+
public static Point of(User user, Long balance) {
57+
return new Point(user, balance);
58+
}
59+
60+
/**
61+
* 포인트를 충전합니다.
62+
*
63+
* @param amount 충전할 포인트 금액 (0보다 커야 함)
64+
* @throws CoreException amount가 null이거나 0 이하일 경우
65+
*/
66+
public void charge(Long amount) {
67+
validateChargeAmount(amount);
68+
this.balance += amount;
69+
}
70+
71+
/**
72+
* 충전 금액의 유효성을 검증합니다.
73+
*
74+
* @param amount 검증할 충전 금액
75+
* @throws CoreException amount가 null이거나 0 이하일 경우
76+
*/
77+
private void validateChargeAmount(Long amount) {
78+
if (amount == null || amount <= 0) {
79+
throw new CoreException(ErrorType.BAD_REQUEST, "포인트는 0보다 큰 값이어야 합니다.");
80+
}
81+
}
82+
}
83+
84+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.loopers.domain.point;
2+
3+
/**
4+
* Point 엔티티에 대한 저장소 인터페이스.
5+
* <p>
6+
* 포인트 정보의 영속성 계층과의 상호작용을 정의합니다.
7+
* </p>
8+
*
9+
* @author Loopers
10+
* @version 1.0
11+
*/
12+
public interface PointRepository {
13+
/**
14+
* 포인트를 저장합니다.
15+
*
16+
* @param point 저장할 포인트
17+
* @return 저장된 포인트
18+
*/
19+
Point save(Point point);
20+
21+
/**
22+
* 사용자 ID로 포인트를 조회합니다.
23+
*
24+
* @param userId 조회할 사용자 ID
25+
* @return 조회된 포인트, 없으면 null
26+
*/
27+
Point findByUserId(String userId);
28+
}
29+
30+
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.loopers.domain.point;
2+
3+
import com.loopers.domain.user.User;
4+
import com.loopers.support.error.CoreException;
5+
import com.loopers.support.error.ErrorType;
6+
import lombok.RequiredArgsConstructor;
7+
import org.springframework.stereotype.Component;
8+
import org.springframework.transaction.annotation.Transactional;
9+
10+
/**
11+
* 포인트 도메인 서비스.
12+
* <p>
13+
* 포인트 생성, 조회, 충전 등의 도메인 로직을 처리합니다.
14+
* </p>
15+
*
16+
* @author Loopers
17+
* @version 1.0
18+
*/
19+
@RequiredArgsConstructor
20+
@Component
21+
public class PointService {
22+
private final PointRepository pointRepository;
23+
24+
/**
25+
* 새로운 포인트를 생성합니다.
26+
*
27+
* @param user 포인트 소유자
28+
* @param balance 초기 잔액
29+
* @return 생성된 포인트
30+
*/
31+
public Point create(User user, Long balance) {
32+
Point point = Point.of(user, balance);
33+
return pointRepository.save(point);
34+
}
35+
36+
/**
37+
* 사용자 ID로 포인트를 조회합니다.
38+
*
39+
* @param userId 조회할 사용자 ID
40+
* @return 조회된 포인트, 없으면 null
41+
*/
42+
public Point findByUserId(String userId) {
43+
return pointRepository.findByUserId(userId);
44+
}
45+
46+
/**
47+
* 사용자의 포인트를 충전합니다.
48+
* <p>
49+
* 트랜잭션 내에서 실행되어 데이터 일관성을 보장합니다.
50+
* </p>
51+
*
52+
* @param userId 사용자 ID
53+
* @param amount 충전할 금액 (0보다 커야 함)
54+
* @return 충전된 포인트
55+
* @throws CoreException 포인트를 찾을 수 없거나 충전 금액이 유효하지 않을 경우
56+
*/
57+
@Transactional
58+
public Point charge(String userId, Long amount) {
59+
Point point = pointRepository.findByUserId(userId);
60+
if (point == null) {
61+
throw new CoreException(ErrorType.NOT_FOUND, "포인트를 찾을 수 없습니다.");
62+
}
63+
point.charge(amount);
64+
return pointRepository.save(point);
65+
}
66+
}
67+
68+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.loopers.domain.user;
2+
3+
/**
4+
* 사용자의 성별을 나타내는 열거형.
5+
*
6+
* @author Loopers
7+
* @version 1.0
8+
*/
9+
public enum Gender {
10+
MALE,
11+
FEMALE
12+
}
13+
14+

0 commit comments

Comments
 (0)