Spring กับ Service Layer ครับ

January 27th, 2009 by roofimon Leave a reply »

ขออภัยทุกท่านที่ปล่อยให้รอตอนนี้ sourcecode พร้อมแล้วที่ google code ครับ
ตอนล่าสุดนี้จะเป็นเรื่องเกี่ยวกับ Service เอ่ามีคนสงสัยว่าได้ Service เนี่ยมันมีไว้ทำมะเขืออะไรในเมื่อเราสามารถต่อตรงไปที่ DAO ได้เลย
คำตอบคือจะมีหรือไม่มีแล้วแต่ชอบ สำหรับผมมีไว้ก็ดีนะครับ
*อันดับแรก Controller เราจะไม่มีอะไรเยอะเกินไปลำพังงานใน Contoller เองก็เยอะพอดูอยู่แล้วในบางเคส ยังจะต้องไป Depend On กับ DAO อีกสี่ห้าตัว
วุ่นวายไปหน่อยไหมครับ
**อันดับที่สองเรื่อง reusable เราสามารถจัดกลุ่มยกตัวอย่างเช่น เราสามารถสร้าง service ที่ทำงานเกี่ยวกับ User ที่ถูกใช้งานบ่อยๆเก็บไว้แล้วเมื่อใดที่มีคนต้องการใช้ค่อย inject เข้าไป
ให้ Controller ซึ่งจะทำให้ Service นั้นๆถูกนำไปใช้ได้บ่อยๆและหลายๆที่
***อันดับสามเรื่อง Transaction เพราะจากการเขียน code ที่ผ่านมาเราจะเห็นว่าเราไม่ได้พูดหรือเขียน code ที่เกี่ยวข้องกับ Transaction เลยแม้แต่น้อย ดังนั้นก่อนอื่นเราต้องมาทำความเข้าใจกันก่อน
เนื่องจากบ่อยครับที่ 1 operation ต้องทำการทำงานมากว่า 1 ตารางดังนั้นเราจะทำการกำหนด scope ว่า transaction ของ operation นั้นครอบคลุมการทำงานอะไรบ้างและต้องทำให้ได้ตามมาตรฐาน
ACID เสมอ
ดังนั้นบ่อยครั้งมากที่เราจะเห็นการสร้าง Service Layer ในการสร้างแอพพลิเคชั่นด้วย Spring และใช้ Proxy Bean เข้ามาทำงานร่วมกับ Transaction Manager (เรื่องรายละเอียด เช่น Proxy Bean จะกล่าวใน
รายละเอียดในภายหลัง)
และเพื่อให้เป็นไปในแนวทาง TDD เรามาเขียน Test กันสำหรับ Service ที่เราต้องการให้มีโดยจะมี method หลักๆเท่านั้น

package com.spring66.tutorial.service;

import com.spring66.tutorial.model.Users;
import com.spring66.tutorial.model.UsersExpression;
import com.spring66.tutorial.model.UsersExpression.Criteria;
import java.util.Date;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;

/**
 *
 * @author TwinP
 */
public class IbatisUserServiceTest extends AbstractDependencyInjectionSpringContextTests {

    protected final Log log = LogFactory.getLog(getClass());
    private UserService usersService;

    @Override
    protected String[] getConfigLocations() {
        setAutowireMode(AUTOWIRE_BY_NAME);
        return new String[]{
                    "classpath:/applicationContext.xml",
                    "classpath*:/applicationContext.xml" // for modular projects
                };
    }

    public void testLoadBean() {
        //UserService usersService = (UserService)super.applicationContext.getBean("usersService");
        log.debug("Looking 4 UserService First");
        assertNotNull(usersService);
    }

    public void testCreateUser() {
        Users u = new Users();
        u.setLogDate(new Date());
        u.setRegDate(new Date());
        u.setUsrFirstLogin(new Date());
        u.setUsrId("1");
        u.setUsrLevel(new Integer(1));
        u.setUsrName("username");
        u.setUsrPwd("password");
        usersService.createUser(u);
        try {
            usersService.createUser(u);
            fail("How come you still can insert duplicate entry!!!!");
        } catch (Exception error) {
            log.debug("Successfully");
        }
    }

    public void testGetByExpression() {
        UsersExpression ex = new UsersExpression();
        ex.createCriteria().andUsrNameLike("user%");
        //cr.

        List<Users> users = usersService.getUsers(ex);
        log.debug("Result->" + users.size());
        assertNotNull(users);
    }

    public void testUpdateUsers() {
        UsersExpression ex = new UsersExpression();
        ex.createCriteria().andUsrNameEqualTo("username");
        List<Users> usrs = usersService.getUsers(ex);
        Users user = usrs.get(0);
        assertEquals(1, usrs.size());
        try {
            user.setUsrName(null);
            usersService.updateUsers(user);
            fail("username can not be null");
        } catch (Exception error) {
            log.debug("OK " + error.toString());
        }
    }

