신규 Service 개발

Anyframe Core 기반의 Service는 Spring Container 기반 위에 동작하는 서비스로 Spring Bean 작성 방법과 동일하게 생성하면 된다. 여기서는 Anyframe Core의 Technical Service들을 이용하여 사용자 정보를 생성, 수정, 삭제, 조회하는 등의 기능을 제공하는 사용자 관리 서비스를 예로 들어 설명하겠다. 여기서 가이드하는 방법대로 서비스를 생성하는 경우 개발자 간 서비스 작성하는 방식이 표준화되는 장점을 얻을 수 있다. Spring Framework에 대한 자세한 내용은 여기 를 참조하도록 한다.
신규 서비스를 생성하기 위해서는 Dependency Injection 개념을 알고 있어야 한다. 다음은 사용자 관리 서비스 작성 예제이다. 나열된 순서에 따라 서비스를 개발해 보도록 하자.

Dependency Injection

Inversion of Control (IoC)는 component dependency resolution, configuration 과 lifecycle을 해결하기 위한 design pattern이다. IoC의 가장 중요한 부분은 Dependency Resolution 부분이며 사실상 IoC관련 대부분의 문서들이 Dependency Injection 부분을 주로 언급하고있다. IoC는 다른 용어로도 많이 불리우는데 그중 대표적인 하나가 Robert C. Martin이 그의 Dependency Inversion Principle 논문에서 사용한 DIP라는 용어이고, 다른 하나는 Hollywood Principle (Don't call us we'll call you)이라는 용어이다. IoC에 따라서 디자인된 어떤 서비스는 작업을 수행하기 위해 필요한 다른 서비스들을 직접 생성하거나, 획득하기 보다는 이러한 의존성들이 외부에 정의되고 이 정의에 따라서 컨테이너가 이들 서비스들을 공급해주는 방식으로 동작하게 된다. 주어진 서비스에 대한 의존성이 역전되었다는 이러한 이유로 IoC/DIP/Hollywood Principle 이라는 용어가 사용된다. IoC는 서비스들이 서로 loosely coupling이 될 수 있도록 도와주며, 이를 통해 다음과 같은 이점을 얻을 수 있다.
  • 클래스 / 서비스의 재사용성 증가
  • 단위 테스트 용이
  • assemble과 configure를 통한 시스템 구축 용이
Dependency Injection에 대해 보다 자세한 설명이 필요한 경우, 본 매뉴얼의 Spring >> IoC >> Dependencies 를 참고하도록 한다.

Constructor Injection

Constructor Injection은 Dependency Injection 유형의 하나로서, constructor를 통해 객체 자신과 의존 관계가 있는 다른 객체, 서비스, 기타 정보를 얻는 방법을 의미한다.
  • Source codes
  • 아래의 소스에서는 DepBean을 Constructor를 통해서 제공받겠다고 선언하고있다.
    public interface IoCService {
     //중략 
    }
    
    public class IoCServiceImpl implements IoCService 
    { 
    	public IoCServiceImpl(DepBean dependencyBean) { 
    		this.dependencyBean = dependencyBean; 
        }
     //중략 
    }
  • Configuration
  • IoCServiceImpl에 어떠한 DepBean 서비스의 구현체를 넘겨줄지는 Spring의 경우 서비스 설정 파일인 XML 파일에 정의한다. Constructor를 통한 DI 설정인 경우 <constructor-arg>를 사용하여 다음과 같이 정의한다.
    <bean id="IoCService" class="IoCServiceImpl">
        <constructor-arg>
            <value>DepBean</value>
        </constructor-arg>
    </bean>
    
    Constructor의 argument 개수가 2개 이상일 경우는 index를 추가하여 다음과 같이 정의할 수 있다.
    <bean id="IoCService" class="IoCServiceImpl">
        <constructor-arg index="0" ref="beanA" />
        <constructor-arg index="1" ref="beanB" />					     
    </bean>
    

Setter Injection

Setter Injection은 Dependency Injection 유형의 하나로서 setter method를 통해 객체 자신과 의존 관계가 있는 다른 객체, 서비스, 기타 정보를 얻는 방법을 의미한다.
  • Source codes
  • 아래의 소스에서는 pageSize, pageUnit을 Setter 메소드를 통해서 제공받겠다고 선언하고있다.
    public class IoCServiceImpl implements IoCService { 
    	public void setPageSize(int pageSize) { 
    		this.pageSize = pageSize; 
    	}
    
    	public void setPageUnit(int pageUnit) {
    		this.pageUnit = pageUnit; 
    	} 
    	//중략 
    }
  • Configuration
  • IoCServiceImpl에 어떠한 값을 넘겨줄지는 Spring의 경우 서비스 설정 파일인 XML 파일 설정에 정의한다. setter를 통한 DI 설정인 경우 <property>를 사용하여 다음과 같이 정의한다. <property> 의 name 속성값에 해당하는 setter 메소드를 호출하여 값이 설정된다.
    <bean id="IoCService" >
    	<property name="pageSize" value="10"/>
    	<property name="pageUnit" value="10"/>
    </bean>
    

