앞서 DAO 설계에서 구성한 요소들을 클라이언트의 요청과 연결하려면 컨트롤러
와 서비스
를 생성해야 함
서비스 레이어에서는 도메인 모델
을 활용 해 애플리케이션에서 제공하는 핵심 기능 담당
-> 세부 기능 정의 해야 함
모든 로직을 서비스 레이어에서 담당하기 어려워 서비스 로직
과 비즈니스 로직
을 분리하기도 함
도메인을 활용한 세부 기능 -> 비즈니스 레이어
에서 구현
기능을 종합 해 핵심 기능 전달하도록 구성 -> 서비스 레이어
여기선 서비스 레이어에서 비즈니스 레이어에서 로직 처리 하였음
서비스 객체는 DAO와 마찬가지로 추상화해서 구성
ProductDto
@Getter
@Setter
@AllArgsConstructor
public class ProductDto {
private String name;
private int price;
private int stock;
}
ProductResponseDto
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class ProductResponseDto {
private Long number;
private String name;
private int price;
private int stock;
}
필요에 따라 빌더 메서드
나 hashCode/equals
메서드 추가
빌더 패턴을 따른 메서드
데이터 클래스를 사용할 때 생성자로 초기화 할 경우 모든 필드에 값을 넣거나
null을 명시적으로 사용
해야 함
빌더 패턴 이용시, 필요만 데이터만 설정할 수 있어 유연성 확보 가능
public class ProductResponseDto2 {
private Long number;
private String name;
private int price;
private int stock;
public static ProductResponseDto2Builder builder(){
return new ProductResponseDto2Builder();
}
public static class ProductResponseDto2Builder{
private Long number;
private String name;
private int price;
private int stock;
ProductResponseDto2Builder(){}
public ProductResponseDto2Builder number(Long number){
this.number = number;
return this;
}
public ProductResponseDto2Builder name(String name){
this.name = name;
return this;
}
public ProductResponseDto2Builder price(int price){
this.price = price;
return this;
}
public ProductResponseDto2Builder stock(int stock){
this.stock = stock;
return this;
}
public ProductResponseDto2 build(){
return new ProductResponseDto2(number, name, price,stock);
}
@Override
public String toString() {
return "ProductResponseDto.ProductResponseDto2Builder{" +
"number=" + this.number +
", name='" + this.name + '\'' +
", price=" + this.price +
", stock=" + this.stock +
'}';
}
}
}
이후 서비스 인터페이스 작성 -> CRUD 기능 호출을 위해 간단하게 메서드 정의
public interface ProductService {
ProductResponseDto getProduct(Long number);
ProductResponseDto saveProduct(ProductDto productDto);
ProductResponseDto changeProductName(Long number, String name) throws Exception;
void deleteProduct(Long number) throws Exception;
}
이 인터페이스는 DAO에서 구현한 기능
을 서비스 인터페이스에서 호출
해 결과값을 가져오는 작업 수행하도록 설계
서비스에서는 클라이언트가 요청한 데이터를 적절히 가공해 컨트롤러에게 넘기는 역할
리턴타입 : DTO
-> DAO 객체에서 엔티티 타입을 사용하는 것을 고려하면 서비스 레이어에서 DTO 객체와 엔티티 객체를 각 레이어에 변환해서 전달하는 역할도 수행
한다고 볼 수 있다
_정리해보면 DB와 밀접한 관련이 있는 데이터 엑세스 레이어까지는 엔티티 객체를 사용하고, 클라이언트와 가까워지는 다른 레이어에서는 데이터를 교환하는데 DTO 객체를 사용하는 것이 일반적이다!!!
서비스와 DAO 사이에서 엔티티로 전달한다고 표현했지만 규정에 따라 DTO를 사용하기도 함
소량 데이터 전달 시 DTO , Entity 사용하지 않기도!!
@Service
public class ProductServiceImpl implements ProductService {
private final ProductDAO productDAO;
@Autowired
public ProductServiceImpl(ProductDAO productDao){
this.productDAO = productDao;
}
@Override
public ProductResponseDto getProduct(Long number) {
return null;
}
@Override
public ProductResponseDto saveProduct(ProductDto productDto) {
return null;
}
@Override
public ProductResponseDto changeProductName(Long number, String name) throws Exception {
return null;
}
@Override
public void deleteProduct(Long number) throws Exception {
}
}
인터페이스 구현체 클래스에서는 DAO 인터페이스를 선언하고 @Autowired
를 저장한 생성자를 통해 의존성을 주입받음
@Override
public ProductResponseDto getProduct(Long number) {
Product product = productDAO.selectProduct(number);
ProductResponseDto productResponseDto = new ProductResponseDto();
productResponseDto.setNumber(product.getNumber());
productResponseDto.setName(product.getName());
productResponseDto.setPrice(product.getPrice());
productResponseDto.setStock(product.getStock());
return productResponseDto;
}
현재 서비스 레이어에서는 DTO 객체와, 엔티티 객체가 공존하여 변환 작업 필요
앞서 작성한 @AutoWired 에서 DTO 객체를 넣고 초기화 작업을 수행
-> 이는 빌더 패턴을 활용하거나, 엔티티 객체 or DTO 객체 내부에 변환하는 메서드 추가해 간단하게 전환 가능
@Override
public ProductResponseDto saveProduct(ProductDto productDto) {
Product product = new Product();
product.setName(productDto.getName());
product.setPrice(productDto.getPrice());
product.setStock(productDto.getStock());
product.setCreatedAt(LocalDateTime.now());
product.setUpdatedAt(LocalDateTime.now());
Product savedProduct = productDAO.insertProduct(product);
ProductResponseDto productResponseDto = new ProductResponseDto();
productResponseDto.setName(savedProduct.getName());
productResponseDto.setNumber(savedProduct.getNumber());
productResponseDto.setPrice(savedProduct.getPrice());
productResponseDto.setStock(savedProduct.getStock());
return productResponseDto;
}
저장 메서드 로직
-> 전달받은 DTO객체를 통해 엔티티 객체를 생성
해서 초기화환후 DAO 객체로 전달 하면됨
리턴 타입은 보통 boolean or void 로 하는데 비즈니스 로직 성격에 따라 결정하자!
saveProduct() 메서드는 상품 정보를 전달하고 애플리케이션을 거쳐 DB에 저장하는 역할 수행
현재 데이터 조회 메서드는 DB에서 인덱스를 통해 값을 찾아야 하는데 , void로 저장 메서드 구현 시 클라이언트가 저장한 데이터의 인덱스 값을 알 방법이 없다
-> 데이터를 저장하면서 가져온 인덱스를 DTO에 담아 클라이어트에게 전달하였음(savedProduct 밑)
void 형식
으로 메서드 작성하였다면 조회 메서드 추가로 구현하고 클라이언트에서 한번 더 요청 해야 함
public ProductResponseDto changeProductName(Long number, String name) throws Exception {
Product changedProduct = productDAO.updateProductName(number,name);
ProductResponseDto productResponseDto = new ProductResponseDto();
productResponseDto.setNumber(changedProduct.getNumber());
productResponseDto.setName(changedProduct.getName());
productResponseDto.setPrice(changedProduct.getPrice());
productResponseDto.setStock(changedProduct.getStock());
return productResponseDto;
}
상품 정보 중 이름을 변경하는 작업 수행
이름을 변경하기 위해 클라이언트로 부터 대상 식별할 수 있는 인덱스 값
과 변경 이름 받아옴
-> 견고하게 작성하기 위해 기존 이름도 받아와 가져온 상품정보와 일치하는지 검증하는 단계 추가하기도 함
핵심 비즈니스 로직
-> 레코드의 이름 칼럼을 변경하는 것
실제 레코드 값을 변경하는 것은 DAO
에서 진행하기 때문에 서비스 레이어에서는 해당 메서드를 호출하여 결과값을 받아옴
@Override
public void deleteProduct(Long number) throws Exception {
productDAO.deleteProduct(number);
}
상품 정보를 삭제하는 메서드는 리포지토리에서 제공하는 Delete 메서드 사용 시 리턴하는 값이 지정되있지 않기에 리턴타입을 void
로 지정해 메서드 구현