Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

포인트 도메인 구현 #44

Merged
merged 44 commits into from
Oct 7, 2023
Merged

포인트 도메인 구현 #44

merged 44 commits into from
Oct 7, 2023

Conversation

13wjdgk
Copy link
Contributor

@13wjdgk 13wjdgk commented Oct 1, 2023

Changes 📝

포인트 도메인 관련 기능 로직을 구현하였습니다.

  1. 1일 1 접속 시 100 포인트 적립
  2. 회원가입 시 500 포인트 적립
  3. 헬스커넥트 연동 시 500 포인트 적립
  4. 포인트 상품 구매
  5. 마이페이지 조회
  6. 포인트 상품 목록 조회

Details 🌼

  1. ERD : 총 4개의 테이블 추가
  • UserPoint : 사용자의 최종 사용가능 포인트
  • PointLog : 포인트 내역 ( 사용, 적립)
  • PointProduct : 포인트 상품
  • ProductPurchase : 포인트 상품 구매 내역
  1. 1일 1 접속 포인트 적립
  • JWT Filter에서 사용자 인증을 거친 후 , PointService.addAccessPoint()를 통해 적립 가능 유저 여부 판별
  • 적립 가능한 유저인지 확인이 완료되면 , PointService.addPointEvent() 를 통해 적립
  • PointLog,UserPoint 테이블에 기록
  • PointService 클래스 단에 Transaction 적용
  1. 회원가입 시 500 포인트 적립
  • UserSignupService.signUp() 를 통해 회원가입 -> 완료 후 PointService.addSignupPoint()를 통해 적립 가능 유저 여부 판별
  • 적립 가능한 유저인지 확인이 완료되면 , PointService.addPointEvent() 를 통해 적립
  • PointLog,UserPoint 테이블에 기록
  • UserSignupService.signUp, PointService 클래스 단에 Transaction 적용
  1. 헬스커넥트 연동 시 500 포인트 적립
  • HealthConnectRegisterService.interlockHealthConnect() 를 통해 헬스커넥트 연동 여부 반영
  • 첫 연동한 유저는 PointService.addHealthConnectPoint() 를 통해 적립
  • PointLog,UserPoint 테이블에 기록
  • HealthConnectRegisterService 클래스 , PointService 클래스 단에 Transaction 적용
  1. 낙관적 락 이용
  • 상품 구매 요청이 동시에 100번 들어오는 동시성 테스트를 진행
  • 상황 : 유저가 6000 포인트를 가진 상황에서, 5000 상품 구매를 100번 동시에 요청한 상황
  • 결과 : 100번 중 9번 상품 구매 성공
  • 원인 : 첫 구매의 트랜잭션이 커밋 완료되기 전에 두번째 구매가 진행되었기 때문 (두번갱신 문제)
  • 해결 : UserPoint에 @Version 필드를 추가. 첫번째 트랜잭션 커밋 -> version 업데이트 -> 두번째 트랜잭션이 커밋 -> version이 달라 오류를 반환
  • 이 외에도, 동시 접속, 동시 헬스커넥트 연동, 동시 회원가입 동시성 테스트 진행 완료

Check List ☑️

  • 테스트 코드를 통과했다.
  • merge할 브랜치의 위치를 확인했다. (main ❌)
  • Assignee를 지정했다.
  • Label을 지정했다.

[Optional] Etc

*이번 PR 후 해야할 일

  1. TaskExecutor 설정
  2. 접속 적립 AOP로 구현 여부 확인
  3. refreshToken 기간 늘리기

*Reference
https://tecoble.techcourse.co.kr/post/2023-08-16-concurrency-managing/?fbclid=IwAR3ily9yhZ0wurw1OVGTE9Xvv_fbmRCDl3LTPBf4A73P6U9JDiv7S6GxnUc_aem_Ab7UjAoeWUcSVfSVHL7zzpWs1R5qUoyWmbqGzKB_t04QYHuSjH6kIwsE6hETr40yD3A

자바 ORM 표준 JPA 프로그래밍 (낙관적 락 - 책)

스프링 DB 2편 - 데이터 접근 활용 기술(트랜잭션 - 인프런 강의)

@13wjdgk 13wjdgk added the feature 기능 label Oct 1, 2023
@13wjdgk 13wjdgk self-assigned this Oct 1, 2023
@coniverse-github-app

This comment has been minimized.

@coniverse-github-app

This comment has been minimized.


@EnableJpaAuditing
@EnableAsync
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1) TaskExecutor를 설정하지 않았는데, default로 사용했을 때 발생하는 문제에 대해 찾아보면 좋을 것 같아요!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금 당장 해결해야할 문제는 아니라고 생각되어서, 클라이언트 api 연동 후 해결하려고 합니다 ! 이번 pr 후 해야할 일들을 위에 정리해뒀습니다 ~

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

크리티컬한 문제라고 생각해요 다음 pr에 빠르게 반영됐으면 좋겠습니다~

@@ -39,6 +41,8 @@
@Component
@RequiredArgsConstructor
public class JwtValidationFilter extends OncePerRequestFilter {
private final PointService pointService;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2) Jwt 검증 필터에 서비스 레이어를 주입해서 사용하는 것이 좋은 방법일지 고민이 되네요
결합도가 높아지고 여러 개의 책임을 지는 것은 아닐까? 하는 생각입니다
presentation layer에 AOP를 적용하는 방법은 어떨까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 방법인 것 같아요 ~ 이번 pr 후 고려해서 수정해보겠습니다 ~