    public void testRemoveUsers() {
        usersService.deleteUser("username");
        UsersExpression ex = new UsersExpression();
        ex.createCriteria().andUsrNameEqualTo("username");
        List<Users> usrs = usersService.getUsers(ex);
        assertEquals(0, usrs.size());
    }

    /**
     * @param usersService the usersService to set
     */
    public void setUsersService(UserService usersService) {
        this.usersService = usersService;
    }
}

แน่นอนอย่างยิ่งว่าแดงเถือกครับดังนั้นสิ่งแรกที่เราจะต้องทำคือ สร้าง Service Interface ก่อนครับโดยมีรายละเอียดดังนี้

package com.spring66.tutorial.service;

import com.spring66.tutorial.model.Users;
import com.spring66.tutorial.model.UsersExpression;
import java.util.List;

/**
 *
 * @author TwinP
 */
public interface UserService {
    public Users createUser(Users user);
    public List<Users> getUsers(UsersExpression userEx);
    public void updateUsers(Users user);
    public void deleteUser(String userId);
}

เอ่าหายแดงแล้วครับต่อไปเรียก test ครับและสอบตกแน่นอนเพราะไม่มี bean ก้อนนี้ใน applicationContext ดังนั้นเพิ่มเข้าไปซะใน applicationContext.xml

    <bean id="usersService" class="com.spring66.tutorial.service.ibatis.IbatisUserService">
        <property name="usersDao" ref="usersDao"/>
    </bean>

หลังจากนั้นเราจะสอบตกอีกทีเพราะไม่มี implementation ครับเราต้องเขียน implementation ก่อนครับรายละเอียดดังนี้

package com.spring66.tutorial.service.ibatis;

import com.spring66.tutorial.dao.UsersDao;
import com.spring66.tutorial.model.Users;
import com.spring66.tutorial.model.UsersExpression;
import com.spring66.tutorial.service.UserService;
import java.util.List;

/**
 *
 * @author twinp
 */
public class IbatisUserService implements UserService {
    private UsersDao usersDao;
    @Override
    public Users createUser(Users user) {
        usersDao.insert(user);
        return user;
    }

    @Override
    public List<Users> getUsers(UsersExpression userEx) {
        return usersDao.selectByExpression(userEx);
    }

    @Override
    public void updateUsers(Users user) {
        usersDao.updateByPrimaryKey(user);
    }

    @Override
    public void deleteUser(String userId) {
        usersDao.deleteByPrimaryKey(userId);
    }

    /**
     * @return the usersDao
     */
    public UsersDao getUsersDao() {
        return usersDao;
    }

    /**
     * @param usersDao the usersDao to set
     */
    public void setUsersDao(UsersDao usersDao) {
        this.usersDao = usersDao;
    }

}

จากนั้น test อีกรอบครับผ่านหล่อๆเลย ตอนนี้ขอข้ามเรื่อง Transaction เก็บไว้ตอนหน้านะครับ

Advertisement

8 comments

  1. ใน class IbatisUserServiceTest ไม่มีการเรียก getBean แล้ว userService bean ได้มาได้ยังไงเหรอครับ

  2. กำลังอยากเรียนเรื่อง spring พอดีครับ
    เอ… แล้วส่วนอื่นเวลาเรียก service นี่ก็ไปใช้ IoC เรียกมาให้ใช่ป่ะครับ
    (เดี๋ยวจะมีเขียนให้ดูเปล่าครับ) ขอบคุณครับ

  3. roofimon says:

    @Weerssak
    ใช้
    protected String[] getConfigLocations() {
    setAutowireMode(AUTOWIRE_BY_NAME);

    }
    ครับดังนั้นจังหวะที่เราจะใช้งาน Spring จะทำการเข้าไปหาใน Application Context ว่ามี bean ที่ชื่อ userService ไหมครับถ้ามีก็จะ Inject มาให้เลยครับ จะเห็นได้ว่า Test ของเราก็ไม่ได้ Config ว่า Depend On ตัว userService ครับ

  4. roofimon says:

    @jittat
    เดี๋ยวจะมีส่วนของ Controller ครับที่จะใช้ sevice ซึ่งเราก็จะมองว่า controller เป็น bean อีกตัวครับสิ่งที่เราต้องทำคือการ บอกว่าเราจะใช้ service ตัวไหนบ้างครับ

  5. dekpump says:

    @roofimon ละเอียดมากครับพี่

  6. jittat says:

    ขอบคุณครับ

  7. brainstorm says:

    กว่าจะรู้เรื่อง เล่นเอางงไปหลายวันเลยนะเนี่ย (ความรู้ยังน้อยนิดนัก) แต่สุดยอดมากครับพี่ roof, บทความนี้ช่วยได้เยอะเลย

  8. soda.spring says:

    เจ๋งเลยครับ หลักจากไปเทรนมา เข้ามาอ่านแล้วเข้าใจ concept ครับ ก่อนหน้านี้ อ่านแบบงมๆครับ

    ขอบคุณสำหรับความรู้ครับ

Leave a Reply

You must be logged in to post a comment.