본문 바로가기
Backend/이커머스 api

order api - service(CartService, ProductItemService, ProductSearchService, ProductService) (2)

by 큌 2025. 2. 10.
반응형

service(CartService, ProductItemService, ProductSearchService, ProductService)

ProductSearchService

package com.zerobase.cms.order.service;

import com.zerobase.cms.order.domain.model.Product;
import com.zerobase.cms.order.domain.repository.ProductRepository;
import com.zerobase.cms.order.exception.CustomException;
import com.zerobase.cms.order.exception.ErrorCode;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ProductSearchService {
    private final ProductRepository productRepository;

    public List<Product> searchByName(String name) {
        return productRepository.searchByName(name);
    }

    public Product getByProductId(Long productId) {
        return productRepository.findWithProductItemsById(productId)
                .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_PRODUCT));
    }

    public List<Product> getListByProductIds(List<Long> productIds) {
        return productRepository.findAllByIdIn(productIds);
    }
}

1. 클래스 선언

  • @Service: Spring 프레임워크에게 이 클래스가 서비스(Service) 역할을 한다는 것을 알려줍니다. 서비스는 비즈니스 로직을 처리하는 역할을 합니다.
  • @RequiredArgsConstructor: Lombok 어노테이션입니다. 이 클래스의 final 필드인 productRepository를 매개변수로 받는 생성자를 자동으로 생성해줍니다.
  • public class ProductSearchService: ProductSearchService라는 이름의 클래스를 정의합니다.
    • public: 이 클래스는 어디서든 접근 가능합니다.

2. 필드 선언

  • private final ProductRepository productRepository: 상품 데이터를 데이터베이스에 접근하는 ProductRepository 객체를 저장하는 productRepository 필드를 선언합니다.
    • private: 이 필드는 ProductSearchService 클래스 내에서만 접근 가능합니다.
    • final: 이 필드는 한 번 값이 할당되면 변경할 수 없습니다. 즉, ProductSearchService 객체가 생성된 후에는 productRepository 필드를 변경할 수 없습니다.
    • ProductRepository: 상품 데이터를 데이터베이스에 접근하는 인터페이스입니다.

3. 상품 이름으로 검색 메서드

  • public List<Product> searchByName(String name): 상품 이름을 받아서 해당 이름을 포함하는 상품 목록을 검색하는 메서드입니다.
    • public: 이 메서드는 어디서든 호출 가능합니다.
    • List<Product>: 이 메서드는 Product 객체의 목록을 반환합니다.
    • String name: 검색할 상품 이름을 나타내는 매개변수입니다.
  • return productRepository.searchByName(name): productRepository를 사용하여 데이터베이스에서 상품 이름을 포함하는 상품 목록을 가져와서 반환합니다.
    • productRepository.searchByName(name): ProductRepository 인터페이스에 정의된 searchByName 메서드를 호출하여 데이터베이스에서 상품 목록을 가져옵니다.

4. 상품 ID로 검색 메서드

  • public Product getByProductId(Long productId): 상품 ID를 받아서 해당 ID에 해당하는 상품 정보를 검색하는 메서드입니다.
    • public: 이 메서드는 어디서든 호출 가능합니다.
    • Product: 이 메서드는 Product 객체를 반환합니다.
    • Long productId: 검색할 상품 ID를 나타내는 매개변수입니다.
  • return productRepository.findWithProductItemsById(productId) ...: productRepository를 사용하여 데이터베이스에서 상품 ID에 해당하는 상품 정보를 가져옵니다.
    • productRepository.findWithProductItemsById(productId): ProductRepository 인터페이스에 정의된 findWithProductItemsById 메서드를 호출하여 데이터베이스에서 상품 정보를 가져옵니다. 이 메서드는 상품 정보와 함께 관련된 상품 아이템 정보도 함께 가져옵니다.
    • .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_PRODUCT)): 상품 정보를 가져오지 못하면 NOT_FOUND_PRODUCT 에러 코드를 사용하여 CustomException 예외를 발생시킵니다.

