TIL(Today I Learned)

[TIL] 고객과 통화 연관관계로 환전요청 만들기

jy3574 2024. 12. 2. 23:26
프로젝트 요약

 

1. 프로젝트 목적

  • RESTful API 설계 : Spring Boot와 JPA를 사용하여 CRUD API를 설계하고 구현
  • 데이터 검증 : Validation을 활용하여 입력값 검증 및 데이터 무결성 보장
  • 비즈니스 로직 분리 : 계층별 역할 분리를 통해 유지보수성 향상
  • 예외처리 : GlobalExceptionHandler를 통해 예외 처리 방식 학습
  • JPQL 활용 : 데이터 조회 및 요약을 위한 JPQL 사용법 학습

2. 프로젝트 구현 단계

<필수 기능>

  • Lv0 : 프로젝트 설계
    • API 명세서 작성 : postman 사용
    • ERD 작성 
    • SQL 작성
  • Lv1 : 고객(User)과 통화(Currency) 복잡한 연관관계
    • 환전 요청 중간 테이블 생성
      • 필드 : 고객 고유 식별자, 환전 대상 통화 식별자, 환전 전 금액, 환전 후 금액, 상태
    • 고객과 통화 테이블 간 연관관계
      • 환전 요청 테이블은 중간 테이블이고 User와 Currency 간 관계를 관리
  • Lv2 : 환전 요청 CRUD
    • C : 환전 요청 수행
      • 환전 대상 통화 식별자가 Currency 테이블에 가지고 있는 환율을 기준으로 환전을 수행
      • 환전 후 금액 = 환전 전 금액 / 환율
      • 환전 후 금액에 대해 소수점 두번째 반올림 수행
    • R : 고객 고유 식별자를 기반으로 특정 고객이 수행한 환전 요청 조회
      • 고객 고유 식별자 = 환전 요청 테이블에 있는 UserId
    • U : 특정 환전 요청 상태를 취소로 변경
      • 상태값 : normal, cancelled
    • D : 고객이 삭제될 때 해당 고객이 수행한 모든 환전 요청도 삭제
      • 영속성 전이, cascade
  • Lv3 : 예외처리
    • 유효성 검사 추가
      • 3-Layered Architecture, Validation
      • 이메일, 일자, 데이터, 길이 등
    • 예외 처리 강화
      • API 예외처리 : GlobalExceptionHandler
      • 요청값의 형식이 맞지 않습니다. / 네트워크 요청에 실패했습니다. 등

<도전 기능>

  • Lv4 : PostConstruct 적용
    • 스프링이 구동될 때 통화 테이블에 있는 환율이 0이거나 음수이거나 지정된 범위를 벗어나는 경우, 유효하지 않은 값응로 간주하고 로그를 기록
    • @Component, @Slf4j
  • Lv5 : JPQL
    • 고객의 모든 환전 요청을 그룹화하여 조회
    • 반환해야하는 컬럼
      • 해당 고객이 수행한 환전 요청 데이터들의 총 row 수
      • 해당 고객이 환전 요청한 총 금액
      • @Query, Group By, SUM, COUNT
  • Lv6 : 달러 이외 통화를 환전할 수 있도록 수정
    • Currency 테이블에 다른 통화에 대한 데이터를 추가
    • 각 통화마다 다른 자리수를 적용하도록 개발

사용한 기술 및 개념

 

1. Backend

  • Spring Boot
    • RESTful API 설계 및 계층 분리
    • 애플리케이션 실행
  • Spring Data JPA
    • 데이터베이스와 상호작용을 위한 기본 CRUD 메서드
    • @Query로 복잡한 조회 처리

2. Database

  • MySQL
    • 데이터 저장 및 연관 관계 설정
    • SQL과 JPQL 매핑학습

3. Tools

  • Postman : API 테스트
  • IntelliJ IDEA : IDE 환경에서 프로젝트 개발
  • ERD : 데이터베이스 설계 및 시각화

직접 구현한 내용

https://github.com/jiyoon0000/currency_user

 

GitHub - jiyoon0000/currency_user

Contribute to jiyoon0000/currency_user development by creating an account on GitHub.

github.com

 

1. Lv0 : 프로젝트 설계

 

1-1. API 명세서 작성(postman 사용)

https://documenter.getpostman.com/view/39376424/2sAYBXAAAX

 

currency_user

The Postman Documenter generates and maintains beautiful, live documentation for your collections. Never worry about maintaining API documentation again.

