이번에는 걸음 수 -> 포인트 -> 쿠폰 구매로 이어지는 로직을 구현해볼 예정이다.
걸음 수 -> 포인트
100보당 1포인트의 기준을 세우고 진행한다!
이렇게 되려면 100보 일 때 서버에 저장될 때, 포인트가 증가하고
150보 일 때는 그대로, 200보가 넘어가면 다시 1포인트가 증가하는 식으로 가야한다.
만보기는 누적의 개념이기 때문에 변동량을 더해주는 개념으로 접근하면 된다.
- Member 엔티티
- 회원의 포인트는 객체가 스스로 올린다.
@Entity
@NoArgsConstructor
@Getter
public class Member {
@Id
@Column(name = "member_id")
private String id;
private String username;
private String nickname;
private int userPoint;
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<PointHistory> pointHistories = new ArrayList<>();
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<StepInfo> stepInfos = new ArrayList<>();
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<CouponInfo> couponInfos = new ArrayList<>();
public Member(String id, String username, String nickname, int userPoint){
this.id = id;
this.username = username;
this.nickname = nickname;
this.userPoint = 0;
}
public void appPoint(int point){ //포인트를 증가시켜서 저장하는 메서드
if(point>0){
this.userPoint += point;
}
}
- StepService
- 걸음 수를 저장할 때 포인트를 계산해주는 로직을 작성한다.
- 걸음 수를 100으로 나누었을 때 몫이 같으면 갱신할 필요가 없다
150 / 100 = 1, 180 / 100 = 1 이런 식으로 진행된다. - 그렇다면 업데이트된 걸음 수를 100으로 나눈 값과 전에 있던 걸음 수를 100으로 나눈 걸음 수의 차이가
1이상이라면 포인트를 증가시킨다! - 또한, 포인트 변경이 되는 if문에 진입하면 포인트 이력 레포지토리에도 저장한다!
@Service
@RequiredArgsConstructor
public class StepService {
private final StepInfoRepository stepInfoRepository;
private final MemberRepository memberRepository;
private final PointHistoryRepository pointHistoryRepository;
@Transactional
public void saveStep(StepRequest stepRequest) {
Member member = memberRepository.findById(stepRequest.getUserId()).orElseThrow(
() -> new BusinessException(ErrorCode.USER_NOT_FOUND)
);
StepInfo existingStep = stepInfoRepository.findByMemberAndRecordDate(member, stepRequest.getRecordDate()).orElse(null);
int oldStep = 0;
if(existingStep != null){
oldStep = stepRequest.getStepCount();
existingStep.updateSteps(stepRequest.getStepCount());
}else{
StepInfo newStep = new StepInfo(member, stepRequest.getStepCount(), stepRequest.getRecordDate());
stepInfoRepository.save(newStep);
}
int oldPoints = oldStep / 100;
int newPoints = stepRequest.getStepCount() / 100;
int earnedPoints = newPoints - oldPoints; //차이가 발생하면 포인트 증가시키기
if(earnedPoints > 0){
member.appPoint(earnedPoints);
memberRepository.save(member);
System.out.println(member.getUserPoint());
pointHistoryRepository.save(new PointHistory(member, earnedPoints, "걸음 수 달성 보상"));
}
}
}
그치만 userPoint 가 그대로인 상황이 발생했다. 트랜잭션도 잘 작동함을 확인했고 레포지토리 메서드 문제도 아니였다.

직접 출력해보니 변동량이 생겼는데 지금 userPoint가 업데이트가 안되는 것을 볼 수 있었다.
그래서 memberRepository.save(member) 까지 적어서 멤버를 완전히 다시 저장시켰는데도 포인트는 그대로였다...
if(existingStep != null){ //오류의 범인
oldStep = stepRequest.getStepCount();
existingStep.updateSteps(stepRequest.getStepCount());
}else{
StepInfo newStep = new StepInfo(member, stepRequest.getStepCount(), stepRequest.getRecordDate());
stepInfoRepository.save(newStep);
}
요청을 받은 DTO에 있는 걸음 수를 기존 걸음 수 라고 지정했던 oldStep 에 넣어주고 있었던 사고가 있었다..
당일 날짜가 있는 데이터를 찾아둔 existindStep 에 있는 걸음 수를 저장하는 코드로 바꿨다!
package NetZero.service;
import NetZero.domain.Member;
import NetZero.domain.PointHistory;
import NetZero.domain.StepInfo;
import NetZero.dto.StepRequest;
import NetZero.exception.BusinessException;
import NetZero.exception.ErrorCode;
import NetZero.repository.MemberRepository;
import NetZero.repository.PointHistoryRepository;
import NetZero.repository.StepInfoRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
public class StepService {
private final StepInfoRepository stepInfoRepository;
private final MemberRepository memberRepository;
private final PointHistoryRepository pointHistoryRepository;
@Transactional
public void saveStep(StepRequest stepRequest) {
Member member = memberRepository.findById(stepRequest.getUserId()).orElseThrow(
() -> new BusinessException(ErrorCode.USER_NOT_FOUND)
);
StepInfo existingStep = stepInfoRepository.findByMemberAndRecordDate(member, stepRequest.getRecordDate()).orElse(null);
int oldStep = 0;
if(existingStep != null){
oldStep = existingStep.getStepCount(); //여기만 변경시켰다
existingStep.updateSteps(stepRequest.getStepCount());
}else{
StepInfo newStep = new StepInfo(member, stepRequest.getStepCount(), stepRequest.getRecordDate());
stepInfoRepository.save(newStep);
}
int oldPoints = oldStep / 100;
int newPoints = stepRequest.getStepCount() / 100;
int earnedPoints = newPoints - oldPoints; //차이가 발생하면 포인트 증가시키기
if(earnedPoints > 0){
member.appPoint(earnedPoints);
pointHistoryRepository.save(new PointHistory(member, earnedPoints, "걸음 수 달성 보상"));
}
}
}
결과를 보기 위해 스웨거에서 걸음 수 저장 요청을 보내서 테스트하기로 했다.
내 손으로 100번, 200번, 300번은 무리니까..


우선 150보를 걸었을 때 포인트가 1 증가하는지 확인했다!


그 다음 250보가 되었을 때 확인한 결과이다. 변동량만큼만 포인트에 반영되었다!!


650보로 올렸을 때도 변동량만큼만 잘 저장되었다!

포인트 변동 이력도 잘 저장되었다!
누적 걸음 수
내가 생각한 누적 걸음 수는 일자별로 나누어서 저장된 걸음 수들의 총합이다.
그렇다면 엔티티에 새로운 컬럼이 필요하고 이 컬럼에 저장되는 로직이 필요하다.
그렇지만, 누적 걸음 수는 걸음 수 API에서 관리하는 것이 아닌 회원에 대한 정보로도 존재할 수 있다.
따라서 Member엔티티에 totalSteps 를 추가해주고
엔티티에서 업데이트 해주는 메서드를 작성하기로 했다.
@Entity
@NoArgsConstructor
@Getter
public class Member {
@Id
@Column(name = "member_id")
private String id;
private String username;
private String nickname;
private int userPoint;
private int totalSteps;
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<PointHistory> pointHistories = new ArrayList<>();
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<StepInfo> stepInfos = new ArrayList<>();
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<CouponInfo> couponInfos = new ArrayList<>();
public Member(String id, String username, String nickname, int userPoint){
this.id = id;
this.username = username;
this.nickname = nickname;
this.userPoint = 0;
this.totalSteps = 0;
}
public void appPoint(int point){
if(point>0){
this.userPoint += point;
}
}
public void addTotalSteps(int addSteps){
if(addSteps>0){
this.totalSteps += addSteps;
}
}
}
그리고 걸음 수 저장 요청이 왔을 때
걸음 수의 변동량만 계속해서 더해주면 된다. 내가 생각하기로는 날짜별로 끊어서 저장해야 한다고 생각했지만,
걸음 수 자체에 대해서 변동량을 계속해서 더해주면 멤버에 저장되므로
꼭 날짜별로 확인하는 것이 아닌 총 누적 걸음 수를 확인할 수 있었다!
그리고 날짜가 바뀌면 걸음 수 정보를 새로 저장하는 로직을 구현했기 때문에 일 별 누적 걸음 수도 기록할 수 있었다.
@Service
@RequiredArgsConstructor
public class StepService {
private final StepInfoRepository stepInfoRepository;
private final MemberRepository memberRepository;
private final PointHistoryRepository pointHistoryRepository;
@Transactional
public void saveStep(StepRequest stepRequest) {
Member member = memberRepository.findById(stepRequest.getUserId()).orElseThrow(
() -> new BusinessException(ErrorCode.USER_NOT_FOUND)
);
StepInfo existingStep = stepInfoRepository.findByMemberAndRecordDate(member, stepRequest.getRecordDate()).orElse(null);
int oldStep = 0;
if(existingStep != null){ //기존에 있던 날짜의 걸음 수를 저장
oldStep = existingStep.getStepCount();
existingStep.updateSteps(stepRequest.getStepCount());
}else{ //새로운 날짜의 걸음 수를 저장
StepInfo newStep = new StepInfo(member, stepRequest.getStepCount(), stepRequest.getRecordDate());
stepInfoRepository.save(newStep);
}
int addedStep = stepRequest.getStepCount() - oldStep; //요청이 올 때 걸음 수 변동량만 계속해서 더해준다.
if(addedStep > 0){
member.addTotalSteps(addedStep);
}
int oldPoints = oldStep / 100;
int newPoints = stepRequest.getStepCount() / 100;
int earnedPoints = newPoints - oldPoints; //차이가 발생하면 포인트 증가시키기
if(earnedPoints > 0){
member.appPoint(earnedPoints);
pointHistoryRepository.save(new PointHistory(member, earnedPoints, "걸음 수 달성 보상"));
}
}
}
이렇게 하면 기존 날짜에 있는 데이터든 새로운 날짜에 생기는 데이터 상관없이
회원 한 명에 대한 누적 걸음 수를 쉽게 구할 수 있다.
스웨거를 통해서 다시 확인해보자


일단 걸음 수 자체는 저장이 잘 되고 있다. 포인트도 문제 없다.

1500보가 되면 포인트가 15포인트가 되어야하며 누적 걸음 수도 1500보가 되어야 한다.

만약 다른 날짜로 넘어간다면?


누적 걸음 수는 정상적으로 나오고, 포인트도 문제 없다.

걸음 수 정보가 다른 날짜로 두 줄로 생긴다!
<리팩터링>
지금 StepService 코드에서 걸음 수 저장, 걸음 수 업데이트, 누적 걸음 수 저장, 걸음 수 포인트 변동, 포인트 이력 저장 의 기능을
수행하고 있다. 이렇게 되면 하나의 클래스에서 너무 많은 책임을 가지게 되어
단일 책임 원칙을 위배한다!
포인트 관리를 맡아서 하는 클래스를 따로 만들었다!
package NetZero.service;
import NetZero.domain.Member;
import NetZero.domain.PointHistory;
import NetZero.dto.PointHistoryResponse;
import NetZero.repository.PointHistoryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Transactional
@RequiredArgsConstructor
public class PointService {
private final PointHistoryRepository pointHistoryRepository;
public List<PointHistoryResponse> getPointHistories(Member member){
List<PointHistory> histories = pointHistoryRepository.findByMember(member);
return histories.stream() //엔티티 리스트들을 DTO 리스트로 변환
.map(PointHistoryResponse::new) //하나하나를 DTO로 바꿈
.collect(Collectors.toList()); // 다시 리스트로 묶음
}
public void rewardStepPoints(Member member, int oldSteps, int newSteps){
int oldPoints = oldSteps / 100;
int newPoints = newSteps / 100;
int earnedPoints = newPoints - oldPoints;
if(earnedPoints > 0){
member.appPoint(earnedPoints);
pointHistoryRepository.save(new PointHistory(member, earnedPoints, "걸음 수 달성 보상"));
}
}
}
기존에 있었던 리스트로 포인트 이력을 얻어내는 메서드와
걸음 수를 포인트로 바꿔주는 로직을 추가했다.
@Service
@RequiredArgsConstructor
public class StepService {
private final StepInfoRepository stepInfoRepository;
private final MemberRepository memberRepository;
private final PointHistoryRepository pointHistoryRepository;
private final PointService pointService;
@Transactional
public void saveStep(StepRequest stepRequest) {
Member member = memberRepository.findById(stepRequest.getUserId()).orElseThrow(
() -> new BusinessException(ErrorCode.USER_NOT_FOUND)
);
StepInfo existingStep = stepInfoRepository.findByMemberAndRecordDate(member, stepRequest.getRecordDate()).orElse(null);
int oldStep = 0;
if(existingStep != null){ //기존에 있던 날짜의 걸음 수를 저장
oldStep = existingStep.getStepCount();
existingStep.updateSteps(stepRequest.getStepCount());
}else{ //새로운 날짜의 걸음 수를 저장
StepInfo newStep = new StepInfo(member, stepRequest.getStepCount(), stepRequest.getRecordDate());
stepInfoRepository.save(newStep);
}
int addedStep = stepRequest.getStepCount() - oldStep; //걸음이 늘어난 만큼 요청이 올 때 계속해서 더해준다.
if(addedStep > 0){
member.addTotalSteps(addedStep); //누적 걸음 수 더해준다.
}
pointService.rewardStepPoints(member, oldStep, stepRequest.getStepCount());
}
}
이렇게 되면 StepService에서는 걸음 수만을 관리한다.
관심사를 분리시키고 포인트를 가지고 하는 다른 로직을 PointService에서 추가할 수 있다!

잘 동작한다!