@@ -39,6 +41,8 @@
@Component
@RequiredArgsConstructor
public class JwtValidationFilter extends OncePerRequestFilter {
private final PointService pointService;
private final UserRepository userRepository;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3) 사용하지 않는 DI인 것 같습니다

@@ -0,0 +1,4 @@
package com.coniverse.dangjang.domain.healthmetric.dto.request;

public record HealthConnectRegisterRequest(boolean healthConnectInterlock) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3) JavaDoc 필요!

pointService.addHealthConnectPoint(user);
}
} else if (!request.healthConnectInterlock() && user.getHealthConnect().equals(HealthConnect.CONNECTING)) {
user.setHealthConnect(HealthConnect.DISCONNECTED);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위와 같은 맥락

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한번도 연동한 적 없는 유저와, 연동이 이미 해제되어 있는 유저는 아무런 처리를 하지 않습니다. 연동되어 있는 유저일때만 연동을 해지합니다

* @since 1.0.0
*/
@GetMapping("/mypage")
public ResponseEntity<SuccessSingleResponse<?>> getMyPage(@AuthenticationPrincipal User user) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P4) response data 필드가 null이 아니면 제네릭에 와일드카드 대신 클래스명을 명시해주셨으면 좋겠습니당

@@ -59,10 +65,19 @@ public class User extends BaseEntity implements Persistable<String> {
private boolean medicine;
private boolean injection;
private String profileImagePath;
@Enumerated(EnumType.STRING)
private HealthConnect healthConnect = HealthConnect.NEVER_CONNECTED;
@Column(name = "ACCESSED_AT", nullable = false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

유저는 BaseEntity를 상속받아서 updatedAt 필드가 존재하는데 추가로 중복되는 필드를 작성하신 이유가 있을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updatedAt은 객체가 수정되었을 때만 기록되지만, accessedAt은 접속하였을 때만 업데이트합니다.

*
* @since 1.0.0
*/
@Modifying(flushAutomatically = true, clearAutomatically = true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

포인트에서 서술한 내용과 동일

* @since 1.0.0
*/
@Service
@AllArgsConstructor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3) @requiredargsconstructor로 수정 부탁드려여

* @return User 사용자
* @since 1.0.0
*/
public Optional<User> findNickname(String nickname) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3) JavaDoc은 "존재하는" 닉네임인지 조회하여 "확인" 인데 조회만 하고 있어요

@coniverse-github-app

This comment has been minimized.

@coniverse-github-app

This comment has been minimized.

if (balancePoint >= 0) {
return balancePoint;
} else {
throw new BusinessException(400, "포인트가 부족합니다.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3) Exception 클래스를 구현해 주세요

}

/**
* 헬스커넥트 포인트 적립
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3) JavaDoc 수정 필요!

Authentication auth = getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
} else if (jwtStatus.equals(JWTStatus.EXPIRED)) {
request.setAttribute("exception", jwtStatus.getMessage());
} else if (jwtStatus.equals(JWTStatus.INVALID)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3) elif 하나로 합쳐도 될 것같아요

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

조건문 수정했습니다 ~

Copy link
Member

@GIVEN53 GIVEN53 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전체적으로 수정이 이루어지면서 JavaDoc과 불일치하는 메서드가 많아요 아마 IDE가 잡아줄텐데 같이 수정 부탁드려요

* @since 1.0.0
*/
@PostMapping
public ResponseEntity<SuccessSingleResponse> usePoint(@Valid @RequestBody UsePointRequest request,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SuccessSingleResponse 제네릭 명시안하면 IDE가 잡아주지 않나요?

private PointId pointId;
private String phone;
@ColumnDefault("false")
private boolean completed;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

구매가 완료되었다는 의미의 필드인가요? 해당 필드 사용 이유가 궁금해여

*/
public PointProduct findPointProductById(String pointProduct) {
return pointProductRepository.findById(pointProduct)
.orElseThrow(() -> new BusinessException(400, "포인트 상품이 없습니다."));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3) Exception 클래스를 구현해 주세요

*/
public UserPoint findUserPointByOauthId(String oauthId) {
return userPointRepository.findById(oauthId)
.orElseThrow(() -> new BusinessException(400, "유저 포인트가 없습니다."));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3) Exception 클래스를 구현해 주세요

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

새로운 예외를 구현하기 보다, 표준 예외를 재사용하는 방법으로 수정하겠습니다 ~


import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위에서 서술한 내용과 동일

private User 유저;

@Test
void getMypage() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3) 테스트 설명으로 수정해주세요

@coniverse-github-app

This comment has been minimized.

@coniverse-github-app

This comment has been minimized.

@coniverse-github-app

This comment has been minimized.

@13wjdgk 13wjdgk merged commit 60cefbe into dev Oct 7, 2023
1 check passed
@GIVEN53 GIVEN53 deleted the feat/point branch October 8, 2023 13:07
@coniverse-github-app
Copy link

Passed

Analysis Details

0 Issues

  • Bug 0 Bugs
  • Vulnerability 0 Vulnerabilities
  • Code Smell 0 Code Smells

Coverage and Duplications

  • Coverage No coverage information (97.30% Estimated after merge)
  • Duplications No duplication information (0.90% Estimated after merge)

Project ID: co-niverse_dangjang-backend_AYj2jZJELehUZAlqDvRk

View in SonarQube

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature 기능
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants