이강현님 포트폴리오 최종 작업 내용 피드백
메가스터디IT아카데미 SpringBoot 백엔드 개발자 과정 주말반 (25.02.22 ~ 25.09.13). 이강현님의 포트폴리오 4주차 작업 내용에 대한 피드백
이강현님 포트폴리오 최종 작업 내용 피드백
작성일: 2025-09-15
github에 push된 코드를 바탕으로 장점, 단점, 그리고 개선사항을 정리했습니다.
HTML,CSS는 최종적으로 화면에 보이는 결과가 가장 우선됩니다. 아래 사항은 참고만 하시고 적용 여부는 본인이 직접 판단하시면 됩니다.
안녕하세요, 강현님! 마지막 피드백인 만큼 지난 작업들을 전체적으로 살펴보며, 특히 이번에 새로 구현된 장바구니 기능을 중심으로 꼼꼼하게 리뷰를 진행했습니다. 전반적으로 아주 훌륭하게 프로젝트를 마무리하고 계신다는 인상을 받았습니다. 아래 정리된 내용을 바탕으로 프로젝트를 더 멋지게 완성해나가시길 바랍니다!
👏 잘된 점 (Good Points)
1. RESTful API 설계 및 계층 분리
파일: CartRestController.java
, CartController.java
CartController
는 장바구니 페이지를 보여주는 역할만 하고, 실제 데이터 처리는 CartRestController
에서 AJAX 호출로 처리하도록 역할을 명확하게 분리한 점이 매우 좋습니다. 이는 현대적인 웹 애플리케이션의 구조를 잘 이해하고 적용한 결과입니다.
CartRestController.java
:@PostMapping
,@GetMapping
,@PutMapping
,@DeleteMapping
등 HTTP 메서드에 맞춰 기능을 명확하게 분리하고,ResponseEntity
를 사용해 서버의 처리 결과(성공, 실패, 오류 등)를 명확하게 클라이언트에게 전달하는 방식은 RESTful API의 좋은 사례입니다.
2. 효율적인 데이터베이스 연동 (Mapper)
파일: mappers/cart/CartMapper.java
MyBatis의 Annotation을 활용하여 Mapper 인터페이스를 깔끔하게 구현했습니다. 특히, 장바구니 목록을 가져올 때 products
테이블과 JOIN
하고, @Results
를 이용해 Cart
모델 안의 Product
객체에 바로 매핑한 부분은 매우 인상적입니다.
mappers/cart/CartMapper.java
(21~32행)1 2 3 4 5 6 7 8 9 10 11 12 13
@Select("SELECT c.id, c.user_id, c.product_id, c.quantity, " + "p.id as p_id, p.name as p_name, p.price as p_price, p.image_url as p_imageUrl " + "FROM cart c " + "JOIN products p ON c.product_id = p.id " + "WHERE c.user_id = #{userId}") @Results({ @Result(property="id", column="id"), // ... @Result(property="product.id", column="p_id"), @Result(property="product.name", column="p_name"), // ... }) List<Cart> selectCartByUserId(String userId);
이렇게 하면 서비스 로직에서 불필요한 추가 쿼리를 호출할 필요가 없어(N+1 문제 방지) 성능적으로도 유리하고 코드도 간결해집니다.
3. 견고한 비즈니스 로직 (Service)
파일: services/impl/CartServiceImpl.java
장바구니에 상품을 추가할 때, 이미 담겨있는 상품인지 먼저 확인하고, 존재할 경우 수량을 더하고, 없을 경우 새로 추가하는 로직을 구현한 점이 좋습니다. 이는 실제 쇼핑몰의 장바구니 기능의 핵심 로직을 정확하게 파악하고 구현한 것입니다.
services/impl/CartServiceImpl.java
(28~39행)1 2 3 4 5 6 7 8 9 10 11 12 13
Cart existing = cartMapper.findCartItem(userId, productId); if (existing != null) { // 이미 있으면 수량 업데이트 cartMapper.updateCartQuantity(existing.getId(), existing.getQuantity() + quantity); } else { // 없으면 새로 추가 Cart cart = new Cart(); cart.setUserId(userId); cart.setProductId(productId); cart.setQuantity(quantity); cartMapper.insertCart(cart); }
4. 테스트 코드 작성
파일: CartMapperTests.java
, CartServiceTests.java
새로운 기능(장바구니)을 구현하면서, 그 기능이 올바르게 동작하는지 검증하기 위한 테스트 코드를 작성했다는 점은 정말 칭찬할 만한 부분입니다. 테스트 코드를 작성하는 습관은 안정적인 소프트웨어를 만드는 개발자의 가장 중요한 역량 중 하나입니다.
💡 개선점 (Areas for Improvement)
1. 서비스 로직의 트랜잭션 처리 (❗반드시 수정 권고)
파일: services/impl/CartServiceImpl.java
addToCart
메서드는 상품 조회(findCartItem
)와 추가/수정(insertCart
/updateCartQuantity
)이라는 2단계의 DB 작업을 수행합니다. 만약 동시에 두 개의 요청이 들어와 findCartItem
이 모두 null을 반환하면, 두 요청 모두 insert
를 시도하게 되어 오류가 발생하거나 데이터 정합성이 깨질 수 있습니다.
이러한 문제를 방지하기 위해 여러 DB 작업을 하나의 묶음(원자적 연산)으로 처리하는 트랜잭션 처리가 필수적입니다. Spring에서는 @Transactional
어노테이션 하나로 간단하게 해결할 수 있습니다.
- 개선 제안:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
// services/impl/CartServiceImpl.java import org.springframework.transaction.annotation.Transactional; // import 추가 @Service @RequiredArgsConstructor public class CartServiceImpl implements CartService { private final CartMapper cartMapper; @Override @Transactional // 이 어노테이션을 추가하여 해당 메서드를 트랜잭션으로 묶어주세요. public void addToCart(String userId, int productId, int quantity) { // ... 기존 로직 ... } // ... 다른 메서드들도 데이터 변경이 일어난다면 추가를 고려해보세요. @Override @Transactional public void updateQuantity(Long id, int quantity) { // ... } @Override @Transactional public void deleteItem(Long id) { // ... } }
2. 의미있는 테스트 코드 작성 (❗반드시 수정 권고)
파일: CartMapperTests.java
, CartServiceTests.java
테스트 코드를 작성한 것은 매우 좋지만, 현재 코드는 단순히 메서드를 실행만 해볼 뿐, 그 결과가 올바른지 검증(Assertion)하는 부분이 빠져있습니다. 테스트의 핵심은 “예상한 결과가 실제로 나왔는가?”를 확인하는 것입니다.
- 개선 제안:
Assertions
클래스를 사용하여 결과를 검증하는 코드를 추가해야 합니다.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// CartServiceTests.java import org.junit.jupiter.api.Assertions; // import 추가 import java.util.List; // ... @Test public void addToCart() throws Exception { // given: 테스트 준비 String userId = "testUser"; int productId = 1; int initialQuantity = 1; // when: 테스트할 메서드 실행 cartService.addToCart(userId, productId, initialQuantity); // then: 결과 검증 List<Cart> items = cartService.getCartItems(userId); Assertions.assertFalse(items.isEmpty(), "장바구니에 상품이 추가되어야 합니다."); // 리스트가 비어있지 않은지 확인 Assertions.assertEquals(1, items.size(), "장바구니에 상품이 1개만 있어야 합니다."); // 상품 종류가 1개인지 확인 Assertions.assertEquals(initialQuantity, items.get(0).getQuantity(), "추가된 상품의 수량이 일치해야 합니다."); // 수량 확인 }
다른 테스트 메서드들(
update
,delete
등)에도 이와 같이 실행 후 결과가 예상대로 변경되었는지 확인하는 검증 코드를 꼭 추가해 보세요.
3. 컨트롤러의 중복 코드 개선 (💡수정 제안)
파일: controllers/cart/CartRestController.java
CartRestController
의 모든 메서드에서 세션 유무를 확인하는 코드가 반복적으로 나타납니다.
1
2
3
4
// CartRestController.java
if (session.getAttribute("memberInfo") == null) {
return ResponseEntity.badRequest().body("로그인이 필요합니다.");
}
이러한 중복 코드는 Spring의 Interceptor를 사용하면 훨씬 깔끔하게 관리할 수 있습니다. 이미 프로젝트에 MyInterceptor.java
파일이 있으니, 이를 활용하여 특정 URL 패턴(api/cart/**
등)에 접근하기 전 로그인 상태를 미리 확인하도록 만들면 컨트롤러 코드가 훨씬 간결해지고 유지보수하기 좋아집니다.
📝 최종 총평
4주간의 짧은 기간 동안 SpringBoot의 기본 구조부터 데이터베이스 연동, 그리고 동적인 프론트엔드 구현까지 정말 많은 것을 배우고 적용했습니다. 특히 이번 장바구니 기능 구현은 백엔드 개발의 핵심적인 요소(계층 분리, DB 연동, 비즈니스 로직 처리)를 종합적으로 잘 보여주는 훌륭한 결과물이라고 생각합니다.
스스로 테스트 코드를 작성하려고 시도한 점이 가장 인상 깊었습니다. 이는 좋은 개발자로 성장하기 위한 매우 중요한 첫걸음입니다.
앞으로 개발을 계속해 나가면서, 이번 피드백에서 언급된 트랜잭션 처리의 중요성과 올바른 테스트 코드 작성법에 대해 조금 더 깊이 학습하고 체득한다면, 훨씬 더 안정적이고 완성도 높은 프로그램을 만드는 개발자로 성장할 수 있을 것이라 확신합니다.
정말 고생 많으셨습니다! 성공적인 프로젝트 마무리와 앞으로의 멋진 개발자로서의 여정을 응원하겠습니다!