비즈니스 계층은 고객의 요구사항을 반영하는 계층으로 프레젠테잇녀 계층과 영속 계층의 중간 다리 역할을 합니다.
영속 계층은 데이터베이스를 기준으로 설계를 나눠 구현 하지만, 비즈니스 계층은 로직을 기준으로 처리 하게 됩니다.
예를들어 "쇼핑몰에서 상품을 구매한다" 라고 가정 했을때
해당 쇼핑몰의 로직이 물건을 구매한 회원에게는 포인트를 올려준다고 하면 영속 계층의 설계는 "상품"과 "회원"으로 나누어서 설계하게 됩니다.
반면에 비즈니스 계층은 상품 영역과 회원 영역을 동시에 사용해서 하나의 로직을 처리하게 되므로 아래와 같은 구조로 표현할 수 있습니다.
현재 예제는 단일한 테이블을 이용하고 있기 때문에 위와 같은 구조는 아니지만, 설계를 할 때는 원칙적으로 영역을 구분해서 작성해야 합니다.
일반적으로 비즈니스 영역에 있는 객체들은 서비스(Service)라는용어를 많이 사용합니다.
9.1 비즈니스 계층의 설정
비즈니스 계층을 위해서 프로잭트 내 org.zerock.service라는 패키지를 작성합니다.
설계를 할 때 각 계층간의 연결은 인터페이스를 이용해서 느슨한(loose) 연결(결합)을 합니다.
게시물은 BoardService 인터페이스와 인터페이스를 구현한 BoardServiceImpl클래스를 선언합니다.
package org.zerock.service;
import java.util.List;
import org.zerock.domain.BoardVO;
public interface BoardService {
public void register(BoardVO barod);
public BoardVO get(Long bno);
public boolean modify(BoardVO board);
public boolean remove(long bno);
public List<BoardVO> getList();
}
BoardService 메서드를 설계할 떄 메서드 이름은 현실적인 로직의 이름을 붙여주는 것이 관례입니다.
명백하게 변환해야 할 데이터가 있는 경우 "select"를 해야 하는 메서드는 리턴 타입을 지정할 수 있습니다.
게시물은 특정한 게시물을 가져오는 get() 메서드와 전체 리스트를 구현하는 getList()의 경우 처음부터 메서드의 리턴타입을 결정해서 진행할 수 있습니다.
BoardService 인터페이스를 구현하는 구현체는 BoardServiceImpl이라는 클래스로 작성합니다.
클래스의 상세 내용은 조금 미뤄두도록 하고, 약간의 로그를 기록할 수 있는 정도의 코드를 작성 합니다.
package org.zerock.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.zerock.domain.BoardVO;
import org.zerock.mapper.BoardMapper;
import lombok.AllArgsConstructor;
import lombok.Setter;
import lombok.extern.log4j.Log4j;
@Log4j
@Service
@AllArgsConstructor
public class BoardServiceImpl implements BoardService{
//Spring 4.3이상 에서 자동 처리
@Setter(onMethod_ =@Autowired)
private BoardMapper mapper;
@Override
public void register(BoardVO barod) {
// TODO Auto-generated method stub
}
@Override
public BoardVO get(Long bno) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean modify(BoardVO board) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean remove(long bno) {
// TODO Auto-generated method stub
return false;
}
@Override
public List<BoardVO> getList() {
// TODO Auto-generated method stub
return null;
}
}
BoardServiceImpl 클래스에서 가장 중요한 부분은 @Service라는 어노테이션 입니다.
@Service는 계층 구조상 주로 비즈니스 영역을 담당하는 객체임을 표시하기 위해 사용합니다.
작성된 어노테이션은 패키지를 읽어 들이는 동안 처리됩니다.
BoardServiceImpl가 정상적으로 동작하기 위해서는 BoardMapper 객체가 필요합니다.
이는 @Autowired와 같이 직접 작성해 줄 수 있고, Setter를 이용해서 처리할 수도 있습니다.
Lombok을 이용한다면 아래와 같은 방식으로 만들 수 있습니다.
//Spring 4.3이상 에서 자동 처리
@Setter(onMethod_ =@Autowired)
private BoardMapper mapper;
스프링 4.3 부터는 단일 파라미터를 받는 생성자의 경우에는 필요한 파라미터를 자동으로 주입할 수 있습니다.
@AllArgsContstructor는 모든 파라미터를 이용하는 생성자를 만들겠다는 뜻이기 때문에, 실제 코드는 아래와 같이 BoardMapper를 주입받는 생성자가 만들어 지게 됩니다.
프로젝트 구조에서 클래스를 조사해보면 스프링 4.3의 자동주입 기능으로 인해 앞에 그림과 같은 형태가 됩니다.
9.1.1 스프링의 서비스 객체 설정(root-context.xml)
비즈니스 계층의 인터페이스와 구현 클래스가 작성되었다면, 이를 스프링 빈으로 인식 시키기 위해 root-context.xml에 @Service 어노테이션이 있는 org.zerock.service 패키지를 스캔(조사) 하도록 추가 해야 합니다.
프로잭스 생성 시 만들어진 root-context.xml의 네임스페이스 탭에서 context 항목을 추가 합니다.
네임스페이스를 추가하면 해당 이름으로 시작하는 태그들을 사용 할 수 있습니다.
root-context.xml에 아래 내용을 추가 합니다.
<context:component-scan base-package="org.zerock.service">
</context:component-scan>
9.2 비즈니스 계층의 구현과 테스트
BoardMappper와 BoardService, BoardServiceImpl에 대한 구조 설정이 완료 되었으므로 /src/test/java 밑에 org.zerock.service.BoardServiceTests 클래스를 이용해 테스트를 진행 합니다.
package org.zerock.service;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import lombok.*;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class BoardServiceTests {
@Setter(onMethod_ = {@Autowired})
private BoardService service;
@Test
public void testExist() {
log.info(service);
assertNotNull(service);
}
}
BoardServiceTests의 첫 테스트는 BoardService 객체가 제대로 주입이 가능한지 확인하는 작업으로 진행 됩니다.
정상적으로 BoardService 객체가 생성되고 BoardMapper가 주입 되었다면 아래와 같이 BoardService 객체와 데이터베이스 관련 로그가 출력 됩니다.
9.2.1 등록 작업의 구현과 테스트
등록 작업은 BoardServiceImpl에서 파리미터로 전달되는 BoardVO 타입의 객체를 BoardMapper를 통해서 처리합니다.
구현되는 코드는 아래와 같습니다.
@Override
public void register(BoardVO board) {
log.info("register......" + board);
mapper.insertSelectKey(board);
}
BoardService는 void 타입으로 설계되었으므로 mapper.insertSelectKey()의 반환값인 int를 사용하고 있지는 않지만, 필요하다면 예외 처리나 void대신 int 타입을 이용 할 수 도 있습니다.
mapper의 insertSelectKey()를 이용해서 나중에 생성된 게시물의 번호를 확인 할 수 있게 작성 했습니다.
테스트 코드는 아래와 같이 작성 합니다.
package org.zerock.service;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.zerock.domain.BoardVO;
import lombok.*;
import lombok.extern.log4j.Log4j;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@Log4j
public class BoardServiceTests {
@Setter(onMethod_ = {@Autowired})
private BoardService service;
@Test
public void testExist() {
log.info(service);
assertNotNull(service);
}
@Test
public void testRegister() {
BoardVO board = new BoardVO();
board.setTitle("새로작성하는 글");
board.setContent("새로작성하는 내용");
board.setWriter("newbie");
service.register(board);
log.info("새로생성된 게시물의 번호 : "+board.getBno());
}
}
testRegister()의 테스트 결과는 다음과 같이 생성된 게시불의 번호를 확인 할 수 있습니다.
9.2.2 목록(리스트) 작업의 구현과 테스트
BoardServiceImpl 클래스에서 현재 테이블에 저정된 모든 데이터를 가져오는 getList()는 아래와 같이 구현 합니다.
//BoardServiceImpl.java에 추가
@Override
public List<BoardVO> getList() {
log.info("getList..............");
return mapper.getList();
}
테스트 코드는 아래와 같이 작성할 수 있습니다.
@Test
public void testGetList() {
service.getList().forEach(board -> log.info(board));
}
실행 해보면 추가된 데이터를 가져오는 것을 확인 할 수 있습니다.
9.2.3 조회 작업의 구현과 테스트
조회는 게시물의 번호가 파라미터이고 BoardVO의 인스턴스가 리턴이 됩니다.
@Override
public BoardVO get(Long bno) {
log.info("get........"+bno);
return mapper.read(bno);
}
테스트 코드는 아래와 같이 작성할 수 있습니다.
//BoardServiceTests.java에 추가
@Test
public void testGet() {
log.info(service.get(1L));
}
테스트결과로 1번 게시물이 검색 되었습니다.
9.2.4 삭제/수정 구현과 테스트
삭제/수정은 메서드의 타입을 void로 설계할 수도 있지만 엄격하게 처리하기 위해 boolean으로 처리 합니다.
정삭적으로 수정과 삭제가 이뤄지면 1이라는 값이 return 되기 때문에 == 1 을 추가하여 true/false를 처리 할 수 있습니다.
테스트 코드는 아래와 같습니다.
@Test
public void testDelete() {
//게시물의 번호 존재 여부를 확인 하고 테스트 할 것
log.info("REMOVE REsult : "+ service.remove(2L));
}
@Test
public void testUpdate() {
BoardVO board = service.get(1L);
if(board == null) {
return;
}
board.setTitle("제목 수정 합니다.");
log.info("Modify RESULT : "+service.modify(board));
}
testDelete()의 경우에는 해당 게시물이 존재할 때 true 를 변환하는것을 확인 할 수 있고,
testUpdate()는 특정 게시물을 먼저 조회하고 title값을 수정 한 후 업데이트 하는 것을 확인 할 수 있습니다.
'개발관련 > 코드로 배우는 스프링 웹 프로젝트(개정판)' 카테고리의 다른 글
Part3 - 화면 처리 Chapter11 (0) | 2023.03.26 |
---|---|
Part3 - 프레젠테이션(웹) 계층의 CRUD 구현 Chapter10 (0) | 2023.03.19 |
Part3 - 영속/비즈니스 계층의 CRUD 구현 Chapter08 (0) | 2023.03.09 |
Part3 - 스프링 MVC 프로젝트의 기본 구성 Chapter07 (0) | 2023.03.05 |
Part2 - 스프링 MVC Controller Chapter06 (0) | 2023.02.20 |