UserService Configuration

설계 산출물인 클래스 다이어그램을 기반으로 신규 서비스인 사용자 관리 서비스를 개발해보도록 하자. 아래의 클래스 다이어그램을 통해 도출된 사용자 관리 서비스의 해당 클래스 명을 기반으로 configuration 파일을 작성한다.
다음은 사용자 관리 서비스를 위한 클래스 다이어그램이다.

applicationContext-user-aop.xml

앞서 제시된 클래스 다이어그램의 참조 관계를 기반으로 Spring 기반의 속성 정의 파일을 생성한다. 다음은 사용자 관리 서비스의 속성을 정의한 applicationContext-user-aop.xml 의 일부이다.
<bean id="UserServiceWithXML" class="com.sds.emp.user.services.impl.UserServiceImplWithXML">
	<property name="userDAO">
		<bean class="com.sds.emp.user.services.impl.UserDAO">
			<property name="queryService" ref="oracle_queryservice" />
			<property name="propertiesService" ref="propertiesService" />
		</bean>
	</property>
</bean>

  • UserServiceWithXML Bean의 속성
  • 아래는 UserServiceImplWithXML Bean의 속성 정의 내용이다. 위의 클래스 다이어그램에서 보는 바와 같이 UserServiceImplWithXML는 UserDAO라는 클래스를 참조하고 있으므로 UserServiceImplWithXML가 UserDAO의 인스턴스를 참조할 수 있도록 속성 정의가 필요하다.
    Property Name
    Description
    Required
    Default Value
    userDAO Data Access Object 클래스로, 사용자 관리 서비스의 기능을 제공하기 위해 실제 DB에 연결하여 CRUD를 수행하는 역할을 담당한다. com.sds.emp.user.services.impl.UserServiceImplWithXML 클래스 내에 setUserDAO 메소드가 존재하고 있어야 한다.(Setter Injection 방식 사용함)
    Y
    N/A
    또한, 위의 클래스 다이어그램에서 보는 바와 같이 UserDAO 클래스는 노란색 박스 형태로 표현된 QueryService와 PropertiesService를 참조하고 있으므로 UserDAO가 QueryService와 PropertiesService의 인스턴스를 참조할 수 있도록 속성 정의가 필요하다.

    Property Name
    Description
    Required
    Default Value
    queryService UserDAO 클래스 내에서 Anyframe Core의 Technical Service인 QueryService를 사용하기 위해 QueryService의 Bean id를 참조하고 있으며 com.sds.emp.user.services.impl.UserDAO 클래스 내에 setQueryService 메소드가 존재하고 있어야 한다.(Setter Injection 방식 사용함) QueryService 관련 내용은 매뉴얼의 Tech. Service >> Query 를 참고하도록 한다.
    Y
    N/A
    propertiesService UserDAO 클래스 내에서 Anyframe Core의 Technical Service인 PropertiesService를 사용하기 위해 PropertiesService의 Bean id를 참조하고 있으며 com.sds.emp.user.services.impl.UserDAO 클래스 내에 setPropertiesService 메소드가 존재하고 있어야 한다.(Setter Injection 방식 사용함) PropertiesService 관련 내용은 매뉴얼의 Tech. Service >> Properties 를 참고하도록 한다.
    Y
    N/A

  • 트랜잭션 관리가 필요한 경우
  • 작성하고자 하는 서비스의 특성에 따라 서비스의 메소드 단위 별로 트랜잭션을 관리해야 하는 경우와 관리하지 않아도 되는 경우가 있다. 트랜잭션 관리가 필요한 경우에는 아래와 같이 Transaction AOP를 이용하여 선언적으로 정의할 수 있다. 다음은 사용자 관리 서비스에 대해 트랜잭션 속성을 부여한 applicationContext-user-aop.xml 의 일부이다. Transaction 관련 내용은 매뉴얼의 Tech. Service >> Transaction 을 참고하도록 한다.
    <tx:advice id="txAdvice" transaction-manager="oracleTransactionManagerDataSource">
    	<tx:attributes>
    		<tx:method name="*" no-rollback-for="com.sds.emp.common.EmpException"/>
    	</tx:attributes>
    </tx:advice>
    
    <aop:config>
    	<aop:pointcut id="userServiceOperations" 
    	    expression=
    	       "execution(* com.sds.emp.user.services.impl.UserServiceImplWithXML.*(..))"/>
    	<aop:advisor advice-ref="txAdvice" pointcut-ref="userServiceOperations"/>
    </aop:config>
    