5. 여러 상품 ID로 검색 메서드

  • public List<Product> getListByProductIds(List<Long> productIds): 상품 ID 목록을 받아서 해당 ID 목록에 해당하는 상품 목록을 검색하는 메서드입니다.
    • public: 이 메서드는 어디서든 호출 가능합니다.
    • List<Product>: 이 메서드는 Product 객체의 목록을 반환합니다.
    • List<Long> productIds: 검색할 상품 ID 목록을 나타내는 매개변수입니다.
  • return productRepository.findAllByIdIn(productIds): productRepository를 사용하여 데이터베이스에서 상품 ID 목록에 해당하는 상품 목록을 가져와서 반환합니다.
    • productRepository.findAllByIdIn(productIds): ProductRepository 인터페이스에 정의된 findAllByIdIn 메서드를 호출하여 데이터베이스에서 상품 목록을 가져옵니다.

요약

이 코드는 상품 정보를 검색하는 ProductSearchService 클래스입니다. 상품 이름으로 검색, 상품 ID로 검색, 여러 상품 ID로 검색하는 기능을 제공하며, 데이터베이스 연동 및 예외 처리를 수행합니다. Spring 프레임워크의 @Service 어노테이션을 사용하여 서비스 클래스로 등록하고, Lombok 라이브러리를 사용하여 코드의 보일러플레이트(반복적인 코드)를 줄이고, 가독성을 높였습니다.

  • Repository: Repository는 데이터베이스에 접근하는 인터페이스입니다. Spring Data JPA를 사용하면 Repository 인터페이스만 정의하면 Spring이 자동으로 구현체를 생성해줍니다. Repository를 사용하면 데이터베이스 연동 코드를 간결하게 작성할 수 있고, 데이터베이스를 쉽게 교체할 수 있습니다.
  • 예외 처리(Exception Handling): 예외 처리는 프로그램 실행 중에 발생할 수 있는 예외 상황을 처리하는 방법입니다. 예외 처리를 통해 프로그램이 비정상적으로 종료되는 것을 방지하고, 사용자에게 유용한 오류 메시지를 제공할 수 있습니다.
  • Optional: Optional은 값이 있을 수도 있고 없을 수도 있는 컨테이너 클래스입니다. Optional을 사용하면 null pointer exception을 방지하고, 코드를 더 안전하게 작성할 수 있습니다.

ProductService

package com.zerobase.cms.order.service;

import com.zerobase.cms.order.domain.model.Product;
import com.zerobase.cms.order.domain.model.ProductItem;
import com.zerobase.cms.order.domain.product.AddProductForm;
import com.zerobase.cms.order.domain.product.UpdateProductForm;
import com.zerobase.cms.order.domain.product.UpdateProductItemForm;
import com.zerobase.cms.order.domain.repository.ProductRepository;
import com.zerobase.cms.order.exception.CustomException;
import com.zerobase.cms.order.exception.ErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ProductService {
    private final ProductRepository productRepository;

    @Transactional
    public Product addProduct(Long sellerId, AddProductForm form) {
        return productRepository.save(Product.of(sellerId, form));
    }

    @Transactional
    public Product updateProduct(Long sellerId, UpdateProductForm form) {
        Product product = productRepository.findBySellerIdAndId(sellerId, form.getId())
                .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_PRODUCT));
        product.setName(form.getName());
        product.setDescription(form.getDescription());

        for (UpdateProductItemForm itemForm : form.getItems()) {
            ProductItem item = product.getProductItems().stream()
                    .filter(pi -> pi.getId().equals(itemForm.getId()))
                    .findFirst().orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_ITEM));
            item.setName(itemForm.getName());
            item.setPrice(itemForm.getPrice());
            item.setCount(itemForm.getCount());
        }
        return product;
    }

    @Transactional
    public void deleteProduct(Long sellerId, Long productId) {
        Product product = productRepository.findBySellerIdAndId(sellerId, productId)
                .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_PRODUCT));

        productRepository.delete(product);
    }
}

