Grazie 마무리

chaean·2024년 11월 28일

프로젝트 - Grazie

목록 보기
7/7

0. 시작

시작이 참 다사다난했다.
약 4월부터 졸업작품 인원 모집부터 아이디어를 선정했지만, 7월쯤 진행하려던 서비스가 법적인 문제가 있다는 것을 알아차리고 새로운 아이디어를 급하게 뽑아내어 진행하는 과정에서 팀원과의 불협화음부터 최종 제출까지....
고생했다 나 자신..

1. 아이디어 선정 배경

학교가 시내와 거리가 멀어 교내에 있는 카페만 사용해야되는 상황에서 점심 시간이나 강의가 몰려있는 시간대에 사람이 굉장히 많이 몰려 대기 시간이 최소 10분이상이다.
바쁘디 바쁜 현대사회에서 매일 이러한 시간을 견디는 것은 쉽지않다.
이를 해결하기 위해 우리는 "모바일 오더 서비스"와 "백오피스 환경"을 제공하기 위해 프로젝트를 진행하게 되었다.

2. 역할

서버 개발을 담당하였고, 주요 개발 기능은 이러하다.

  • 주문 & 결제 기능
  • 상품 관련 기능
  • 매장 관련 기능
  • 배포 ( GCP )
  • DB 설계

ERD

주문부터 결제까지 과정은 아래와 같다.

  1. 주문 생성 (임시) + 결제 금액 산정 (쿠폰, 추가 옵션 등)
  2. 결제
  3. 주문 생성 (최종)

전체적인 Flow는 아래와 같다.

3. 노력..

주문을 생성하는 과정에서 다양한 예외처리, 쿠폰 적용에서 고민을 많이 했고
데이터베이스 접근 횟수를 최소화하여 성능을 최적화하기 위해 노력했다.

List<Long> productIds = orderItemsCreateDTOS.stream()
                .map(OrderItemsCreateDTO::getProductId)
                .toList();

List<Product> products = productRepository.findByProductIdIn(productIds);

Map<Long, Product> productMap = products.stream()
                .collect(Collectors.toMap(Product::getProductId, product -> product));

List<StoreProduct> storeProducts = storeProductRepository.findByStoreAndProductIn(store, productIds);

Map<Long, StoreProduct> storeProductMap = storeProducts.stream()
                .collect(Collectors.toMap(sp -> sp.getProduct().getProductId(), sp -> sp))

1. 문제

기존에는 for문을 사용하여 각 productId에 대해 반복적으로 데이터베이스에 접근.
이는 N개의 productId가 있을 때 N번의 데이터베이스 호출이 발생하여 성능 저하

2. 개선

데이터를 한 번에 조회하여 메모리 내에서 처리함으로써 데이터베이스 접근 횟수 감소!!

1. 문제

정신을 차리고보니 쿠폰이 위와 같이 상속 구조로 만들어져 있었다..

2. 개선

성능 최적화와 가독성을 고려해 코드를 작성했으며,
예외 처리를 통해 발생 가능한 오류를 최대한 통제하려고 노력했다

if (orderItemsCreateDTO.getCouponId() != null) {
    Coupon coupon = couponRepository.findById(orderItemsCreateDTO.getCouponId())
            .orElseThrow(() -> new CouponNotFoundException("쿠폰을 찾을 수 없습니다."));

    if (coupon instanceof ProductCoupon productCoupon) {
        if (productCoupon.getExpirationDate().isBefore(LocalDate.now())) {
            throw new CouponExpiredException("기한이 만료된 쿠폰입니다.");
        }
        if (!userCouponRepository.existsByUserAndCoupon(user, productCoupon)) {
            throw new CouponNotOwnedException("유저가 해당 쿠폰을 가지고 있지 않습니다.");
        }
        Optional<UserCoupon> userCoupon = userCouponRepository.findByUserAndCoupon(user, productCoupon);
        if (userCoupon.isPresent() && userCoupon.get().getIsUsed()) {
            throw new RuntimeException("이미 사용된 쿠폰입니다.");
        }
        orderItems.setCoupon(productCoupon);

        discountPrice = discountPrice.add(new BigDecimal(orderItemsCreateDTO.getProductPrice()));
        userCoupon.get().setIsUsed(true);
        userCouponRepository.save(userCoupon.get());
    } else if (coupon instanceof DiscountCoupon discountCoupon) {
        if (discountCoupon.getExpirationDate().isBefore(LocalDate.now())) {
            throw new CouponExpiredException("기한이 만료된 쿠폰입니다.");
        }
        if (!userCouponRepository.existsByUserAndCoupon(user, discountCoupon)) {
            throw new CouponNotOwnedException("유저가 해당 쿠폰을 가지고 있지 않습니다.");
        }
        Optional<UserCoupon> userCoupon = userCouponRepository.findByUserAndCoupon(user, discountCoupon);
        if (userCoupon.isPresent() && userCoupon.get().getIsUsed()) {
            throw new RuntimeException("이미 사용된 쿠폰입니다.");
        }
        orderItems.setCoupon(discountCoupon);

        BigDecimal productPrice = new BigDecimal(orderItemsCreateDTO.getProductPrice());
        BigDecimal discountRate = discountCoupon.getDiscountRate().divide(BigDecimal.valueOf(100), RoundingMode.HALF_UP);
        discountPrice = productPrice.multiply(discountRate);
        userCoupon.get().setIsUsed(true);
        userCouponRepository.save(userCoupon.get());
    }
}

추가적으로.. 최대한 테스트를 돌려 배포했을 때 문제가 없도록 노력해봤다,,,

4. 후기

졸업작품 멘토님과의 주기적인 소통과 피드백, 코드리뷰를 통해서 배운것이 꽤 많다

1. Buillder 패턴

  • Setter사용 최소화
    객체 생성 이후에도 필드의 값이 변경될 수 있어 객체의 상태를 신뢰하기 어렵다.
    필수 필드선택적 필드 구별하기 어렵다.
    가독성
    캡슐화 : 내부 상태를 보호 + 외부에서 직접 접근 X

2. 배포 경험

GCP를 통해 직접 프로젝트를 빌드하고 배포하는 과정을 경험했다.
인바운드 규칙을 통해 필요한 port를 직접 열고 SSH를 통해 접근하여 jar파일을 직접 실행시켰다.
프로젝트가 변경될 때마다 직접 배포하려니까 이만큼 비효율적인 것이 없더라..
Ubuntu환경에서 MySQL을 설치 등에 어려움을 겪었다.
MySQL을 설치하려했더니 오류가 발생하거나 MySQL대신 MariaDB가 설치되는 오류가 있었다.

== 배포 자동화 + Docker의 참된 뜻을 알게되었다..

3. 첫 협업

제대로된? 첫번째 협업이다.
역시 각자 다른 부분을 맡아 진행하다보니 다른 생각을 가지고 개발을 진행하고 있는 것을 하나로 묶는 과정이 어려웠다.
소통의 부재, 열정의 차이 등 여러 요소들이 작용했다고 생각한다.
팀원과의 갈등도 있었지만, 멘토님이 해주신 명언이 떠오른다..

" OO님 듣기 좋게만 말하는게 커뮤니케이션은 아닙니다"

요즘 개발자 시장에서 괜히 커뮤니케이션을 강조하는 것이 아니였구나...

'
'
'

나의 처음이자 마지막 졸업작품 끝 ~ 😬

profile
백엔드 개발자

0개의 댓글