UserService Source Codes

다음은 사용자 관리 서비스를 구성하는 소스 코드 예제들이다. 앞서 설계한 대로 Service에 대한 Interface, Implementation, DAO, VO 클래스들을 작성하도록 한다.

UserService Interface Class

다음은 인터페이스 클래스의 소스 코드인 UserService.java 의 일부이다.
  • UserService
  • public interface UserService {	
    	/** 특정 서비스에 대한 식별자로써 사용하기 위해 ROLE을 정의할 수 있다.
    	패키지명을 포함한 클래스명을 식별자로 하는 경우 동일한 Bean Id에 대한 사용을 방지할 수 있게 된다.
    	이러한  경우 특정 Bean을 호출할 때 해당 Bean Id를 직접 문자열로 직접 입력하게 되면 오타로 인해 
    	오류를 발생시킬 수 있으므로, 별도의 static 변수를 정의하여 UserService.ROLE과 같이 사용하는 
    	것도 고려해 볼 수 있다.
    	 */
    	String ROLE = UserService.class.getName();
    	
    	/** 특정 서비스 내에서 동일한 LOGGER를 사용하기 위해 정의할 수 있다. */
    	Log LOGGER = LogFactory.getLog(UserService.class);
    
    	/** 사용자 목록 조회 */
    	Page getUserList(SearchVO searchVO) throws EmpException;
    
    	/** 사용자 정보 조회 */
    	UserVO getUser(String userId) throws EmpException;
    
    	/** 사용자 생성 */
    	void addUser(UserVO userVO) throws EmpException;
    
    	/** 사용자 정보 수정 */
    	void updateUser(UserVO userVO) throws EmpException;
    
    	/** 사용자 아이디 중복 체크 */
    	boolean checkDuplication(String userId) throws EmpException;
    	
    	// 중략 ...
    }
    

UserService Implementation Class

다음은 서비스 구현 클래스 소스 코드인 UserServiceImplWithXML.java 의 일부이다. Exception 발생 시 ERROR 레벨의 로그를 남길 때 사용하기 위한 Logging 서비스에 대한 사용법은 매뉴얼의 Tech. Service >> Logging 을 참고하도록 한다.
  • UserServiceImplWithXML
  • public class UserServiceImplWithXML implements UserService, ApplicationContextAware {
    	private UserDAO userDAO;
    	private MessageSource messageSource;
    
    	/** userDAO setter injection */
    	public void setUserDAO(UserDAO userDAO) {
    		this.userDAO = userDAO;
    	}
    	
    	/** ApplicationContextAware 클래스의 구현 메소드로 특정 서비스의 구현 클래스는 
    	MessageSource Bean을 인식하기 위하여 implements ApplicationContextAware해야 한다*/
    	public void setApplicationContext(ApplicationContext applicationContext)
    			throws BeansException {
    		messageSource 
    		    = (MessageSource) applicationContext.getBean("messageSource");
    	}
    
    	/** 사용자 목록 조회 구현 메소드 */
    	public Page getUserList(SearchVO searchVO) throws EmpException { 
    		// 중략...
    	}
    
    	/** 사용자 정보 조회 구현 메소드 */
    	public UserVO getUser(String userId) throws EmpException {
    		// 중략...
    	}
    
    	/** 사용자 생성 구현 메소드 */
    	public void addUser(UserVO userVO) throws EmpException {
    		// 중략...
    	}
    
    	/** 사용자 정보 수정 구현 메소드 */
    	public void updateUser(UserVO userVO) throws EmpException {
    		// 중략...
    	}
    
    	/** 사용자 아이디 중복 체크 구현 메소드 */
    	public boolean checkDuplication(String userId) throws EmpException {
    		// 중략...
    	}
    	
    	// 중략...
    }

UserService Data Access Object