1. 클래스 선언

  • @Service: Spring 프레임워크에게 이 클래스가 서비스(Service) 역할을 한다는 것을 알려줍니다. 서비스는 비즈니스 로직을 처리하는 역할을 합니다.
  • @RequiredArgsConstructor: Lombok 어노테이션입니다. 이 클래스의 final 필드인 productRepository를 매개변수로 받는 생성자를 자동으로 생성해줍니다.
  • public class ProductService: ProductService라는 이름의 클래스를 정의합니다.
    • public: 이 클래스는 어디서든 접근 가능합니다.

2. 필드 선언

  • private final ProductRepository productRepository: 상품 데이터를 데이터베이스에 접근하는 ProductRepository 객체를 저장하는 productRepository 필드를 선언합니다.
    • private: 이 필드는 ProductService 클래스 내에서만 접근 가능합니다.
    • final: 이 필드는 한 번 값이 할당되면 변경할 수 없습니다. 즉, ProductService 객체가 생성된 후에는 productRepository 필드를 변경할 수 없습니다.
    • ProductRepository: 상품 데이터를 데이터베이스에 접근하는 인터페이스입니다.

3. 상품 추가 메서드

  • @Transactional: 이 메서드를 트랜잭션으로 묶어줍니다. 트랜잭션은 데이터베이스의 상태를 변화시키는 작업의 단위입니다. 트랜잭션은 ACID (Atomicity, Consistency, Isolation, Durability) 속성을 보장해야 합니다.
    • Atomicity: 트랜잭션 내의 모든 작업은 완전히 성공하거나 완전히 실패해야 합니다.
    • Consistency: 트랜잭션이 완료된 후에도 데이터베이스의 무결성이 유지되어야 합니다.
    • Isolation: 동시에 실행되는 트랜잭션은 서로 영향을 미치지 않아야 합니다.
    • Durability: 트랜잭션이 성공적으로 완료되면 그 결과는 영구적으로 데이터베이스에 저장되어야 합니다.
  • public Product addProduct(Long sellerId, AddProductForm form): 판매자 ID와 상품 추가 폼 데이터를 받아서 상품을 추가하는 메서드입니다.
    • public: 이 메서드는 어디서든 호출 가능합니다.
    • Product: 이 메서드는 Product 객체를 반환합니다.
    • Long sellerId: 판매자 ID를 나타내는 매개변수입니다.
    • AddProductForm form: 상품 추가 폼 데이터를 나타내는 매개변수입니다.
  • return productRepository.save(Product.of(sellerId, form)): productRepository를 사용하여 데이터베이스에 상품 정보를 저장하고, 저장된 상품 정보를 반환합니다.
    • Product.of(sellerId, form): Product 클래스에 정의된 of 메서드를 호출하여 판매자 ID와 폼 데이터를 기반으로 새로운 Product 객체를 생성합니다.
    • productRepository.save(...): ProductRepository 인터페이스에 정의된 save 메서드를 호출하여 데이터베이스에 상품 정보를 저장합니다. save 메서드는 Spring Data JPA에서 제공하는 기본적인 데이터 저장 메서드입니다.

