https://github.com/Griotold/CodeBloom_DeliveryApp
GitHub - Griotold/CodeBloom_DeliveryApp
Contribute to Griotold/CodeBloom_DeliveryApp development by creating an account on GitHub.
github.com
1. 프로젝트 간단 소개
스프링부트 개발환경을 통해 '00의 민족'과 같은 주문 관리 플랫폼의 백엔드 구축했습니다.
생성형 인공지능 서비스(`API`)를 활용하여 `AI` 기능 적용했습니다.
2. `ERD`, 인프라 설계서
특징적인 것은 "음식점 허가 요청(`p_store_request`)"라는 테이블을 따로 만들어서
가게 생성시 곧장 "음식점(`p_store`)" 테이블에 생성하지 않고
먼저 음식점 허가 요청에 생성한 뒤,
관리자가 승인 또는 반려하는 관리자 승인 프로세스를 도입했습니다.
`AWS EC2` 에 서버를 띄웠고, `RDS`에 데이터베이스를 구축하였습니다.
3. 주요 기능 구현 및 기술 도입 이유
3 - 1. `AuditorAware`를 사용하여 사용자 정보 자동 입력
`AuditorAware`를 사용하여, 사용자 정보를 자동 입력되게 했습니다.
데이터베이스에 데이터를 생성하거나 수정할 때,
현재 로그인된 사용자의 userId가 자동으로 입력되도록 했습니다.
@Slf4j
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (null == authentication || !authentication.isAuthenticated()) {
return null;
}
//사용자 환경에 맞게 로그인한 사용자의 정보를 불러온다.
log.info("authentication.getName(): {}", authentication.getName());
if (authentication.getName().equals("anonymousUser")) {
return Optional.of("anonymous");
}
return Optional.of(authentication.getName());
}
}
3 - 2. `AI - GPT API`를 사용하여 메뉴 설명 자동 생성
`GPT API`를 사용한 이유는 `GPT-4o`의 벤치마크 결과를 확인하고 선택하였습니다.
퀄리티, 속도, 가격 측면을 고려하여 높은 효율을 낼 수 있는 `GPT-4o` 버전을 선택하게 되었습니다.
2 - 3. `Junit5`를 사용하여 자동화된 테스트
결제 도메인에 결제시도 로직의 결제 성공 케이스, 결제 실패 케이스를 테스트했습니다.
- `PaymentServiceTest`
`Service Layer`와 `Repository Layer` 통합 테스트이고,
@Transactional
@ActiveProfiles("test")
@SpringBootTest
class PaymentServiceTest {
@Autowired
PaymentService paymentService;
@Autowired
PaymentRepository paymentRepository;
@Autowired
OrderRepository orderRepository;
@Autowired
UserRepository userRepository;
@Autowired
AddressRepository addressRepository;
@Autowired
StoreCategoryRepository storeCategoryRepository;
@Autowired
LocationRepository locationRepository;
@Autowired
StoreRepository storeRepository;
@MockBean
PgClient pgClient; // Mocking!
@BeforeEach
void setUp() {
// Payment 외 부수적인 엔티티 ex) User, Order, Store 등등...
}
@DisplayName("결제 성공")
@Test
void createPayment_success() {...}
@DisplayName("없는 주문을 결제하려고하면 결제 실패한다.")
@Test
void createPayment_fail_1() {...}
@DisplayName("주문자와 결제요청자가 다르면 결제 실패한다.")
@Test
void createPayment_fail_2() {...}
@DisplayName("외부 결제 시스템에서 결제 승인 실패 시 예외 발생")
@Test
void createPayment_fail_3() {...}
@DisplayName("CARD 또는 CASH가 아니면 결제 실패")
@Test
void createPayment_fail_4() {...}
@DisplayName("NHN, TOSS, KG가 아니면 결제 실패")
@Test
void createPayment_fail_5() {...}
// 생성을 위한 private method 들...
}
외부 `PG`사를 호출하는 `PgClient`는 `Mocking` 처리하였고,
`@BeforeEach` 에서 `Payment` 외 부수적인 엔티티를 생성하도록 했습니다.
2 - 4. `Scheduler`를 사용하여 가게의 평점 갱신
1시간 마다 가게에 달린 리뷰들의 점수를 계산하여 가게의 평점을 갱신하도록 구현했습니다.
가게의 평점은 실시간성이 중요하지 않다고 판단하였습니다.
2 - 4 - 1. `Scheduler`의 장점
리뷰가 등록/수정/삭제될 때마다 평점을 계산하지 않아도 되어 서비스 응답 시간이 개선됩니다.
2 - 4 - 2. `Scheduler`의 단점
실시간성이 떨어집니다. (최대 1시간의 지연 발생)
스케줄러가 실행되는 동안 `DB` 부하가 집중될 수 있습니다.
2 - 4 - 3. 대안으로 고려할 수 있는 방법
1. 리뷰 변경시 즉시 평점을 업데이트
2. Redis 캐시를 활용한 하이브리드 방식 (실시간 캐시 + 주기적 `DB` 업데이트)
a. 리뷰 추가/수정/삭제시 `Redis` 캐시에 해당 가게의 평점을 즉시 갱신
b. 평점 조회시
i. `Redis` 캐시에서 먼저 조회
ii. 캐시에 없으면 `DB`에서 조회후 `Redis` 캐시에 저장
c. 주기적 `DB` 동기화
i. `Redis`에 저장된 모든 가게의 평점을 DB에 업데이트
3. 어려웠던 점, 그에 대한 해결 방안
3 - 1. `AuditorAware` 를 사용하여 사용자 정보 자동 입력
회원가입, 로그인 시도하면 `Authentication` 객체에 사용자 정보가 없기 때문에
`Authentication.getName()`을 호출하면 `"anonymousUser"` 라는 값을 반환받게 됩니다.
우리 프로젝트의 `BaseEntity`의 `createdBy`, `updatedBy` 는 10글자 제약 조건이 걸려 있어서
충돌이 발생하게 되었습니다.
@CreatedBy
@Column(name="created_by", length=10)
private String createdBy;
@LastModifiedBy
@Column(name="updated_by", length=10)
private String updatedBy;
3 - 1 - 1. 해결 방안
`Authentication` 객체에 사용자 정보가 없는 경우 `"anonymous"` (9글자) 가 사용자 정보로 입력되도록 했습니다.
추가적으로 제약조건을 `10` -> `2048` 로 늘렸습니다.
4. 잘된 점과 그 이유
4 - 1. 깃허브를 이용한 협업
각자 브랜치를 나눠, 맡은 부분 개발을 진행해 역할 분담이 잘 이루어졌습니다.
기능 구현별 PR과 코드리뷰를 통해 자신이 미처 발견하지 못한 오류를 수정하거나 개선할 수 있는 부분을 발견하는 등
피드백과 반영이 잘 이루어졌습니다.
5. 협업간 발생한 문제와 해결방안
5 - 1. `DTO` 클래스 명칭 통일 문제
`DomainRequestDto`, `DomainResponseDto` 로 할지
`dto` 명칭을 뺄지 의견이 나뉘었습니다.
결론은 `DomainRequest`, `DomainResponse`로 통일하였습니다.
5 - 2. `Repository` 사용 방식
`QueryDSL`을 사용하는 `Repository`를 `JpaRepository` 와 합쳐서 사용할지 따로 사용할지
의견이 나뉘었습니다.
결론은 각 도베인별 개인시 선호하는 방식을 사용하기로 합의했습니다.
5 - 3. `PR` 생성 방식
도메인별 기능 하나 구현할 때마다 `PR`을 올릴지, `CRUD` 전체를 구현 후에 한꺼번에 올릴지
의견이 나뉘었습니다.
`PR`을 잘게 나눌 경우 리뷰할 코드의 양이 적어 빠른 피드백 및 승인이 가능한 장점이 있습니다.
`PR`을 합칠 경우 잦은 피드백 요청을 피하고 개인 개발에 집중 가능한 장점이 있습니다.
결론은 튜터님의 `Git` 특강 조언에 따라 `PR`을 나누어 올리기로 합의했습니다.
6. 팀 프로젝트를 진행하며 느낀 점
은우
- 사람마다 같은 `CRUD`를 개발해도 코딩하는 방식이 다름을 느꼈다.
- `PR`을 통해 서로의 코드에 대한 각자의 의견을 공유할 수 있었다.
해성
- `PR`로 협업한 건 처음이라 좋은 경험했다.
- `API` 설계, `ERD` 설계가 약했는데 이번 프로젝트를 진행하며 감이 좀 잡혔다.
- 테스트 코드가 없으니 리팩토링할 때 일일이 `API`로 요청해 봐야 해서 번거로웠다.
시원
- 이렇게 단기로 빠르게 개발한 경험은 처음이라 신기했다.
- 제출 후에도 차근차근 리팩터링을 진행하면 좋을 것 같다.
- 네이밍 등에서도 다양한 의견을 들을 수 있어서 좋았다.
'자바 심화 2기' 카테고리의 다른 글
Spring Dozen - 물류 관리 시스템 - MSA 기반 플랫폼 (0) | 2024.12.19 |
---|---|
2024 11 14 TIL - Spring Data의 @PageableDefault, @SortDefault (0) | 2024.11.14 |
2024 11 13 TIL - QueryDSL, BooleanExpression (1) | 2024.11.13 |
2024 11 12 TIL - 회원가입하려는데 왜 자꾸 인증하라고 그러는걸까? (Feat. AuditorAware) (0) | 2024.11.12 |
2024 11 11 TIL - AuditorAware 구현하기 (2) | 2024.11.11 |