다음은 DAO 클래스의 소스 코드인 UserDAO.java 의 일부이다.
  • UserDAO
  • public class UserDAO {
    
    	/** an instance variable for the queryService. */
    	protected IQueryService queryService;
    
    	/** an instance variable for the propertiesService. */
    	protected IPropertiesService propertiesService;
    
    	/** QueryService 사용을 위한 Setter Injection */
    	public void setQueryService(IQueryService queryService) {
    		this.queryService = queryService;
    	}
    
    	/** PropertiesService 사용을 위한 Setter Injection */
    	public void setPropertiesService(IPropertiesService propertiesService) {
    		this.propertiesService = propertiesService;
    	}
    
    	/** 사용자 목록 조회 */
    	public Page getUserList(SearchVO searchVO) throws Exception {
    	
    		// PropertiesService 사용 모습
    		int pageSize = propertiesService.getInt("PAGE_SIZE");
    		int pageUnit = propertiesService.getInt("PAGE_UNIT");
    
    		// QueryService 사용 모습
    		HashMap userListMap = queryService
    		      .findWithRowCount("getUserList", iVal, pageIndex, pageSize);
    		중략...
    	}
    
    	/** 사용자 정보 조회 */
    	public UserVO getUser(String userId) throws Exception {
    		// QueryService 사용 모습
    		Collection userCollection = queryService
    		                              .find("getUser", new Object[] { userId });
    		중략...		
    	}
    
    	/** 사용자 생성 */
    	public void addUser(UserVO userVO) throws Exception {
    		중략...	
    		// QueryService 사용 모습		
    		queryService.create("addUser", new Object[] { userId, userName
    			, password, ssn, slYn, birthDay, age, cellPhone, addr, email
    			, emailYn, imageFile });
    	}
    
    	/** 사용자 정보 수정 */
    	public void updateUser(UserVO userVO) throws Exception {
    		중략...	
    		// QueryService 사용 모습			
    		queryService.update("updateUser", new Object[] { userName, ssn, slYn
    			,birthDay, age, cellPhone, addr, email, emailYn, userId });
    	}
    }

UserService Value Object

다음은 입력/수정을 위한 사용자 정보를 포함한 UserVO 클래스의 소스 코드인 UserVO.java 와 조회를 위한 검색 조건을 포함한 SearchVO 클래스의 소스 코드인 SearchVO.java 의 일부이다. 이때 Value Object는 java.io.Serializable를 implement하도록 하며 멤버 변수는 private으로 선언하고 setter, getter 메소드는 public으로 선언하도록 한다.
  • UserVO
  • public class UserVO implements Serializable {
        private String userId;
        private String userName;
    
        public void setUserId(String param) {
    	this.userId = param;
        }
    
        public String getUserId() {
    	return this.userId;
        }
    	// 중략...
  • SearchVO
  • public class SearchVO implements Serializable {
        private String searchCondition = "";
        public String getSearchCondition() {
    	return searchCondition;
        }
    	// 중략...
    

UserService TestCase

사용자 관리 서비스에서 제공하는 기능(사용자 목록 조회, 사용자 정보 조회, 사용자 생성, 사용자 정보 수정 등)에 대해서 테스트 데이터를 이용하여 제대로 동작하는지 확인해 본다.

UserService 테스트

다음은 사용자 관리 서비스에 대한 테스트 케이스인 UserServiceWithXMLTest.java 의 일부이다.
  • UserServiceWithXMLTest
  • public class UserServiceWithXMLTest extends AbstractTest {
    
    	// 중략 ...
    	
    	public void testAddUser() throws EmpException {
    		UserService userService 
    		    = (UserService) context.getBean("UserServiceWithXML");
    		String userID = "woos41";
    
    		{
    			// 1. add user
    			UserVO userVO1 = new UserVO();
    			userVO1.setUserId(userID);
    			userVO1.setUserName("gang");
    			userVO1.setPassword("gang");
    			userVO1.setRole("user");
    			userVO1.setSsn("1234567890");
    
    			userVO1.setSlYn("Y");
    			userVO1.setBirthDay("19750319");
    
    			userVO1.setAge(null);
    			userVO1.setCellPhone("1234567890");
    			userVO1.setAddr("kamala road");
    			userVO1.setEmail("ga@samsung.com");
    			userVO1.setEmailYn("y");
    			userVO1.setImageFile("ga");
    			userVO1.setRegDate(null);
    
    			userService.addUser(userVO1);
    
    			// 2. get user
    			userVO1 = userService.getUser(userID);
    
    			// 3. assert
    			boolean checked = userVO1.getUserId().equals(userID);
    			if (!checked)
    				throw new EmpException("fail to add user.");
    		}
    	}	
    	
    	public void testUpdateUserWithNotExistUser() throws EmpException {
    		// 중략...
    
    Spring에서 TestCase는 다음과 같은 절차로 생성할 수 있다.
    • AbstractTest클래스를 상속받아 TestCase 클래스를 작성
    • getConfigLocations() 메소드에 Spring 속성 정의 XML위치 명시
    • 테스트 메소드에 구현
    • main 메소드 구현

Resources