documenter.getpostman.com

 

1-2. ERD 작성

 

1-3. SQL 작성

 

Lv1 : 고객(User)과 통화(Currency) 복잡한 연관관계

  • 구현내용
    1. 환전 요청 중간 테이블 생성
      • ExchangeRequest 테이블을 생성하여 User와 Currency 사이의 중간 테이블로 동작하게 함
      • 주요 필드
        • 고객 고유 식별자(user_id), 통화 식별자(currency_id)
        • 환전 전 금액(beforeExchange), 환전 후 금액(afterExchange)
        • 상태(status) : normal, cancelled
      • JPA Auditing으로 생성, 수정일을 자동 관리
    2. User와 Currency 연관관계 설정
      • Currency ↔ ExchangeRequest ↔ User
      • @ManyToOne으로 다대일 관계 설정
    3. 연관 데이터 삭제
      • CascadeType.All, orphanRemoval을 사용하여 사용자 삭제 시 관련 환전 요청도 삭제
  • 사용기술
    1. Spring Data JPA
      • 연관관계 설정 : @ManyToOne, @OneToMany
      • 연관 엔티티 삭제 처리 : Cascade, orphanRemoval
    2. JPA Auditing
      • 생성일, 수정일 자동 관리
    3. Entity 설계
      • User, Currency, ExchangeRequest 엔티티 간 관계 설정
  • 학습한 내용
    • JPA의 @OneToMany, @ManyToOne 관계 설정에 대해 이해
    • Cascade와 orphanRemoval로 연관 데이터 관리

Lv2 : 환전 요청 CRUD

  • 구현내용
    1. 환전 요청 수행
      • currencyId를 기반으로 환율을 조회하여 금액 계산
      • 환전 후 금액 = 환전 전 금액 / 환율
      • BigDecimal의 setScale을 사용하여 소수점 두자리를 반올림
    2. 환전 요청 조회
      • 고객 ID를 기반으로 특정 고객의 모든 환전 요청을 조회
    3. 환전 요청 상태 업데이트
      • Enum을 사용하여 상태값 NORMAL, CANCELLED 변경 API 구현
    4. 사용자 삭제
      • 사용자 삭제 시 연관된 모든 환전 요청도 함께 삭제되도록 구현
  • 사용기술
    1. Spring MVC
      • API 엔드포인트 구성 : @PostMapping, @GetMapping, @PutMapping, @DeleteMapping
      • HTTP 메서드 기반 CURD 설계
    2. Spring Data JPA
      • CRUD 메서드 활용 : save, findAllByUserId, delete
    3. BigDecimal 연산
      • 소수점 자리 수 처리 : setScale
    4. Validation
      • DTO에 @NotNull, @DecimalMin 등 검증 로직 추가
    5. Service 계층 분리
      • 비즈니스 로직과 데이터 접근 분리
  • 학습한 내용
    • BigDecimal 연산 및 자리수 반올림 처리하는 방법에 대해 학습
    • Cascade로 부모 엔티티 삭제 시 자식 엔티티 자동 삭제
    • Enum을 사용하여 다양한 상태값을 적용하는 방법에 대해 이해

Lv3 : 예외처리

  • 구현내용
    1. 유효성 검사
      • DTO에서 Validation 적용
        • @NotNull : 필수 값 검증
        • @DecimalMin : 최소 값 검증
      • @Valid로 controller 계층에서 요청 데이터 검증
    2. GlobalExceptionHandler
      • @RestControllerAdvice를 사용해 전역 예외 처리
        • IllegalArgumentException - 입력값 오류 시 처리
        • 기타 예외 : 네트워크 요청 실패 메시지 반환
  • 사용기술
    1. Validation
      • DTO 유효성 검증 : spring-boot-starter-validation
      • 입력값 검증 : @Valid, @NotNull, @DecimalMin
    2. GlobalExceptionHandler
      • 전역 예외 처리 : @RestControllerAdvice
      • 특정 예외에 대한 처리 로직 정의 : @ExceptionHandler
    3. Custom Error Response
      • DTO(ErrorResponse)를 활용해 에러 코드와 메시지 반환
  • 학습한 내용
    • GlobalExceptionHandler를 사용해 예외처리 로직을 분리
    • 유효성 검증과 예외 처리를 통해 안정적인 애플리케이션 설계방법 학습

