본문 바로가기

카테고리 없음

Spring 미니 프로젝트 [RN & Spring] - 걸음 수 -> 포인트

이번에는 걸음 수 -> 포인트 -> 쿠폰 구매로 이어지는 로직을 구현해볼 예정이다.

 

걸음 수 -> 포인트

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에서 추가할 수 있다!

 

잘 동작한다!