[봄싹] 새 기능 소개

1. 트위터 서비스!

http://twitter.com/springsprout

봄싹 트위터를 통해서 스터디와 모임 개설/변경 소식을 실시간으로 전해드립니다. 트위터 RSS를 구족하시면 RSS 리더기를 통해서도 스터디와 모임 정보를 쉽게 받아 보실 수 있겠죠.

2. 구글 토크 알리미!

s2cmailer@gmail.com

이 주소를 구글토크에 친구로 추가해두고, 구글 토크에서 사용하는 이메일로 봄싹에 가입했다면,
스터디와 모임 메시지를 바로 바로 받을 수 있습니다.

그것 뿐 아니라, 대화형 기능을 제공하여 study? 라고 입력하면 현재 운영중인 스터디 목록을 보여주고, meeting? 이라고 입력하면 현재 개설되어 있는 모임 목록을 보여줍니다.


3. 통계 기능 추가!

스터디에 얼마나 열성적으로 참석하는지..
스터디 별로 참석률은 어떤지..
모임 참가 신청을 하고나서 자주 불참하는 회원은 아닌지..
스터디 별로 신청을 해놓고 얼마나 약속을 잘 지키는지..

한눈에 알 수 있는 기능을 추가했습니다. (테스트 데이터가 엉망이라 통계가 좀 이상합니다.)


나중에는 참석률과 신뢰도를 게이지 형태로 디자인해서 마치.. 게임 캐릭터의 체력과 마력을 나타내듯이 표시할 생각입니다. ㅋㅋ

셋 다 제가 코딩했고 만들면서 많이 고민하고 재미를 느꼈던 기능들입니다.

– 트위터 알리미를 만들 때는 인터페이스 사용의 혜택을 만끽해 보았으며,

– 구글 토크 서비스를 만들 때는 smack API를 이용하는 JabberService를 만들면서 스프링 DI를 어떻게 하면 잘 활용할 수 있을까 고민할 수 있는 계기가 되었습니다.

– 마지막으로 통계 기능을 만들 때는 별로 안 해봤엇던 하이버네이트에 Map<Entity, Primitive Type> 형태의 맵핑도 해보고, 어떤 콜렉션 타입이 좋을지 고민도 해보고, EL로 map안에 있는 데이터 꺼내오기도 해봤네요. 하지만 뭐니뭐니해도 테스트를 통해서 얻을 수 있는 안정감.. 그걸 느껴볼 수 있었습니다.

현재 봄싹은 제가 만든 새로운 기능 말고도 다른 분들이 만들고 있는 새 기능도 많이 있습니다. 본격적인 운영은 추석이 지나고나서 될 것 같네요. 그전까지 지금 상태 그대로 갑니다.

모두 명절 잘 보내세요~

[테스트 데이터] 테스트에 필요한 데이터 만들기

    @Test
    public void calcTotalAttendanceRate() throws Exception {
        Member member = new Member();
        Study study1 = new Study();
        Meeting meeting1 = new Meeting();
        Meeting meeting2 = new Meeting();
        Meeting meeting3 = new Meeting();
        Study study2 = new Study();

        study1.addMeeting(meeting1);
        study1.addMeeting(meeting2);
        study2.addMeeting(meeting3);
        member.addJoinedStudy(study1);
        member.addJoinedStudy(study2);
        member.applyAttendance(meeting1);
        member.applyAttendance(meeting2);

        int attendanceSize = member.getAttendances().size();
        assertThat(attendanceSize, is(2));
        assertThat(member.getStudies().size(), is(2));
        assertThat(study1.getMeetingCount(), is(2));
        assertThat(study2.getMeetingCount(), is(1));

        when(mockStudyRepository.getConfirmedAttendanceCountOf(member)).thenReturn(1);
        when(mockStudyRepository.getTotalAttandanceCountOf(member)).thenReturn(attendanceSize);
        memberService.calcRatesOf(member);
       
        assertThat(member.getTotalAttendanceRate(), is(33));
        assertThat(member.getTotalTrustRate(), is(50));
    }