Lv4 : PostConstruct 적용

  • 구현내용
    1. 애플리케이션 구동 시 Currency 테이블의 데이터 유효성 검증
      • 환율이 0이거나 음수인 경우 로그 경고
      • 비정상적으로 높은 환율(10,000 이상)인 경우 로그 경고
    2. @PostConstruct로 애플리케이션 초기 실행 시 검증 로직 수행
  • 사용기술
    1. Spring Component
      • @Component : CurrencyValidator 클래스 등록
    2. Application Lifecycle 
      • @PostConstruct : 애플리케이션 구동 시 초기화 로직 수행
    3. Slf4j
      • log.warn : 환율 검증 결과를 로그로 기록
  • 학습한 내용
    • @Component와 @PostConstruct를 활용해 초기화 로직 구현
    • 로그 경고로 데이터 문제를 미리 감지

Lv5 : JPQL

  • 구현내용
    1. 고객별 환전 요청 요약 조회
      • JPQL을 활용해 고객 ID별 환전 요청 수와 총 금액 조회
      • Group By와 SUM, COUNT 사용
    2. DTO 활용
      • DTO로 데이터 변환 후 클라이언트에 반환
  • 사용기술
    1. JPQL
      • @Query : JPQL을 활용한 그룹화 및 연산
      • SUM, COUNT, GROUP BY : 고객별 환전 요청 요약 조회
    2. DTO 활용
      • ExchangeSummaryDto : JPQL 결과를 DTO로 매핑
    3. Repository 계층
      • Spring Data JPA의 Repository 인터페이스에 사용자 정의 쿼리 추가
  • 학습한 내용
    • JPQL과 SQL의 차이점 학습 - 엔티티 중심의 조회
    • DTO를 사용한 데이터 매핑과 API 응답 설계

Lv6 : 달러 이외 통화를 환전할 수 있도록 수정

  • 구현내용
    1. Currency 엔티티 확장
      • decimalPlaces 필드를 추가해 통화별 소수점 자리수 관리
      • 통화 기호(symbol)와 자리수 정보를 기반으로 환전 금액 포맷팅
    2. 엔화와 달러 포맷
      • 엔화 : 1111
      • 달러 : 11.11$
  • 사용기술
    1. Entity 확장
      • Currency 엔티티에 decimalPlaces 필드 추가
    2. BigDecimal 포맷팅
      • setScale : 통화별 소수점 자리수 설정
    3. Service 계층 로직
      • 포맷팅 로직을 Service 계층에 구현하여 재사용성과 가독성 강화
    4. 통화별 처리 로직
      • 각 통화의 기호와 자리수에 맞는 금액 출력
  • 학습한 내용
    • 데이터 포맷팅 로직 구현(service 계층)
    • 엔티티 확장을 통해 통화별 특성이 반영되도록 함

진행 중 어려웠던 점과 해결방법
& 피드백

1. 진행 중 문제점과 해결방법은 트러블 슈팅을 작성해 정리

https://jy3574.tistory.com/113

 

[내일배움캠프/백엔드] Spring 심화 개인과제 트러블 슈팅

- 어떤 현상을 발견했는가?1. @RequestParam으로 Enum 값 처리 postman 오류 2. @NotNull과 Bean Validation이 작동하지 않음 3. @PostMapping 어노테이션 사용 불가 오류 4. Project JDK is not defined 이런 장애가 생길 수

jy3574.tistory.com

 

2. JPQL 활용방법

  1. JPQL vs SQL
    • JPQL은 SQL의 문법과 비슷하지만 데이터 베이스 테이블이 아닌 entity 객체를 중심으로 작성되는 쿼리 언어
    • SQL과 유사하지만 객체 지향적인 방법으로 데이터베이스와 상호작용
    • JPQL은 엔티티 클래스 이름을 기반으로 작성해야하며, 필드 이름은 반드시 entity class에 선언된 변수명과 일치해야함.
  2. DTO를 활용해서 결과 반환
    • JPQL에서 new 키워드는 DTO 생성자를 호출 (결과를 DTO로 반환하기 위해 new 키워드 사용)
    • DTO 클래스의 패키지 경로를 포함한 전체 이름을 명시해야함
    • 해당 DTO 클래스는 반드시 해당 필드를 받는 생성자가 정의되어 있어야 함
  3. JPQL의 반환값
    • JPQL은 기본적으로 엔티티나 특정 필드를 반환하도록 설계되어 있음
      • DTO를 반환하거나 집계 쿼리를 작성할 때는 new 키워드나 집계함수를 사용해야 함
    • DTO 생성자에서 타입 불일치가 발생하지 않도록 집계함수(SUM, COUNT)를 사용할 때 반환타입에 맞는 변수 타입을 일치시켜줘야함
      • SUM : 기본적으로 BigDecimal로 반환
      • COUNT : Long 타입으로 반환