4. 상품 수정 메서드

  • @Transactional: 이 메서드를 트랜잭션으로 묶어줍니다.
  • public Product updateProduct(Long sellerId, UpdateProductForm form): 판매자 ID와 상품 수정 폼 데이터를 받아서 상품 정보를 수정하는 메서드입니다.
    • public: 이 메서드는 어디서든 호출 가능합니다.
    • Product: 이 메서드는 수정된 Product 객체를 반환합니다.
    • Long sellerId: 판매자 ID를 나타내는 매개변수입니다.
    • UpdateProductForm form: 상품 수정 폼 데이터를 나타내는 매개변수입니다.
  • Product product = productRepository.findBySellerIdAndId(sellerId, form.getId()) ...: 판매자 ID와 상품 ID를 사용하여 데이터베이스에서 상품 정보를 가져옵니다.
    • productRepository.findBySellerIdAndId(sellerId, form.getId()): ProductRepository 인터페이스에 정의된 findBySellerIdAndId 메서드를 호출하여 데이터베이스에서 상품 정보를 가져옵니다.
    • .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_PRODUCT)): 상품 정보를 가져오지 못하면 NOT_FOUND_PRODUCT 에러 코드를 사용하여 CustomException 예외를 발생시킵니다. orElseThrow는 Optional 객체에서 값을 가져오는 방법 중 하나이며, 값이 없을 경우 예외를 발생시킵니다.
  • product.setName(form.getName()): 상품 이름을 폼 데이터의 상품 이름으로 변경합니다.
  • product.setDescription(form.getDescription()): 상품 설명을 폼 데이터의 상품 설명으로 변경합니다.
  • for (UpdateProductItemForm itemForm : form.getItems()) { ... }: 폼 데이터에 포함된 상품 아이템 목록을 순회하면서 각 상품 아이템 정보를 수정합니다.
    • for: 반복문의 한 종류로, 특정 조건을 만족하는 동안 코드 블록을 반복해서 실행합니다. 여기서는 form.getItems()에서 가져온 각 itemForm에 대해 코드 블록을 실행합니다.
    • UpdateProductItemForm itemForm : form.getItems(): form.getItems()는 UpdateProductForm 객체에서 상품 아이템 목록을 가져오는 메서드입니다. 이 목록에 있는 각 UpdateProductItemForm 객체를 순서대로 itemForm 변수에 할당합니다.
    • ProductItem item = product.getProductItems().stream() ...: 현재 상품(product)에 포함된 상품 아이템 중에서 폼 데이터의 상품 아이템 ID와 일치하는 상품 아이템을 찾습니다.
      • product.getProductItems().stream(): 상품에 속한 상품 아이템 목록을 스트림으로 변환합니다. 스트림은 데이터를 처리하는 데 유용한 도구이며, filter, map, reduce 등의 다양한 연산을 제공합니다.
      • .filter(pi -> pi.getId().equals(itemForm.getId())): 스트림에서 상품 아이템 ID가 폼 데이터의 상품 아이템 ID와 같은 상품 아이템만 필터링합니다. filter는 스트림에서 특정 조건을 만족하는 요소만 선택하는 연산입니다.
      • .findFirst(): 필터링된 상품 아이템 중에서 첫 번째 상품 아이템을 Optional 객체로 반환합니다. findFirst는 스트림에서 첫 번째 요소를 찾는 연산입니다.
      • .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_ITEM)): 상품 아이템을 찾지 못하면 NOT_FOUND_ITEM 에러 코드를 사용하여 CustomException 예외를 발생시킵니다.
    • item.setName(itemForm.getName()): 상품 아이템 이름을 폼 데이터의 상품 아이템 이름으로 변경합니다.
    • item.setPrice(itemForm.getPrice()): 상품 아이템 가격을 폼 데이터의 상품 아이템 가격으로 변경합니다.
    • item.setCount(itemForm.getCount()): 상품 아이템 수량을 폼 데이터의 상품 아이템 수량으로 변경합니다.
  • return product: 변경된 상품 정보를 반환합니다.