이 테스트에서 절반 이상이 테스트 데이터를 만들고 그 부분을 검증하는 코드입니다. 이 부분을 다음과 같이 바꿀 수 있다면.. 좋을까요? 안 좋을까요?

        Meeting meeting1 = Builder.Create(Meeting.class).Build();
        Meeting meeting2 = Builder.Create(Meeting.class).Build();
        Meeting meeting3 = Builder.Create(Meeting.class).Build();
       
        Builder.Create(Member.class)
            .addStudy(Builder.Create(Study.class)
                    .addMeeting(meeting1)
                    .addMeeting(meeting2)
                    .Build())
            .addStudy(Builder.Create(Study.class)
                    .addMeeting(meeting3)
                    .Build())
            .addAttendance(Builder.Create(Attendance.class)
                    .addMeeting(meeting1)
                    .Build())
            .addAttendance(Builder.Create(Attendance.class)
                    .addMeeting(meeting2)
                    .Build())
            .Build();

Object Mother에 대한 글을 찾아 보다가 Test Data Builder에 대한 글과 거기에 달린 댓글을 통해 닷넷에서 사용하는 NBuilder라는 것까지 대충 살펴봤는데.. 이거 뭐.. 해보지 않고서는 어떨지 잘 상상이 안 되네요.

http://martinfowler.com/bliki/ObjectMother.html
http://c2.com/cgi/wiki?ObjectMother
http://geekswithblogs.net/Podwysocki/archive/2008/01/08/118362.aspx
http://www.nbuilder.org/

그래서 일단은 위와 같이 상상 코딩을 해보았는데.. 어떨런지요.. 흠..

[테스트 코드 리팩토링] extract method

    @Test
    public void getAttendanceCountOf() throws Exception {
        insertXmlData(“testData.xml”);
        Member member = new Member();
        Study study = new Study();
        member.setId(1);
        study.setId(1);
        assertThat(sr.getAttendanceCountOf(member, study), is(2));
        member.setId(1);
        study.setId(2);
        assertThat(sr.getAttendanceCountOf(member, study), is(1));
        member.setId(2);
        study.setId(2);
        assertThat(sr.getAttendanceCountOf(member, study), is(1));
    }

다음과 같이 코드를 리팩토링할 수 있습니다.

    @Test
    public void getAttendanceCountOf() throws Exception {
        insertXmlData(“testData.xml”);
        checkAttendanceCountOf(1, 1, 2);
        checkAttendanceCountOf(1, 2, 1);
        checkAttendanceCountOf(2, 2, 1);
    }
   
    private void checkAttendanceCountOf(int memberId, int studyId, int count){
        Member member = new Member();
        Study study = new Study();
        member.setId(memberId);
        study.setId(studyId);
        assertThat(sr.getAttendanceCountOf(member, study), is(count));
    }

이번 경우에는 코드 라인수 차이가 얼마 나지 않지만, 대부분의 경우 훨씬 깔끔해집니다.

[스프링 3.0] 애노테이션 기반 스케줄링

참조: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ch25s05.html

웹 애플리케이션을 띄울 때 구글 토크 봇을 로그인 시켜두려고 스케줄링을 이용하려 했습니다. 찾아보니까 애노테이션 기반으로 설정할 수 있는 기능이 추가됐더군요.

<task:annotation-driven executor=”myExecutor” scheduler=”myScheduler”/>

<task:executor id=”myExecutor” pool-size=”5″/>

<task:scheduler id=”myScheduler” pool-size=”10″/>}

이렇게 task:annotation-driven 엘리먼트를 XML에 추가해주면 빈에 설정되어 있는 @Schedule과 @Async 애노테이션을 활성화 시켜줍니다.

@Schedule 애노테이션은 cron, fixedDelay, fixedRate 세 가지 속성 중 하나를 이용해서 설정해야 합니다. 반드시 이 셋 중에 하나는 설정되어 있어야 합니다.

@Async 애노테이션은 해당 메서드 호출을 비동기로 처리해주고 싶을 때 사용할 수 있습니다. 즉 이 애노테이션으로 스케줄링이 적용된 메서드를 호출하면 결과는 바로 리턴되고 실제 실행은 스프링의 TaskExecutor에 의해 별도의 Task 내부(이 녀석이 별도의 쓰레드겠죠)에서 실행됩니다.

막상 해보니 라이브러리 때문에 에러가 나더군요.

        <dependency>
            <groupId>edu.emory.mathcs.backport</groupId>
            <artifactId>com.springsource.edu.emory.mathcs.backport</artifactId>
            <version>3.1.0</version>
        </dependency>

그래서 필요한 라이브러리를 추가해주고 돌려보니까 잘 돌아갑니다.

그런데 해보고 나니까 굳이 반복 실행할 필요가 없는 메서드라;;; -_-;; @PostConstruct 애노테이션 붙여서 끝냈습니다.

이 간단한것을… 스캐쥴링은 머하러 찾아봤담;;

[JSP 리팩토링] 태그 파일로 중복 코드 제거하기