3. 피드백

  1. ExchangeRequestController의 User 삭제 API
    • 피드백
      • User 삭제는 User와 관련된 주요 작업이며, Cascade를 통해 관련 ExchangeRequest가 삭제되기 때문에 ExchangeRequestController에 존재하지 않아도 됨
    • 개선 방안
      • 사용자 삭제 시 관련 환전 요청을 지워야한다는 문구를 보고 User와 ExchangeRequest 양쪽 다 삭제 API를 만들어야한다고 생각
      • 삭제로직을 User에만 두고 환전요청측에서 삭제되는 로직은 지움
  2. 특정 사용자의 환전 요청 조회 API REST 규약 미준수
    • 피드백
      • 특정 사용자 ID를 기반으로 환전 요청 목록을 조회하는 API의 URL이  /user/{userId} 로 끝나게 설계가 되어있어 REST 규약을 따르지 않음
      • 특정 사용자 ID는 쿼리 파라미터로 받는게 좋음
    • 개선방안
      • REST 규약에 맞게 URL을 설계한 후에 쿼리 파라미터로 사용자 ID를 전달받도록 변경
  3. 환전 요청 상태 업데이트
    • 피드백
      • 상태 업데이트 API에서 @RequestParam을 사용하여 상태 업데이트를 하고 있으나, @RequestBody로 변경하면 더 간결하고 명확할 것 같음
    • 개선방안
      • 상태값을 쿼리파라미터로 전달받는것이 간단하고 테스트하기도 편리해서 선택했지만 @RequestBody로 상태값을 직접 전달받는 방식이 RESTful 설계에 맞고 가독성과 유지보수를 높일 수 있어서 개선
  4. Enum 클래스 분리
    • 피드백
      • ExchangeStatus Enum이 엔티티 내부에 정의되어있는데, 보통 Enum은 독립적인 클래스로 관리하며, enums 디렉토리 등에 분리하여 보관
    • 개선방안
      • Enum을 enums 패키지를 만들어 이동한 후 따로 정의 및 관리

느낀점 및 개선해야할 점

 

저번 과제에서는 구현하지 못했던 GlobalExceptonHandler를 구현했다는 점에서 발전했다고 생각하고, 막상 구현을 해보니 전역예외처리는 신경쓸게 많다는 것을 알게 되었다. 그리고 항상 RESTful 설계를 해야겠다고 생각하지만 항상 실수가 나는 부분인 것 같다. 이런 부분에 대해 공부를 조금 더 꼼꼼하게 해야겠다고 느꼈다.

 

이번 과제에는 User와 Currency에 대한 기본 코드가 주어진 채로 과제를 진행했는데, 처음부터 내가 짜는 것이 아니다 보니 간단한 코드여도 가벼운 분석을 하고 시작해야했다. 이런 방식으로 코드를 짜보니 협업하는 것과 비슷한 느낌이 들었고 추가해야할 것과 어떻게 연관지어야 할지 등에 대해 조금 더 빨리 정리할 수 있었던 것 같다. 또한, 이번과제에서는 처음 사용해보는 enum, cascade 이 두가지를 가장 중점적으로 생각하였는데 class나 interface를 만들때 enum도 분명 목록에 있는데 그냥 지나쳐 entity에 구현했다는 점이 조금 아쉽다. 그리고 피드백을 받아보니 Cascade에 대해 정확한 이해를 하지 못한 것 같다. Cascade를 제대로 이해했으면 이걸 사용했을 때 환전요청에서는 삭제 로직을 굳이 만들지 않아도 된다는 것을 바로 인지했을 것 같다. 조금 더 꼼꼼하게 공부를 해야겠다.

 

마지막으로 환전요청을 엔화로도 할 수 있도록 하는 것(통화별 포맷)의 조건을 꼼꼼하게 읽지 않아서 소수점 관리만 하고 엔화로 환전했을 때 나오는 symbol과 자리수를 제대로 생각하지 못했던 것 같다. JPQL, RESTful, 예외처리 등에 대해 다시 한번 공부해봐야겠다. 이번 과제는 꼼꼼함이 조금 부족했던 과제였던 것 같다.