5. 상품 삭제 메서드

  • @Transactional: 이 메서드를 트랜잭션으로 묶어줍니다.
  • public void deleteProduct(Long sellerId, Long productId): 판매자 ID와 상품 ID를 받아서 상품 정보를 삭제하는 메서드입니다.
    • public: 이 메서드는 어디서든 호출 가능합니다.
    • void: 이 메서드는 반환값이 없습니다.
    • Long sellerId: 판매자 ID를 나타내는 매개변수입니다.
    • Long productId: 삭제할 상품 ID를 나타내는 매개변수입니다.
  • Product product = productRepository.findBySellerIdAndId(sellerId, productId) ...: 판매자 ID와 상품 ID를 사용하여 데이터베이스에서 상품 정보를 가져옵니다.
    • productRepository.findBySellerIdAndId(sellerId, productId): ProductRepository 인터페이스에 정의된 findBySellerIdAndId 메서드를 호출하여 데이터베이스에서 상품 정보를 가져옵니다.
    • .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_PRODUCT)): 상품 정보를 가져오지 못하면 NOT_FOUND_PRODUCT 에러 코드를 사용하여 CustomException 예외를 발생시킵니다.
  • productRepository.delete(product): productRepository를 사용하여 데이터베이스에서 상품 정보를 삭제합니다.
    • productRepository.delete(product): ProductRepository 인터페이스에 정의된 delete 메서드를 호출하여 데이터베이스에서 상품 정보를 삭제합니다.

요약

이 코드는 상품 정보를 관리하는 ProductService 클래스입니다. 상품 추가, 수정, 삭제 기능을 제공하며, 데이터베이스 연동, 예외 처리, 반복문 활용 등 다양한 프로그래밍 기법을 사용합니다. Spring 프레임워크의 @Service 어노테이션을 사용하여 서비스 클래스로 등록하고, @Transactional 어노테이션을 사용하여 트랜잭션을 관리합니다. 또한, Lombok 라이브러리를 사용하여 코드의 보일러플레이트(반복적인 코드)를 줄이고, 가독성을 높였습니다.

  • 트랜잭션(Transaction): 트랜잭션은 데이터베이스의 상태를 변화시키는 작업의 단위입니다. 트랜잭션은 ACID (Atomicity, Consistency, Isolation, Durability) 속성을 보장해야 합니다. 트랜잭션을 사용하면 데이터베이스의 무결성을 유지하고, 오류 발생 시 롤백(rollback)하여 데이터베이스의 상태를 이전 상태로 되돌릴 수 있습니다.
  • 예외 처리(Exception Handling): 예외 처리는 프로그램 실행 중에 발생할 수 있는 예외 상황을 처리하는 방법입니다. 예외 처리를 통해 프로그램이 비정상적으로 종료되는 것을 방지하고, 사용자에게 유용한 오류 메시지를 제공할 수 있습니다.
  • Repository: Repository는 데이터베이스에 접근하는 인터페이스입니다. Spring Data JPA를 사용하면 Repository 인터페이스만 정의하면 Spring이 자동으로 구현체를 생성해줍니다. Repository를 사용하면 데이터베이스 연동 코드를 간결하게 작성할 수 있고, 데이터베이스를 쉽게 교체할 수 있습니다.
  • Optional: Optional은 값이 있을 수도 있고 없을 수도 있는 컨테이너 클래스입니다. Optional을 사용하면 null pointer exception을 방지하고, 코드를 더 안전하게 작성할 수 있습니다.
  • for 반복문: for 반복문은 코드 블록을 여러 번 반복해서 실행하는 데 사용되는 기본적인 제어문입니다. for 반복문은 초기화, 조건, 증감의 세 부분으로 구성됩니다.
    • 초기화: 반복문을 시작하기 전에 실행되는 부분입니다. 주로 반복 횟수를 제어하는 변수를 초기화합니다.
    • 조건: 코드 블록을 실행할지 여부를 결정하는 부분입니다. 조건이 참(true)이면 코드 블록이 실행되고, 거짓(false)이면 반복문이 종료됩니다.
    • 증감: 코드 블록이 실행된 후에 실행되는 부분입니다. 주로 반복 횟수를 제어하는 변수를 증가시키거나 감소시킵니다.
반응형