<%@ page language=”java” contentType=”text/html; charset=UTF-8″ pageEncoding=”UTF-8″%>
<%@ taglib prefix=”page” tagdir=”/WEB-INF/tags/page”%>
<%@ taglib prefix=”form” uri=”http://www.springframework.org/tags/form”%>
<%@ taglib prefix=”c” uri=”http://java.sun.com/jsp/jstl/core”%>
<%@ taglib prefix=”s” tagdir=”/WEB-INF/tags/study”%>

<page:studypage>
<s:defaultpage>
    <h1>스터디 추가</h1>
    <form:form commandName=”study” method=”post”>
    <p>
        <label>스터디명</label>
        <form:input path=”studyName” cssClass=”text” />
        <form:errors path=”studyName” />
    </p>
    <p>
        <label>최대인원수</label>
        <form:input path=”maximum” cssClass=”text” />
        <form:errors path=”maximum” />
    </p>
    <p>
        <label>시작일</label>
        <form:input path=”startDay” cssClass=”text”/>
        <form:errors path=”startDay” />
    </p>
    <p>
        <label>종료일</label>
        <form:input path=”endDay” cssClass=”text”/>
        <form:errors path=”endDay” />
    </p>
    <p>
        <label>설명</label>
        <form:textarea path=”descr” rows=”4″ cols=”60″ cssClass=”text”/>
        <form:errors path=”descr” />
    </p>
    <br/><hr/><br/>
    <a href=”<c:url value=”/study/list.do”/>”>취소</a>
    <input type=”submit” value=”저장” class=”s_waitblock” />
    </form:form>
</s:defaultpage>
<script type=”text/javascript”>
  $(document).ready(function(){
    $(“#startDay”).datepicker({ dateFormat: ‘yy/mm/dd’ });
    $(“#endDay”).datepicker({ dateFormat: ‘yy/mm/dd’ });
  });
</script>
</page:studypage>

이미 태그 파일로 <html> </html>과 js, css 임포트 하는 부분을 제거 해 두었습니다. 태그 파일을 여러 추상화 계층으로 세분화 해서 로우 레벨 태그파일과 하이 레벨 태그파일로 나눌 수도 있겠습니다. 저 위에 보이는 page 태그는 하이 레벨 태그 파일로 볼 수 있고, s 태그는 로우 레벨로 볼 수 있습니다. 하이 레벨이라고 해서 뭔가 더 여러운 태그라는게 아니라, 로우 레벨 태그를 조합하여 한 단계 더 추상화시킨 태그파일 입니다. 이런 구분이 원래 있는 것이 아니라 제가 생각하는 걸 정리한 것 뿐이오니,,, 괜히 “하이 레벨 태그 파일” 이런식으로 구글링을 하는 사태가 없기를 바랍니다.

사설을 좀 길었네요, 일단락하기로 하고, 위 코드를 태그파일로 리팩토링하면 다음과 같이 됩니다.

<page:studypage>
<s:defaultpage>
    <h1>스터디 추가</h1>
    <form:form commandName=”study” method=”post”>
    <s:ftext title=”스터디명” path=”studyName” />
    <s:ftext title=”최대인원수” path=”maximum” />
    <s:fdate title=”시작일” path=”startDay” />
    <s:fdate title=”종료일” path=”endDay” />
    <s:ftextarea title=”설명” path=”descr” rows=”4″ cols=”60″ />
    <hr/>
    <s:back-button url=”/study/list.do” />
    <input type=”submit” value=”저장” class=”s_waitblock” />
    </form:form>
</s:defaultpage>
<script type=”text/javascript”>
  $(document).ready(function(){
    $(“.fdate”).datepicker({ dateFormat: ‘yy/mm/dd’ });
  });
</script>
</page:studypage>

이렇게 했을 때 좋은 점은 소스 코드에서 중복을 제거 했을 때 얻을 수 있는 장점과 같습니다.

그러나,,, 단점도 있는데 태그 파일에 정의해준 속성만 받아서 사용하기 때문에 그만큼 사용할 수 있는 기능이 제한 될달까.. 그런게 좀 있습니다. 해결책은 있습니다. 태그 파일에 거의 모든 속성을 다 정의해 놓고 정말 필요한 것만 required로 하고 사용해도 될테지만.. 태그 파일을 만드는 비용이 꽤 많이 들겠지요. 결국 선택의 기로에 서게 되는데, 저는 귀찮아서;; 그냥 최소한의 속성만 정의해서 쓰는 편입니다.