ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [toby의스프링] 5장 - 서비스 추상화
    먹고 사는 이야기/프로그래밍 2011. 9. 28. 02:01

    Spring에서 중요한 3가지 개념이 있는데, 바로 IoC/DI, 서비스 추상화, AOP 이다.
    이 중 JAVA라는 객체지향 프로그램과 이를 이용한 Spring 프레임워크가 지향하는 가장 중요한 개념을 꼽으라면 바로 서비스 추상화일 것이다.
    (심지어 IoC/DI는 이 서비스 추상화를 용이하게 하기 위한 부차적인 개념이라고 봐도 무방하다는 생각이다.)

    추상화란 하위 시스템의 공통점을 뽑아내서 분리시키는 것을 말한다. 그렇게 하면 하위 시스템이 어떤 것인지 알지 못해도, 또는 하위 시스템이 바뀌더라도 일관된 방법으로 접근할 수가 있다.(본문 p.371)

    다시 말해 서비스 추상화를 적용한다는 것은, 코드들을 비슷한 것들끼리 모으고(강한 응집력) + 서로 다른 코드들이 서로를 구속하지 않는(낮은 결합도) 프로그램 개발을 말하는 것이다. 그리고 이를 위해 Spring은 쉽고 편리한 여러가지 방법들을 제공한다.

    그 중 가장 중요한 것은 이미 앞에서 학습했던 IoC/DI이다.
    이 의존주입을 이용해 각 코드들을 독립적으로 개발하고, 이 코드들이 프레임워크 상에서 서로 연결되도록 하여 서비스 추상화를 제공하는 것이다.

    이 장에서는 예제로 Spring에서 제공하는 트랜잭션을 이용하는 방법, 개발 중 메일발송 테스트를 간단하게 적용하는 방법 등 IoC/DI를 이용해 서비스 추상화를 용이하게 하는 방법들에 대해서 다루고 있다.

    ※ 트랜잭션의 개념이나, 동작원리, Spring 트랜잭션의 사용방법 등에 대해서는 책에서 다룬 usage 외 별도의 설명은 생략한다.

    UserService.java

    package springbook.user.service;

    import java.util.List;

    import org.springframework.mail.MailSender;
    import org.springframework.mail.SimpleMailMessage;
    import org.springframework.transaction.PlatformTransactionManager;    //Spring에서 제공하는 트랜잭션을 사용하기 위해 반드시 선언해야 한다.
    import org.springframework.transaction.TransactionStatus;    //Spring에서 제공하는 트랜잭션을 사용하기 위해 반드시 선언해야 한다.
    import org.springframework.transaction.support.DefaultTransactionDefinition;    //Spring에서 제공하는 트랜잭션을 사용하기 위해 반드시 선언해야 한다.

    //다른 class import

    public class UserService {
        public static final int MIN_LOGCOUNT_FOR_SILVER = 50; //여러 클래스에서 자주 사용되는 값은 스태틱 변수에 담아 처리하는 것이 좋다.
        public static final int MIN_RECCOMEND_FOR_GOLD = 30;

        //UserDao 및 MailSender, PlatformTransactionManager 등 외부 클래스를 사용하기 위해 생성자 및 수정자 메소드를 생성한다.
        private UserDao userDao;
        private MailSender mailSender;
        private PlatformTransactionManager transactionManager;

        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }

        public void setMailSender(MailSender mailSender) {
            this.mailSender = mailSender;
        }
     
        public void setTransactionManager(PlatformTransactionManager transactionManager) {
            this.transactionManager = transactionManager;
        }
        //수정자 메소드 생성 완료


        public void upgradeLevels() {
            TransactionStatus status =  this.transactionManager.getTransaction(new DefaultTransactionDefinition());    //트랜잭션 선언
            try {
                List<User> users = userDao.getAll();
                for (User user : users) {
                    if (canUpgradeLevel(user)) {
                        upgradeLevel(user);    //코드의 시인성을 높이기 위해 실제 처리는 upgradeLevel() 에서 진행한다.
                    }
                }
                this.transactionManager.commit(status);
            } catch (RuntimeException e) {
                this.transactionManager.rollback(status);    //트랜잭션이 실패한 경우 롤백한다.
                throw e;
            }
        }
     
        private boolean canUpgradeLevel(User user) {
            Level currentLevel = user.getLevel();
            switch(currentLevel) {                                  
                case BASIC: return (user.getLogin() >= MIN_LOGCOUNT_FOR_SILVER);
                case SILVER: return (user.getRecommend() >= MIN_RECCOMEND_FOR_GOLD);
                case GOLD: return false;
                default: throw new IllegalArgumentException("Unknown Level: " + currentLevel);
            }
        }

        protected void upgradeLevel(User user) {
            user.upgradeLevel();
            userDao.update(user);    //실제 DB입력 처리는 UserDao에서 한다.
            sendUpgradeEMail(user);
        }
     
        private void sendUpgradeEMail(User user) {
            SimpleMailMessage mailMessage = new SimpleMailMessage();
            mailMessage.setTo(user.getEmail());
            mailMessage.setFrom("useradmin@ksug.org");
            mailMessage.setSubject("Upgrade 안내");
            mailMessage.setText("사용자님의 등급이 " + user.getLevel().name());
      
            this.mailSender.send(mailMessage);
        }
     
        public void add(User user) {
            if (user.getLevel() == null) user.setLevel(Level.BASIC);
            userDao.add(user);
        }
    }


    이때 UserDao 클래스는 DB입출력 로직 외 특별한 처리가 없으므로 생략한다.
    아래 applicationContext.xml 에서 굵게 처리된 부분이 새로 추가된 내용이다.

    test-applicationContext.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xsi:schemaLocation="http://www.springframework.org/schema/beans 
                                               http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

        <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
            <property name="driverClass" value="com.mysql.jdbc.Driver" />
            <property name="url" value="jdbc:mysql://localhost/springbook?characterEncoding=UTF-8" />
            <property name="username" value="spring" />
            <property name="password" value="book" />
        </bean>
     
        <bean id="userDao" class="springbook.user.dao.UserDaoJdbc">
            <property name="dataSource" ref="dataSource" />
        </bean> 
     
        //아래와 같은 방법으로 userService 에서 DI받을 다른 클래스들을 정의한다.
        <bean id="userService" class="springbook.user.service.UserService">
            <property name="userDao" ref="userDao" />

            <property name="transactionManager" ref="transactionManager" />
            <property name="mailSender" ref="mailSender" />
        </bean>


        //메일 발송을 위한 bean 등록이다.
        <!-- 
        //실제 운영서버에서는 주석으로 표시된 부분을 사용한다.
        <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
            <property name="host" value="mail.server.com" />
        </bean>
         -->
        <bean id="mailSender" class="springbook.user.service.DummyMailSender" />
      
     
        //Spring에서 제공하는 트랜잭션매니저를 사용하기 위한 bean 등록이다.
        <bean id="transactionManager
                 class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource" /> 
        </bean>

    </beans> 


    소스가 많이 추가된 것 같지만 결국 applicationContext.xml을 통해 서비스 추상화를 구현한다는 것이 핵심이다.
    이때 중요한 것은 높은 응집도와 낮은 결합도를 준수하는 것이다.
    이것이 핵심이다.

    댓글

Kunner.com since 2000.