Whiteship's Note


[Agile 번역] 어떻게 하면 번역을 기민하게 할 수 있을까?

모하니?/Thinking : 2010.03.12 11:09


번역은 쉽지 않다. 하지만 나름 재미는 있다. 돈은 되지 않지만, 하고 나면 뿌듯하다. 책으로 치면 지금하고 있는 책까지 두 권째다. 난 번역을 잘하지 못한다. 한글 실력이 엉망이고 책읽기를 별로 좋아하지 않아서 인지 문학 도서에 비하면 비교적 쉬운 영어를 사용하는 서적들 임에도 불구하고 한글 문장으로 옮기는 일이 쉽지 않다. 그래도 최소한 내가 다시 읽었을 때 이해할 수 있을 정도의 수준 만큼은 달성하고자 노력한다.

그렇게 어렵사리 번역을 하고나면 사실 다른 사람의 시각에서 읽었을 때 어떠한지 궁금하다. 그래서 베타리딩을 한다. 베타리딩을 하면서 내가 미처 감지하지 못했던 어색한 부분과 이해 못할 만한 부분이 드러난다. 그 부분을 개선하는 작업은 중요하다. 내가 읽었을 때 이해할 수 있었던 건 난 원문의 내용을 읽었기 때문이다. 그래서 원문을 보지 않고도 이해할 수 있는지 알려면 반드시 베타리딩이 필요하다. 코딩으로 치면 일종의 테스트다. 그런데 베타리딩도 쉽지 않다. 하이버네이트 같은 경우 한 챕터당 워드로 100페이지 정도가 된다. 넘는 것도 있고 덜 되는 것도 있지만 대충 그렇다. 그걸 바쁜 개발자들에게 읽어달라고 부탁하기가 참 뭐하다. 그 분들이 받는 댓가라고는 책을 미리 읽어볼 수 있다는 것 정도?

그래서 이런 저런 이유로 고민 중에 번역에도 Agile 기법을 도입하면 어떨까 하는 생각이 들었다. 그래서 상상을 해봤다.

페어 번역 

두 명이 앉아서 번역을 한다. 한명은 부르고 한명은 타이핑 하거나 그 반대로 한명이 읊으면서 동시에 타이핑도 하고 옆 사람은 보고 있다가 이상한 문장이나 틀린걸 봐주면 될 것 같다. 하지만 이건 번역으로 먹고 사는 사람이 아닌 이상 힘들어 보인다. 그렇게 30분 정도 번역을 한 다음 교대한다.

이런식으로 하루에 2시간을 번역한다고 쳤을 때 두 사람은 각각 1시간 번역 1시간 리뷰를 하게 된다. 한 사람이 2시간을 번역할 때와 2사람이 각각 1시간 번역/1시간 리뷰를 했을 때의 생산량과 품질을 확인해보고 싶다.

리뷰 퍼스트 번역

타이핑을 하기 전에 먼저 말로 리뷰를 한다. 아 이 문장은 대충 요러 요러한 이야기 같네요. 이렇게 옮기겠습니다. 라고 말을 한 뒤에 타이핑을 한다. 그리고 타이핑이 끝나면 옆 사람과 리팩토링을 진행한다. 어설프거나 한글 어법에 어긋나면 교정한다. 나중에 QA 팀 겪인 출판사의 검수자가 봐주겠지만 최소한의 품질을 보장하기 위한 수단이라고 보면 되겠다. 

페어 번역을 진행할 때 타이핑만 하지 않고 리뷰를 함으로써 대화를 유도할 수 있다. 하지만 대화가 목적은 아니기에 잡담으로 새는 일이 없게 주의해야겠다. 리뷰를 최소화하고 바로 타이핑을 한다. 그 다음 리팩토링에 해당하는 작업도 반드시 단락 단위나 문자 단위로 하는게 좋겠다. 이런 과정이 없다면 페어 번역을 하는 의미가 없으니 이렇게 하지 않을꺼면 페어 번역을 하지 않는게 좋겠다.

점진적인 베타리딩

베타리더에게 전달하는 과정을 일종의 배포로 간주하고 Agile 기법인 여러번 그리고 점진적으로 배포하는 수단을 번역에도 도입하면 어떨까. 예를 들어 베타리딩 주기를 1주일로 잡고 1주일치 번역한 내용을 베타리더에게 공개한다. 그리고 피드백을 받은 다음 그 주 초에 지난 배포의 개선 작업을 한 다음에 주 중~말까지 새로운 번역 작업을 진행하는 식이다.

베타리더에게 한번에 100 페이지씩 읽어야 하는 부담을 줄일 수 있고 피드백을 초기에 받음으로써 나중에 뭉탱이로 중복적인 피드백을 받는 것을 개선할 수 있을 것 같다. 하지만 베타리더도 번역자 못지않게 기민해야 할 것이다.

다음 번역은 봄싹에서 진행할 계획인데 이 세가지를 시도해 봐야겠다. 
과연.. 어떨까.. 후훗.. 재밌을 것 같다.
저작자 표시
신고
top

TAG agile, 번역

[봄싹] 스프링 레퍼런스 3.0 번역 시작



http://springsprout.org/wiki/464.do

3.0 레퍼런스 번역을 꾸준히 하겠습니다. 그동안 블로그에 조금씩 번역해서 올려두기도 했었는데 아무래도 레퍼런스 글은 블로그에서 찾아보는게 불편해서 봄싹 위키에 정리하기로 했습니다.

일단은 저 혼자 시작합니다. 하지만 봄싹 회원이라면 누구나 위키 페이지를 추가/수정/이동 시킬 수 있기 때문에 자유롭게 참여하실 수 있습니다.

정해진 틀도 없고 파트를 나누지도 않았지만, 봄싹 사이트도 처음에는 이런 방법으로 개발을 시작했습니다. 지금은 제법 틀도 갖춰져 있고, 특정 모듈 또는 기능 담당자(? 라기 보단 스스로 책임을 느끼시는 분들)도 있습니다. 수직 구조로 누가 누구에게 지시하거나 일을 나눠주지 않고 수평구조로 서로 토론하며 자신이 만들고 싶은 기능을 마음대로 구현해 넣고 있습니다. 레퍼런스도 이런 방법으로 번역을 완성할 겁니다.

기여(?).. 흠. 참여하고 싶으신 분들은 언제나 대환영입니다.

파이팅!

ps: 위키 수정/추가시 포인트를 계산해서 위키 기여도를 측정해야겠군요. 가장 많이 기여한 분에게 봄싹 티셔츠라도...

ps: 위키 미리 보기 화면과 실제 화면이 좀 다른데;; 아마도 조만간 소내기형이 수정해주지 않을까 싶네요... 형 수정해주세요.. ㅠ.ㅠ


신고
top


SWF 12장 JSF 통합



12.1. 도입

스프링 Faces는 스프링의 JSF 통합 모듈로 스프링에서 JSF 사용을 간편하게 해준다. JSF UI 컴포넌트 모델을 스프링 MVC와 스프링 웹 플로우 컨틀로러와 함께 사용할 수 있게 해준다.

스프링 Faces는 또한 Ajax와 클리이언트쪽 검증 기능을 제공하는 자그마한 Facelets도 제공한다. 이 컴포넌트 라이브러리는 스프링 자바스크립트를 기반으로 만들었다. 스프링 자바스크립트는 Dojo를 기반 UI 툴킷으로 통합한 자바스크립트 추상화 프레임워크드다.

12.2. 스프링-중심 통합 방법

스프링 Faces는 JSF의 UI 컴포넌트 모델 장정을 스프링의 컨트롤러와 설정 모델 장점과 결합해준다. 아무런 약점없이 JSF의 모든 장점을 활용할 수 있다.

스프링 Faces는 표준 JSF 기능에 다음과 같은 강력한 보완재를 제공한다.

  1. managed bean facility
  2. scope management
  3. event handling
  4. navigation rules
  5. easy modularization and packaging of views
  6. cleaner URLs
  7. model-level validation
  8. client-side validation and UI enhancement
  9. Ajax partial page updates and full navigation
  10. progressive enhancement and graceful degradation

이 기능을 사용하면 faces-config.xml에 필요한 설정 분량을 현격하게 줄여줄 것이며 뷰와 컨트롤러 계층을 보다 깔끔하게 분리해주며 애플리케이션의 기능 책임 모듈화를 보다 잘 지원한다. 이 기능들의 사용법은 다음 절에서 살펴보겠다. 이 기능들 대부분은 스프링 웹 플로우의 플로우 정의 언어를 기반으로 한다. 여러분이 플로우 정의하기에 나와있는 기본을 이해하고 있다고 가정한다.

12.3. web.xml 설정하기

스프링 Faces를 사용하는 첫 번째 단계는 요청을 web.xml 파일에 있는 DispatcherServlet으로 라우팅하는 것이다. 이번 예제에서, 우리는 /spring/으로 시작하는 모든 URL을 서블릿으로 맵핑한다. 서블릿을 설정해야 한다. init-param을 사용하여 서블릿에 contextConfigLocation을 넘겨준다. 이 것은 애플리케이션의 스프링 설정 파일 위치다.

<servlet>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/web-application-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
   
<servlet-mapping>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <url-pattern>/spring/*</url-pattern>
</servlet-mapping>
       
JSF를 적절하게 동작시키려면, FacesServlet을 web.xml에 설정해야 한다. 스프링 Faces를 사용할 때는 그것을 사용하여 요청을 라우팅할 필요가 없다.

<!-- Just here so the JSF implementation can initialize, *not* used at runtime -->
<servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
   
<!-- Just here so the JSF implementation can initialize -->
<servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.faces</url-pattern>
</servlet-mapping>
       
스프링 Faces  컴포넌트를 사용할 때에도 스프링 자바스크립트 ResourceServlet을 설정하여 컴포넌트에서 CSS와 자바스크립트 리소스를 제대로 출력할 수 있게 할 필요가 있다. 이 서블릿은 반드시 /resources/*로 맵핑해야 컴포넌트에서 랜더링한 URL에 대해 제대로 동작한다.

<!-- Serves static resource content from .jar files such as spring-faces.jar -->
<servlet>
    <servlet-name>Resource Servlet</servlet-name>
    <servlet-class>org.springframework.js.resource.ResourceServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
</servlet>
       
<!-- Map all /resources requests to the Resource Servlet for handling -->
<servlet-mapping>
    <servlet-name>Resource Servlet</servlet-name>
    <url-pattern>/resources/*</url-pattern>
</servlet-mapping>
       
스프링 Faces 컴포넌트는 JSP 대신에 Facelet을 사용해야 한다. 따라서 이들 컴포넌트를 사용하려면 일반적인 Facelet 설정을 반드시 추가해야 한다.

!-- Use JSF view templates saved as *.xhtml, for use with Facelets -->
<context-param>
    <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
    <param-value>.xhtml</param-value>
</context-param>
       
12.4. JSF 뷰 랜더링 하도록 웹 플로우 설정하기

The next step is to configure Web Flow to render JSF views. To do this, in your Spring Web Flow configuration include the faces namespace and link in the faces flow-builder-services :

다음 단계는 JSF 뷰를 랜더링 하도록 웹 플로우를 설정하는 것이다. 그렇게 하려면 스프링 웹 플로우 설정에 faces 네임스페이스를 추가하고

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

    <!-- Executes flows: the central entry point into the Spring Web Flow system -->
    <webflow:flow-executor id="flowExecutor" />

    <!-- The registry of executable flow definitions -->
    <webflow:flow-registry id="flowRegistry" flow-builder-services="facesFlowBuilderServices" base-path="/WEB-INF">
        <webflow:flow-location-pattern value="**/*-flow.xml" />
    </webflow:flow-registry>

    <!-- Configures the Spring Web Flow JSF integration -->
    <faces:flow-builder-services id="facesFlowBuilderServices" />

</beans>
       
faces:flow-builder-services 태그는 JSF 환경에 적절한 다른 기본 값들도 설정한다. 특히 Unified EL을 기본 EL로 설정한다.

완전히 동작하는 예제는 배포판에서 swf-booking-faces 레퍼런스 애플리케이션을 참고하라.

12.5. faces-config.xml 설정하기

faces-config.xml에 유일하게 설정할 필요가 있는 것은 Facelet 사용에 대한 것이다. 만약 스프링 Faces 컴포넌트를 사용하지 않고 JSP를 사용하고 있다면, 어떠한 스프링 Faces 관련 설정도 faces-config.xml에 추가하지 않아도 된다.

<faces-config>
    <application>
        <!-- Enables Facelets -->
        <view-handler>com.sun.facelets.FaceletViewHandler</view-handler> 
    </application>
</faces-config>
       
12.6. JSF가 관리하는 빈 기능 교체하기

스프링 Faces는 JSF가 관리하는 빈 기능을 플로우가 관리하는 변수와 스프링이 관리하는 빈으로 완전히 교체할 수 있게 해준다. 그렇게 하여 여러분이 관리하는 객체를 잘 정의되어 있는 초기화 후크와 도메인 객체 실행 후크로 생명 주기를 보다 잘 관리할 수 있다. 게다가, 여러분이 이미 비즈니스 계층에서 스프링을 사용해봤다는 가정한다면, 두 개의 다른 빈 관리 모델을 유지하는 것에 대한 개념적인 오버헤드를 줄일 수 있다.

순수 JSF 개발을 할 떄는 request 스코프로는 복잡한 이벤트-기반 뷰를 다루는 대화형 모델 객체를 저장하기에는 적당하지 않다는 것을 금방 알 수 있을 것이다. 오직 사용할 수 있는 옵션은 session 스코프에 모든 것들을 넣는 것이다. 애플리케이션의 다른 뷰 또는 기능적인 부분을 처리할 때 객체들을 청소해줘야 한다는 추가 작업이 생긴다. 정말로 필요한 것은 request와 session 스코프 중간 쯤 되는 어떤 스코프가 필요하다. 우누 좋게도 웹 플로우는 그러한 확장 기능을 제공한다.

12.6.1. 플로우 변수 사용하기

가장 간단하고 자연스럽게 모델을 선언하고 관리하는 것은 플로우 변수를 사용하는 것이다. 이 변수들을 플로우 시작시에 선언할 수 있다.

<var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/>
           
그런 다음 이 변수를 플로우 중 어떤 JSF 뷰 템플릿에 EL을 통해서 참조한다.

<h:inputText id="searchString" value="#{searchCriteria.searchString}"/>
           
(좀 더 구체적일 필요가 있다면 그렇게 할 수도 있지만)템플릿에서 변수를 참조할 때 그 스코프로 접두어를 붙이지 않아도 된다는 것에 주목하라. 표준 JSF 빈으로 대응하는 변수에 대해 모든 사용 가능한 스코프에서 찾아볼 것이다. 따라서 EL 표현식을 수정하지 않고도 플로우 정의에서 그것을 참조하는 변수의 스코프를 변경할 수 있다.

또한 뷰 인스턴스 변수를 현재 뷰로 범위를 제한하고 다른 뷰로 이동하면 자동으로 비워버리게 정의할 수 있다. 이렇게 하는 것은 보통 페이지 내에서 다른 뷰로 전이하기 전에 여러 여러 요청에 걸쳐 이벤트를 처리하는 JSF 뷰에서 매우 유용하다.

뷰 인스턴스 변수를 정의할때 var 엘리먼트를 view-state 정의 내부에서 사용할 수 있다.

<view-state id="enterSearchCriteria">
    <var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/>
</view-state>
           

12.6.2. 스코프를 가진 스프링 빈 사용하기

비록 자동 연결 플로우 인스터스 변수가 괜찮은 모듈화와 가독성을 제공하지만 가끔 여러분은 AOP 같은 스프링 컨테이너의 기능을 활용하고 싶을 수 있다. 그런 경우 여러분은 스프링 애플리케이션컨텍스트 내부에 있는 빈에 특정 웹 플로우 스코프를 줄 수 있다.

<bean id="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria" scope="flow"/>
           
이런 접근 방법의 가장 큰 차이점은 EL 표현식에 의해 접근될 때 비을 초기화 한다는 것이다. EL을 통한 이런 종류의 생성 지연은 JSF가 관리하는 빈이 할당되는 것과 매우 비슷하다.

12.6.3. 모델 조작하기

뷰 랜터링 하기 전에 (데이터베이스에서 영속 엔티티를 로딩하는 것 같은)모델 초기화가 필요한 것은 매우 흔한 일이지만 JSF 자체로는 이러한 초기화에 사용할 편의성 후크(hook)를 제공하지 않는다. 플로우 정의 언어는 액션(Action)을 통해서 이와 관련된 자연스러운 기능을 제공한다. 스프링 Faces는 액션의 결과를 JSF-관련 데이터 구조로 변경해주는 추가적인 편의성을 제공한다. 예제를 보자.

<on-render>
    <evaluate expression="bookingService.findBookings(currentUser.name)"
              result="viewScope.bookings" result-type="dataModel" />
</on-render>
         
이렇게 하면 bookingService.findBookings 매서드 결과를 취하고 그것을 표준 JSF DataTable 컴포넌트에서 해당 목록을 사용할 수 있도록 커스텀 JSF DataModel로 감싼다.

<h:dataTable id="bookings" styleClass="summary" value="#{bookings}" var="booking"
             rendered="#{bookings.rowCount > 0}">
    <h:column>
        <f:facet name="header">Name</f:facet>
        #{booking.hotel.name}
    </h:column>                  
    <h:column>
    <f:facet name="header">Confirmation number</f:facet>
        #{booking.id}
        </h:column>
    <h:column>
        <f:facet name="header">Action</f:facet>
        <h:commandLink id="cancel" value="Cancel" action="cancelBooking" />
    </h:column>
</h:dataTable>
           
커스텀 DataModel은 request 스코프 외의 저장을 위한 직렬화와 EL에서 현재 선택한 줄에 대한 접근 등 몇몇 추가적인 편의성을 제공한다. 예를 들어, DataTable 내의 컴포넌트에 의해 액션 이벤트가 발생한 뷰에서 포스트백 할 때, 여러분은 선택한 줄의 모델 인스턴스를 가질 수 있다.

<transition on="cancelBooking">
    <evaluate expression="bookingService.cancelBooking(bookings.selectedRow)" />           
</transition>
           
12.7. 스프링 웹 플로우로 JSF 이벤트 처리하기

스프링 웹 플로우는 낮은 결합도를 유지하면서 JSF 액션 이벤트를 처리할 수 있게 해준다. 자바 코드에서 JSF API에 의존하지 않아도 된다. 이벤트를 커스텀 자바 액션 코드를 전혀 사용하지 않고 플로우 정의 언어를 사용하여 완전하게 처리할 수 있다. 이렇게 하면 (JSF 뷰 템플릿과 SWF 플로우 정의) 이벤트를 연결할 때 만들어지는 구성물들을 전체 애플리케이션을 빌드하고 다시 배포할 필요 없이 즉시 리프래시 되기 때문에 보다 기민한 개발 프로세스가 가능해진다.

12.7.1. JSF In-page 액션 이벤트 처리하기

간단하지만 JSF에서 가장 흔한 경우가 모델을 조작하는 이벤트를 발생시키고 동일한 뷰로 모델의 변경된 상태를 보여주는 것이다. 플로우 정의 언어는 transition 엘리먼트에서 이것을 지원한다.

이 것에 대한 좋은 예제는 페이징 처리를 하는 목록 표다. 거대한 결과 목록 중의 일부만 읽어오고 보여주고 싶다고 가정해보자. 그리고 사용자는 그 결과를 페이징할 수 있다. 목록을 읽어오고 보여주는 초기 view-state 정의는 다음과 같다.
 
<view-state id="reviewHotels">
    <on-render>
        <evaluate expression="bookingService.findHotels(searchCriteria)"
                  result="viewScope.hotels" result-type="dataModel" />
    </on-render>
</view-state>
           

JSF DataTable로 현재 호텔 목록을 보여주도록 할 수 있다. 그런 다음 표 하단에 "More Results" 링크를 제공한다.
 
<h:commandLink id="nextPageLink" value="More Results" action="next"/>
           
이 커맨드링크는 action 속성에서 "next" 이벤트를 보낸다. 그럼 여러분은 이벤트를 view-state 정의에 추가하려 처리할 수 있다.
 
<view-state id="reviewHotels">
    <on-render>
        <evaluate expression="bookingService.findHotels(searchCriteria)"
            result="viewScope.hotels" result-type="dataModel" />
    </on-render>
    <transition on="next">
        <evaluate expression="searchCriteria.nextPage()" />
    </transition>
</view-state>
           
여기서 searchCriteria 인스턴스에서 page 카운트를 증가하여 "next" 이벤트를 처리한다. 그럼 다음 on-render 액션을 변경된 criteria로 호출한다. 그러면 다음 페이지 결과를 DataModel로 로딩해준다. transition  엘리먼트에 to 속성이 없기 때문에 동일한 뷰를 다시 보여준다. 그리고 모델에 변경된 사항을 뷰에 반영해준다.

12.7.2. JSF 액션 이벤트 처리하기

in-page 이벤트 다음 단계는 조작한 모델을 가지고 다른 뷰로 이동하는 이벤트다. 순수 JSF로 이것을 하려면 faces-config.xml에 네비게이션 로직을 추가하고 자바 코드를 JSF가 관리하는 빈에 추가해야 한다.(두 작업 모두 다시 배포해야 한다.) 플로우 정의 언어를 사용하면, in-page 이벤트를 다루던 방식과 매우 비슷하게 한 곳에서 그런 것을 다룰 수 있다.

계속해서 페이징 처리하는 목록을 살펴보자. 보여지는 DataTable의 각각의 row에 row 인스턴스에 대한 자세한 내용 페이지 링크를 가지고 있도록 하고자 한다. 여러분은 테이블에 다음의 commandLink 컴포넌트를 가지고 있는 컬럼을 추가할 수 있다.

<h:commandLink id="viewHotelLink" value="View Hotel" action="select"/>
           
이것은 "select" 이벤트를 발생시킨다. 그다음 기존의 view-state에 또 다른 transition 엘리먼트를 추가하여 이것을 처리할 수 있다.
 
<view-state id="reviewHotels">
    <on-render>
        <evaluate expression="bookingService.findHotels(searchCriteria)"
            result="viewScope.hotels" result-type="dataModel" />
    </on-render>
    <transition on="next">
        <evaluate expression="searchCriteria.nextPage()" />
    </transition>
    <transition on="select" to="reviewHotel">
            <set name="flowScope.hotel" value="hotels.selectedRow" />
    </transition>
</view-state>
           
여기서 "select" 이벤트는 DataTable에서 현재 선택한 hotel 인스턴스를 플로우 스코프에 넣어서 처리하고 있다. 그렇게 하면 "reviewHotel" view-state에서 참조할 것이다.

12.7.3. 모델 검증 수행하기

JSF는 변경 사항을 모델에 반영하기 전에 필드-수준 입력 검증 관련 유용한 기능을 제공한다. 하지만 변경 사항을 적용한 뒤에 모델-수준의 좀 더 복잡한 검증을 수행할 필요가 있다면 여러분은 관리하는 빈의 JSF 액션 매서드에 커스텀 코드를 추가해야 한다. 이런 종류의 검증은 모메인 모델 자체 책임이지만 도메인 모델 계층에 원하지 않던 JSF API 의존성을 추사하지 않고서는 에러 메시지를 뷰에 전달하기가 어렵다.

스프링 Faces를 사용하면 일반적이고 낮은-수준의 MessageContext를 여러분의 비즈니스 코드에서 유용하게 사용할 수 있고 그곳에 추가한 모든 메시지는 랜더링 시에 FacesContext에서 사용할 수 있다.

예를 들어, 사용자가 호텔 예약을 완료하기 위해 필요한 상세 정보를 입력하는 뷰가 있다고 가정하자. 여러분은 거기서 입력받은 체크인 체크아웃 날짜가 비즈니스 규칙에 맞는지 확인해야 한다. transition 엘리먼트에서 그러한 모델-수준 검증을 호출할 수 있다.
 
<view-state id="enterBookingDetails">
    <transition on="proceed" to="reviewBooking">
        <evaluate expression="booking.validateEnterBookingDetails(messageContext)" />
    </transition>
</view-state>
          
여기서 "proceed" 이벤트는 booking 인스턴스의 모델-수준 검증 매서드를 호출하여 처리한다. 매서드를 호출할 때 MessageContext 인스턴스를 넘겨줘서 메시지를 기록할 수 있게 한다. 그런다음 h:messages 컴포넌트를 사용하여 JSF 메시지를 보여줄 수 있다.

12.7.4. Ajax 이벤트 처리하기

스프링 Faces는 표준 JSF 컴포넌트에 Ajax-기반 일부 뷰 수정 기능을 추가한 몇몇 특별한 UICommand 컴포넌트를 제공한다. 이들 컴포넌트는 사용자가 사용하는 브라우져의 기능이 떨어진다면 전체 페이지를 리프래시 하여 모두 잘 동작할 것이다.

[노트] 노드
스프링 Faces의 코어 JSF 지원이 JSF 1.1 호환가능 하지만, 스프링 Faces Ajax 컴포넌트는 JSF 1.2를 필요로 한다.

Revisiting the earlier example with the paged table, you can change the "More Results" link to use an Ajax request by replacing the standard commandButton with the Spring Faces version (note that the Spring Faces command components use Ajax by default, but they can alternately be forced to use a normal form submit by setting ajaxEnabled="false" on the component):

           
<sf:commandLink id="nextPageLink" value="More Results" action="next" />
           

This event is handled just as in the non-Ajax case with the transition element, but now you will add a special render action that specifies which portions of the component tree need to be re-rendered:

<view-state id="reviewHotels">
    <on-render>
        <evaluate expression="bookingService.findHotels(searchCriteria)"
                  result="viewScope.hotels" result-type="dataModel" />
    </on-render>
    <transition on="next">
        <evaluate expression="searchCriteria.nextPage()" />
        <render fragments="hotels:searchResultsFragment" />
    </transition>
</view-state>
           

The fragments="hotels:searchResultsFragment" is an instruction that will be interpreted at render time, such that only the component with the JSF clientId "hotels:searchResultsFragment" will be rendered and returned to the client. This fragment will then be automatically replaced in the page. The fragments attribute can be a comma-delimited list of ids, with each id representing the root node of a subtree (meaning the root node and all of its children) to be rendered. If the "next" event is fired in a non-Ajax request (i.e., if JavaScript is disabled on the client), the render action will be ignored and the full page will be rendered as normal.

In addition to the Spring Faces commandLink component, there is a corresponding commandButton component with the same functionality. There is also a special ajaxEvent component that will raise a JSF action even in response to any client-side DOM event. See the Spring Faces tag library docs for full details.

An additional built-in feature when using the Spring Faces Ajax components is the ability to have the response rendered inside a rich modal popup widget by setting popup="true" on a view-state .

<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
    <on-entry>
        <render fragments="hotelSearchFragment" />
    </on-entry>
    <transition on="search" to="reviewHotels">
        <evaluate expression="searchCriteria.resetPage()"/>
    </transition>
</view-state>
           

If the "changeSearchCriteria" view-state is reached as the result of an Ajax-request, the result will be rendered into a rich popup. If JavaScript is unavailable, the request will be processed with a full browser refresh, and the "changeSearchCriteria" view will be rendered as normal.

신고

'Spring Web Flow > Chapter 12' 카테고리의 다른 글

SWF 12장 JSF 통합  (0) 2009.03.10
top


SWF 11장 스프링 자바스크립트 퀵 레퍼런스



11.1 도입

스프링 자바스크립트(spring-js)는 Dojo 같이 자주 사용하는 자바스크립트 툴킷에 대한 경량 추상화다. 목표는 공통의 클라인트-쪽 프로그래밍 모델을 제공하여 웹 페이지를 리치 위젯과 애이작스 리모팅으로 급격히 개선하는 것이다.

11.2. 자바스크립트 리소스 제공하기

스프링 JS는 웹 애플리케이션 루트 디렉토리와 jar 파일에서 자바스크립트와 CSS 파일 같은 웹 리소스를 서빙하는 ResourceServlet를 제공한다. 이 서블릿은 Spring.js 파일을 여러분 페이지에 공급하는 편리한 방법을 제공한다. 이 서블릿을 배포하려면 web.xml에 다음과 같이 설정하라.

<!-- Serves static resource content from .jar files such as spring-js.jar -->
<servlet>
    <servlet-name>Resource Servlet</servlet-name>
    <servlet-class>org.springframework.js.resource.ResourceServlet</servlet-class>
</servlet>
       
<!-- Map all /resources requests to the Resource Servlet for handling -->
<servlet-mapping>
    <servlet-name>Resource Servlet</servlet-name>
    <url-pattern>/resources/*</url-pattern>
</servlet-mapping>
       
11.3. 스프링 자바스크립트를 페이지에 포함시키기

스프링 JS는 자신의 API 구현체를 유명한 자바스크립트 툴킷으로 빌드 할 수 있게 설계됐다. Spring.js 기본 구현체는 Dojo 툴킷을 기반으로 한다.

스 프링 자바스크립트를 페이지에서 사용하려면 보통 기반으로 하는 툴킷, Spring.js 기반 인터페이스 파일, 스프링-(라이브러리 구현체).js 파일을 포함시킬 필요가 있다. 예를 들어 다음은 ResourceServlet를 사용하여 Dojo 구현체를 가져온다.

<script type="text/javascript" src="<c:url value="/resources/dojo/dojo.js" />"> </script>
<script type="text/javascript" src="<c:url value="/resources/spring/Spring.js" />"> </script>
<script type="text/javascript" src="<c:url value="/resources/spring/Spring-Dojo.js" />"> </script>
       
기 반 라이브러리의 위젯 시스템을 사용하면 보통 여러분은 반드시 원하는 룩앤필(look and feel)을 얻기 위해 CSS 리소스를 포함시킬것이다. booking-mvc 레퍼런스 애플리케이션은 Dojo의 tundra.css를 포함시킨다.

<link type="text/css" rel="stylesheet" href="<c:url value="/resources/dijit/themes/tundra/tundra.css" />" />
       
11.4. 스프링 자바스크립트 데코레이션

스 프링 자바스크립트의 주요 개념은 기존 DOM 노드에 데코레이션을 적용하는 개념이다. 이 기술은 기능이 좋치 않은 브라우저에서도 여전히 동작할 페이지 같은 웹 페이지를 혁신적으로 개선할 때 사용한다. addDecoration 매서드는 데코레이션을 적용할 때 사용한다.

다음 예제는 스프링 MVC의 <form:input> 태그에 풍부한 제안 기능을 가능하게 하는 것을 보여준다.

<form:input id="searchString" path="searchString"/>
<script type="text/javascript">
    Spring.addDecoration(new Spring.ElementDecoration({
        elementId: "searchString",
        widgetType: "dijit.form.ValidationTextBox",
        widgetAttrs: { promptMessage : "Search hotels by name, address, city, or zip." }}));
</script>
       
ElementDecoration 는 리치 위젯 기능을 기존의 DOM 노드에 추가할 때 사용한다. 이 데코레이션 타입은 기반으로 하는 툴킷을 완전히 감추는 것을 목표로 하지 않는다. 따라서 툴킷의 네이티브 위젯 타입과 속성을 직접 사용한다. 이 방법은 공통의 데코레이션 모델을 사용하여 기반하는 툴킷의 모든 위젯을 일관적인 방법으로 통합하기 위한 것이다. booking-mvc 레퍼런스 애플리케이션에서 제안에서 부터 클라이언트쪽 검증까지 데코레이션을 적용하는 많은 예제를 참조하라.

ElementDecoration를 사용하여 리치 검증 기능을 갖춘 위젯을 적용할 때 일반적인 요구 사항은 폼이 검증을 통과하기 전까지는 서브밋하지 않는 것이다. ValidateAllDecoration으로 그것을 할 수 있다.

<input type="submit" id="proceed" name="_eventId_proceed" value="Proceed" />
<script type="text/javascript">
    Spring.addDecoration(new Spring.ValidateAllDecoration({ elementId:'proceed', event:'onclick' }));
</script>
       

이것은 "Proceed" 버튼을 클라이언트 쪽 검증을 하고 성공적으로 패스 할 때 까지 폼 서브밋을 허용하지 않는 특별한 onclick 이벤트 핸들러로 데코레이트한다.

AjaxEventDecoration는 원격 애이작스 요청을 서버로 보내는 클라이언트쪽 이벤트 리스너를 적용한다. 또한 응답할 때 자동으로 콜백 함수를 링크에 등록한다.

<a id="prevLink" href="search?searchString=${criteria.searchString}&page=${criteria.page - 1}">Previous</a>
<script type="text/javascript">
    Spring.addDecoration(new Spring.AjaxEventDecoration({
        elementId: "prevLink",
        event: "onclick",
        params: { fragments: "body" }
    }));
</script>
       
이 것은 "Previous Results" 링크의 onclick 이벤트를 응답시에 특정 조각을 다시 랜더링하도록 특별한 매개변수와 함께 애이작스 호출로 꾸며준다. 이 링크는 클라이언트에서 자바스크립트를 사용할 수 없는 경우에도 여전히 동작한다는 것에 주목하라. (애이작스 요청 다루기에서 서버에 요청을 어떻게 다루는지 자세히 살펴보라.)

또한 하나의 엘리먼트에 여러 개의 데코레이션을 적용하는 것도 가능하다. 다음 예제는 애이작스와 validate-all 서브밋 금지를 같이 적용한 버튼에 대한 예제다.

<input type="submit" id="proceed" name="_eventId_proceed" value="Proceed" /> 
<script type="text/javascript">
    Spring.addDecoration(new Spring.ValidateAllDecoration({elementId:'proceed', event:'onclick'}));
    Spring.addDecoration(new Spring.AjaxEventDecoration({elementId:'proceed', event:'onclick',formId:'booking', params:{fragments:'messages'}}));
</script>
       
Dojo의 쿼리 API를 사용하여 한 줄로 여러 엘리먼트에 하나의 데코레이션을 적용하는 것도 가능하다. 다음 예제는 체크박스 집합을 Dojo Checkbox 위젯으로 꾸민다.

<div id="amenities">
<form:checkbox path="amenities" value="OCEAN_VIEW" label="Ocean View" /></li>
<form:checkbox path="amenities" value="LATE_CHECKOUT" label="Late Checkout" /></li>
<form:checkbox path="amenities" value="MINIBAR" label="Minibar" /></li>
<script type="text/javascript">
    dojo.query("#amenities input[type='checkbox']").forEach(function(element) {
        Spring.addDecoration(new Spring.ElementDecoration({
            elementId: element.id,
            widgetType : "dijit.form.CheckBox",
            widgetAttrs : { checked : element.checked }
        }));
    });
</script>
</div>

11.5. 애이작스 요청 다루기

스프링 자바스크립트의 클라이언트 쪽 애이작스 응답 다루기는 서버에서 "조각" 받기 개념을 기반으로 했다. 이들 조각은 단순한 표준 HTML로 기존 페이지의 일부를 교체할 의도를 가지고 있다. 서버에서 필요한 핵심 조각은 전체 응답 중에서 어떤 조각을 일부만 랜더링 해야 하는지 판단할 때 사용한다.

전체 응답 중에 일부 조각만 랜더링 하려면 전체 응답은 반드시 응답 구성할 때 컴포지션을 사용할 수 있고 그 컴포지션의 구성 요소들은 개별적으로 참조하거나 랜더링 할 수 있는 템플릿 기술을 사용해서 만들어야 한다. 스프링 자바스크립트는 타일즈(Tiles)를 사용하는 간단한 스프링 MVC 확장을 통해 이것을 달성한다. 컴포지션을 지원하는 어떤 템플릿 시스템이든지 이론적으로는 같은 기술로 사용할 수 있다.

스프링 자바스크립트의 애이작스 리모팅 기능은 애이작스 요청을 다루는 코드가 일반 브라우저 요청과 다르지 않아야 한다는 개념을 기반으로 한다. 따라서 애이작스 요청에 대한 별도의 지식은 코드에서 직접적으로 필요하지 않고 동일한 핸들러를 두 가지 스타일의 요청에 모두 사용할 수 있다.

11.5.1. 스프링 MVC 컨트롤러로 애이작스 요청 다루기

애이작스 요청을 스프링 MVC 컨트롤러로 다루려면 스프링 MVC 확장이 제공하는 설정을 여러분의 스프링 애플리케이션 컨텍스트에 일부 응답을 랜더링 하도록 추가하면 된다.

<bean id="tilesViewResolver" class="org.springframework.js.ajax.AjaxUrlBasedViewResolver">
    <property name="viewClass" value="org.springframework.webflow.mvc.view.FlowAjaxTilesView"/>
</bean>
           
이것은 AjaxUrlBasedViewResolver를 설정하여 애이작스 요청을 분석하고 FlowAjaxTilesView 객체를 만들어서 적절한 부분을 랜더링하는 것을 처리하게 한다. FlowAjaxTilesView는 웹 플로우와 순수 스프링 MVC 요청을 모두 랜더링하는 작업을 할 수 있다. 조각은 타일즈 뷰 정의의 개별 속성에 대응한다. 예를 들어, 다음의 타일즈 뷰 정의를 보자.

<definition name="hotels/index" extends="standardLayout">
    <put-attribute name="body" value="index.body" />
</definition>

<definition name="index.body" template="/WEB-INF/hotels/index.jsp">
    <put-attribute name="hotelSearchForm" value="/WEB-INF/hotels/hotelSearchForm.jsp" />
    <put-attribute name="bookingsTable" value="/WEB-INF/hotels/bookingsTable.jsp" />
</definition>
           
애이작스 요청은 "body", "hotelSearchForm", "bookingsTable"를 기술하여 요청에서 일부를 랜더링할 수 있다.

11.5.2. 스프링 MVC + 스프링 웹 플로우에서 애이작스 요청 다루기

스프링 웹 플로우는 조각의 부가적인 랜더리을 플로우 정의 언어에서 render 엘리먼트로 직접 다룬다. 이 방법의 장점은 조각의 선택이 클리이언트 쪽 코드와 완전히 분리 된다는 것이다. 스프링 MVC 컨트롤러 접근 방법에서 사용하듯이 요청에 넘겨주는 별도의 매개변수가 필요 없다. 예를 들어, 만약 앞선 예제 타일즈 뷰를 리치 자바스크립트 팝업으로 "hotelSearchForm" 조각을 랜더링 하고 싶다면 다음과 같이 할 수 있다.

<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
    <on-entry>
        <render fragments="hotelSearchForm" />
    </on-entry>
    <transition on="search" to="reviewHotels">
        <evaluate expression="searchCriteria.resetPage()"/>
    </transition>
</view-state>               
           
       
신고

'Spring Web Flow > Chapter 11' 카테고리의 다른 글

SWF 11장 스프링 자바스크립트 퀵 레퍼런스  (0) 2009.03.05
top


SWF 10장 스프링 MVC 통합



10.1. 도입

이번 장에서는 웹 플롱를 스프링 MVC 웹 애플리케이션으로 어떻게 통합하는지 살펴보겠다. booking-mvc 예제 애플리케이션은 웹 플로우와 스프링 MVC에 대한 좋은 참고자료다. 이 애플리케이션은 간단하게 만든 여행 사이트로 사용자가 호텔 방을 검색하고 예약할 수 있다.

10.2 web.xml 설정하기

스프링 MVC를 사용하는 첫 번쨰 단계는 DispatcherServlet을 web.xml에 설정하는 것이다. 여러분은 일반적으로 웹 애플리케이션 마다 한 번씩 이 작업을 할 것이다.

아 래 예제는 /spring/ 으로 시작하는 모든 요청을 DispatcherServlet으로 맵핑한다. init-param을 사용하여 contextConfigurationLocation을 제공한다. 이 설정 파일은 웹 애플리케이션에 관한 것이다.

<servlet>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/web-application-config.xml</param-value>
    </init-param>
</servlet>
   
<servlet-mapping>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <url-pattern>/spring/*</url-pattern>
</servlet-mapping>

10.3. 플로우로 디스패칭하기

DispatcherServlet은 애플리케이션 리소스에 대한 요청을 핸들러에게 전달한다. 플로우는 핸들러 하나의 핸들러 타입니다.

10.3.1. FlowHandlerAdapter 등록하기

요청을 플로우로 디스패칭하는 첫 번째 단계는 스프링 MVC에서 플로우 처리가 가능하게 하는 것이다. 그렇게 하려면 FlowHandlerAdapter를 설치하라.

<!-- Enables FlowHandler URL mapping -->
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
    <property name="flowExecutor" ref="flowExecutor" />
</bean>
           

10.3.2. 플로우 맵핑 정의하기

플로우 처리를 가능하게 했다면, 다음 단계는 특정 애플리케이션 리소스를 여러분 플로우로 맵핑하는 것이다. 그렇게 하는 가장 간단한 방법은 FlowHandlerMapping를 정의하는 것이다.

<!-- Maps request paths to flows in the flowRegistry;
     e.g. a path of /hotels/booking looks for a flow with id "hotels/booking" -->       
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
    <property name="flowRegistry" ref="flowRegistry"/>
    <property name="order" value="0"/>
</bean>
           
이 맵핑을 설정함으로써 Dispatcher는 애플리케이션 리소스 경로를 플로우 레지스트리에 있는 플로우로 맵핑한다. 예를 들어, /hotels/booking 경로의 리소스에 접근한다면 레지스트리는 hotels/booking id를 가지고 있는 플로우를 찾는다. 만약 해당 id를 가진 플로우를 발경하면 그 플로우가 요청을 처리한다. 만약 플로우를 찾지 못하면 다음 Dispatcher에 있는 (ordered chain)순서에 따라 다음 핸들러 맵핑이 찾아보거나 "noHandlerFound" 응답을 반환한다.

10.3.3. 플로우 처리 워크플로우

유효한 플로우 맵핑을 발견하면, FlowHandlerAdapter는 플로우의 새로운 실행 시작 지점이 어딘지 알아내거나 현재 HTTP 요청 정보를 기반으로 기존의 실행을 다시 시작한다. 어탭터가 적용할 플로우 실행을 새로 시작하거나 다시 시작하는데 관련된 여러 기본 행위가 존재한다.

  • HTTP 요청 매개변수들은 모든 시작 플로우 실행에서 이용할 수 있다.
  • 플로우 실행이 최종 응답 없이 종료되면 기본 핸들러가 동일한 요청을 새로운 실행 시작을 시도한다.
  • 예 외가 NoSuchFlowExecutionException이 아니라면 처리하지 않는 예외를 DispatcherSerlvet에 위임한다. 기본 핸들러는 NoSuchFlowExecutionException를 만나면 새로운 실행을 시작하여 예외를 극복하려고 한다.
보다 자세한 내용은 FlowHandlerAdapter API 문서를 참조하라. 여러분은 이런 기본 행위를 상속 또는 여러분 만의 FlowHandler를 구현하여 재정의 할 수 있다. 다음 절에서 자세히 다루겠다.

10.4. 커스텀 FlowHandler 구현하기

FlowHandler는 HTTP 서블릿 환경에서 플로우를 어떻게 실행할지 커스터마이징 할 때 사용할 수 있는 확장 지점이다. FlowHandler는 FlowHandlerAdapter가 사용하며 다음을 책임진다.
  • 실행할 플로우 정의의 id를 반환한다.
  • 시작할 때 플로우의 새로운 실행에 넘겨줄 입력값을 만든다.
  • 종료할 때 플로우의 실행에서 반환하는 결과를 처리한다.
  • 플로우 실행 중에 발생하는 예외를 처리한다.
이런 책임들은 org.springframework.mvc.servlet.FlowHandler 인터페이스에 정의에 나타나있다.

public interface FlowHandler {

    public String getFlowId();

    public MutableAttributeMap createExecutionInputMap(HttpServletRequest request);

    public String handleExecutionOutcome(FlowExecutionOutcome outcome,
        HttpServletRequest request, HttpServletResponse response);

    public String handleException(FlowException e,
        HttpServletRequest request, HttpServletResponse response);
}               
       
To implement a FlowHandler, subclass AbstractFlowHandler. All these operations are optional, and if not implemented the defaults will apply. You only need to override the methods that you need. Specifically:

FlowHandler를 구현하려면 AbstractFlowHandler를 상속하라. 모든 기능은 부가적인 것이기 떄문에 구현하지 않으면 기본 설정이 적용된다. 여러분은 필요한 매서드만 재정의하면 된다. 특히...
  • 여러분 플로우의 id를 Http 요청에서 직접 가져올 수 없을 때 getFlowId(HttpServletRequest)를 재정의하라. 기본으로 실행할 플로우의 id는 요청 URI의 pathinfo 부분에서 가져온다. 예를 들어 http://localhost/app/hotels/booking?hotelId=1는 기본으로 플로우 id로 hotels/booking를 가져온다.
  • HttpServletRequest에서 플로우 입력 매개변수 추출을 상세하게 제어하고 싶다면 createExecutionInputMap(HttpServletRequest)를 재정의하라. 기본으로 모든 요청 매개변수를 플로우 입력 매개변수로 사용한다.
  • 특정 플로우 실행 결과를 별도 방식으로 처리하고 싶다면 handleExecutionOutcome을 재정의하라 기본 행위는 종료한 플로우의 URL로 리다이렉트하여 플로우의 새로운 실행을 시작한다.
  • unhandled 플로우 예외를 상세하게 제어하고 싶다면 handleException을 재정의하라. 기본 행동은 클라이언트가 종료됐거나 만료된 플로우 실행에 접근하면 플로우 재시작을 시도한다. 그밖의 예외는 스프링 MVC ExceptionResolver 기반 시설에 던져준다.
10.4.1. FlowHandler 예제

스프링 MVC와 웹 플로우 사이에 흔한 협력은 종료했을 때 @Controller로 리다이렉트 하는 것이다. FlowHandler는 특정 컨트롤러 URL로 플로우 정의에 결합하지 않고도 할 수 있게 해준다. 스프링 MVC로 리다이렉트하는 FlowHandler 예제는 아래와 같다.

public class BookingFlowHandler extends AbstractFlowHandler {
    public String handleExecutionOutcome(FlowExecutionOutcome outcome,
                                         HttpServletRequest request, HttpServletResponse response) {
        if (outcome.getId().equals("bookingConfirmed")) {
            return "/booking/show?bookingId=" + outcome.getOutput().get("bookingId");
        } else {
            return "/hotels/index";
        }
    }
}
           
이 핸들러는 오직 플로우 실행 결과를 특정 방법으로 다뤄야 하기 때문에 다른 것은 재정의할 필요가 없다. bookingConfirmed 결과만 새로운 예약을 보여주도록 리다이렉트할 것이다. 그밖에 다른 결과는 호텔 인덱스 페이지로 다시 리다이렉트 한다.

10.4.2. 커스텀 FlowHandler 배포하기

To install a custom FlowHandler, simply deploy it as a bean. The bean name must match the id of the flow the handler should apply to.

커스텀 FlowHandler를 설치하려면 간단하게 빈으로 배포하면 된다. 빈 이름은 반드시 핸들러를 적용할 플로우의 id와 일치해야 한다.

<bean name="hotels/booking" class="org.springframework.webflow.samples.booking.BookingFlowHandler" />
           
이 설정에서 /hotels/booking에 접근하는 것은 커스텀 BookingFlowHandler를 사용하는 /hotels/booking 플로우를 실행하는 것이다. 예약 플로우가 종료되면 FlowHandler는 플로우 실행 결과를 처리하고 적절한 컨트롤러로 리다이렉트 한다.

10.4.3. FlowHandler 리다이렉트

FlowExecutionOutcome 또는 FlowException를 처리하는 FlowHandler는 처리 한 다음 리다이렉트 할 리소스를 가리키는 문자열을 반환한다. 앞선 예제에서 BookingFlowHandler는 bookingConfirmed 결과는 booking/show 리소스 URI로 리다이렉트 하고 그밖에 모든 결과는 hotels/index 리소스로 리다이렉트 했다.

기본으로, 반환된 리소스 위치는 현재 서블릿 맵핑을 기준으로 상대적이다. 이로인해 플로우 핸들러가 상대 경로를 사용하여 애플리케이션의 다른 컨트롤러로 리다이렉트 할 수 있다. 또한 명시적인 리다이렉트 접두어는 좀 더 제어가 필요한 경우에 사용할 수 있다.

지원하는 명시적인 리다이렉트 접두어는 다음과 같다.
  • servletRelative: - 현재 서블릿에서 상대적인 위치의 리소스로 리다이렉트 하라.
  • contextRelative: - 현재 웹 애플리케이션 컨텍스트 경로에 상대적인 위치의 리소스로 리다이렉트 하라.
  • servetRelative: - 서버 루트에 상대적인 위치의 리소스로 리다이렉트 하라.
  • http:// 또는 https://: - 전체를 명시한 리소스 URI로 리다이렉트 하라.
이것과 동일한 접미어를 exteralRedirect를 사용하여 플로우 정의에서 사용할 수 있다. view-state 또는 end-state에서 지시자로 쓸 수 있다. 예를 들어 view="externalRedirect:http://springframework.org" 이렇게 쓸 수 있다.

10.5. View Resolution

웹 플로우 2는 다른 것을 기술하지 않으면 선택한 뷰 식별자를 플로우 작업 디렉토리에 위치한 파일로 맵핑한다. 기존의 스프링 MVC + 웹 플로우 애플리케이션에서는 외부 viewResolver가 이미 이런 맵핑을 다루고 있을 것이다. 따라서 그 리졸버를 계속 하용하고 플로우 뷰를 패키징 하는 방법을 바꾸고 싶지 않다면 웹 플로우를 다음과 같이 설정하라.

<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
   <webflow:location path="/WEB-INF/hotels/booking/booking.xml" />
</webflow:flow-registry>

<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator"/>

<bean id="mvcViewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
   <property name="viewResolvers" ref="myExistingViewResolverToUseForFlows"/>
</bean>
      
10.6. 뷰에서 발생한 이벤트 신호보내기

When a flow enters a view-state it pauses, redirects the user to its execution URL, and waits for a user event to resume. Events are generally signaled by activating buttons, links, or other user interface commands. How events are decoded server-side is specific to the view technology in use. This section shows how to trigger events from HTML-based views generated by templating engines such as JSP, Velocity, or Freemarker.

플로우가 멈춘 view-state로 들어가면, 사용자를 실행 URL로 리다이렉트 하고 사용자가 다시 시작 이벤트를 발생할 때까지 기다린다. 이벤트는 보통 활성화 버튼, 링크 또는 다른 사용자 인터페이스 커맨드에 의해 신호가 보내진다. 이벤트가 서버쪽에 어떻게 디코드 되느냐는 사용하는 뷰 기술에 따라 다르다. 이번 절에서 (JSP, Velocity, 또는 Freemarker 같은 템플릿 엔진에 의해 만들어진 뷰를 기반으로) HTML에서 이벤트를 어떻게 발생시키는지 살펴보겠다.

10.6.1. 이름이 있는 HTML 버튼 사용하여 이벤트 신호 보내기

아래 예제는 클릭했을 때 각각 proceed와 cancel 이벤트 신호를 보내는 두 개의 버튼을 보여준다.

<input type="submit" name="_eventId_proceed" value="Proceed" />
<input type="submit" name="_eventId_cancel" value="Cancel" />

버튼이 눌리면 웹 플로우는  _eventId_ 로 시작하는 요청 매개 변수 찾고 나머지 문자열을 이벤트 id로 간주한다. 이 예제에서 _eventId_proceed를 보내면 proceed가 id가 된다. 같은 폼에서 발생할 수 있는 여러 이벤트가 있을 때 이런 스타일을 사용할 수 있겠다.

10.6.2. 감춰진 HTML 폼 매개변수를 사용하여 이벤트 신호 보내기

아래 예제는 서브밋 할 때 proceed 이벤트를 보내는 폼이다.

<input type="submit" value="Proceed" />
<input type="hidden" name="_eventId" value="proceed" />   
          
여기서 웹 플로우는 간단하게 특수한 _eventId 매개변수를 찾고 그 값을 이벤트 id로 사용한다. 이런 스타일은 오직 폼에서 발생하는 이벤트가 한 개 일 때에만 사용하라.

10.6.3. HTML 링크를 사용하여 이벤트 신호 보내기

아래 예제는 활성화 했을 때 cancle 이벤트를 보내는 링크다.

<a href="${flowExecutionUrl}&_eventId=cancel">Cancel</a>       
          
이벤트를 발생시키면 HTTP 요청이 서버로 보내진다. 서버 쪽에서는 플로우가 현재 view-state에서 이벤트를 디코딩한다. 어떻게 이 디코딩 프로세스가 동작하는지는 뷰 구현체에 따라 다르다. 스프링 MVC 뷰 구현체가 간단하게 _eventId 이름을 가지고 있는 요청 매개변수를 찾는 것을 기억하라. 만약 _eventId 매개변수가 없다면 뷰는 _eventId_로 시작하는 매개변수를 찾고 해당 문자열의 남은 부분을 이벤트 id로 사용한다. 만약 둘 다 없는 경우에는 어떤 플로우도 실행하지 않는다.
신고

'Spring Web Flow > Chapter 10' 카테고리의 다른 글

SWF 10장 스프링 MVC 통합  (0) 2009.03.04
top


SWF 5장 액션 실행하기



5.1. 소개

이번 장에서는 action-state 엘리먼트를 사용하여 플로우 내에서 특정 시저에 액션 실행을 제어하는 방법을 살펴본다. 또한 decision-state 엘리먼트를 사용하여 플로우 방향을 결정하는 방법도 살펴본다. 마지막으로 플로우 내에서 가능한 다양한 지점에서 액션을 호출하는 예제를 다룰 것이다.

5.2. 액션 스테이트 정의하기

액션을 호출하고 싶을 때 action-state 엘리먼트를 사용하면 액션의 결과에 기반하여 다른 상태로 전이한다.

<action-state id="moreAnswersNeeded">
    <evaluate expression="interview.moreAnswersNeeded()" />
    <transition on="yes" to="answerQuestions" />
    <transition on="no" to="finish" />
</action-state>

위의 action-state를 사용하여 인터뷰를 완성하는데 더 많은 질문이 필요한지 확인하는 인터뷰 플로우는 다음과 같다.

<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

    <on-start>
        <evaluate expression="interviewFactory.createInterview()" result="flowScope.interview" />
    </on-start>

    <view-state id="answerQuestions" model="questionSet">
        <on-entry>
            <evaluate expression="interview.getNextQuestionSet()" result="viewScope.questionSet" />
        </on-entry>
        <transition on="submitAnswers" to="moreAnswersNeeded">
            <evaluate expression="interview.recordAnswers(questionSet)" />
        </transition>
    </view-state>
   
    <action-state id="moreAnswersNeeded">
        <evaluate expression="interview.moreAnswersNeeded()" />
        <transition on="yes" to="answerQuestions" />
        <transition on="no" to="finish" />
    </action-state>

    <end-state id="finish" />
   
</flow>

5.3. 의사결정 상태 정의하기

decision-state 엘리먼트를 action-state 대신 사용하여 편리한 if/else 문법을 사용하여 경로 결정을 할 수 있다. 아래 예제는 위에서 봤던 moreAnswersNeeded 상태를 action-state 대신 의사결정 상태로 구현한 것이다.

<decision-state id="moreAnswersNeeded">
    <if test="interview.moreAnswersNeeded()" then="answerQuestions" else="finish" />
</decision-state>
           
5.4. 액션 결과 이벤트 맵핑

액션은 보통 일반 자바 객체의 매서드를 호출한다. action-state와 decision-state에서 호출한 매서드는 뷰 상태를 전이할 때 사용할 값을 반환한다. 전이는 이벤트에 의해 발생하기 때문에 매서드의 반환값은 반드시 먼저 Event 객체로 맵핑되어야 한다. 다음 표는 흔한 반환값을 어떻게 Event 객체로 맵핑하는지 보여준다.

표 5.1. 액션 매서드 반환 값을 이벤트 id로 맵핑하기

|| 매서드 반환 값 || 맵핑한 이벤트 식별자 표현식 ||
| java.lang.String | 문자열 값 |
| java.lang.Boolean | (true 면) yes, (false 면) no |
| java.lang.Enum | Enum 이름 |
| 그밖에 다른 타입 | success |

아래 예제의 액션 스테이트에서 매서드가 boolean 값을 반환한다는 것을 알 수 있다.

<action-state id="moreAnswersNeeded">
    <evaluate expression="interview.moreAnswersNeeded()" />
    <transition on="yes" to="answerQuestions" />
    <transition on="no" to="finish" />
</action-state>
       
5.5. 액션 구현

액션 코드를 POJO 로직으로 작성하는 것이 가장 일반적이지만, 다른 액션 구현 방법도 있다. 가끔은 액션 코드에서 플로우 컨텍스트에 접속할 필요가 있을 것이다. 이 때 POJO를 호출하고 거기에 flowRequestContext를 EL 변수로 넘겨줄 수 있다. 또는, Action 인터페이스를 구현하거나 MultiAction 기본 클래스를 상속받을 수도 있다. 이러한 대안들이 보다 강한 타입 안전성을 제공하여 여러분의 액션 코드와 스프링 웹 플로우 API 사이를 자연스럽게 연결해준다. 아래에서 이들 각각에 대한 예제를 살펴보자.

5.5.1. POJO 액션 호출하기

<evaluate expression="pojoAction.method(flowRequestContext)" />   
           

public class PojoAction {
    public String method(RequestContext context) {
        ...
    }
}
           
5.5.2. 커스텀 Action 구현체 호출하기

<evaluate expression="customAction" />   
           

public class CustomAction implements Action {
    public Event execute(RequestContext context) {
        ...
    }
}
           
5.5.3. MultiAction 구현체 호출하기

<evaluate expression="multiAction.actionMethod1" />
   
           

public class CustomMultiAction extends MultiAction {
    public Event actionMethod1(RequestContext context) {
        ...
    }

    public Event actionMethod2(RequestContext context) {
        ...
    }

    ...
}
           
5.6. 액션 예외

액션은 보통 복잡한 비즈니스 로직을 캡슐화한 서비스를 호출한다. 이러한 서비스는 액션 코드가 처리해야 할 비즈니스 에외를 던질 수도 있다.

5.6.1. 비즈니스 예외를 POJO 액션에서 처리하기

다름 예제는 비즈니스 예외를 잡는 액션을 호출하고, 에러 메시지를 컨텍스트에 추가하고, 결과 이벤트 식별자를 반환한다. 결과는 호출한 플로우가 반응할 수 있는 플로우 이벤트로 간주한다.

<evaluate expression="bookingAction.makeBooking(booking, flowRequestContext)" />   
           

public class BookingAction {
   public String makeBooking(Booking booking, RequestContext context) {
       try {
           BookingConfirmation confirmation = bookingService.make(booking);
           context.getFlowScope().put("confirmation", confirmation);
           return "success";
       } catch (RoomNotAvailableException e) {
           context.addMessage(new MessageBuilder().error().
               .defaultText("No room is available at this hotel").build());
           return "error";
       }
   }
}
           
5.6.2. 비즈니스 예외를 MultiAction으로 처리하기

다음 예제는 기능적으로 위에 것과 같지만 POJO 액션이 아니라 MultiAction으로 구현했다. MultiAction은 ${methodName}(RequestContext) 형태의 액션 매서드를 필요로 하며 강한 타입 안전성을 제공한다. 반면 POJO 액샨은 더 많은 자유를 제공한다.

<evaluate expression="bookingAction.makeBooking" />   
           

public class BookingAction extends MultiAction {
   public Event makeBooking(RequestContext context) {
       try {
           Booking booking = (Booking) context.getFlowScope().get("booking");
           BookingConfirmation confirmation = bookingService.make(booking);
           context.getFlowScope().put("confirmation", confirmation);
           return success();
       } catch (RoomNotAvailableException e) {
           context.getMessageContext().addMessage(new MessageBuilder().error().
               .defaultText("No room is available at this hotel").build());
           return error();
       }
   }
}
           
5.7. 기타 액션 호출 예제

5.7.1. on-start

다음 예제는 서비스의 매서드를 호출하여 새로운 Booking 객체를 만드는 액션을 보여준다.

<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

    <input name="hotelId" />

    <on-start>
        <evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
                  result="flowScope.booking" />
    </on-start>

</flow>
   

5.7.2. on-entry

다음 예제는 상태 진입 액션으로 특별한 fragments 변수를 정의하여 view-state가 뷰의 일부만 랜더링 하도록 하고 있다.

<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
    <on-entry>
        <render fragments="hotelSearchForm" />
    </on-entry>
</view-state>
           

5.7.3. on-exit

다음 예제는 상태 종료(exit) 액션으로 편집중이던 레코드에 대한 롹을 해제하고 있다.

<view-state id="editOrder">
    <on-entry>
        <evaluate expression="orderService.selectForUpdate(orderId, currentUser)"
                  result="viewScope.order" />
    </on-entry>
    <transition on="save" to="finish">
        <evaluate expression="orderService.update(order, currentUser)" />
    </transition>
    <on-exit>
        <evaluate expression="orderService.releaseLock(order, currentUser)" />
    </on-exit>
</view-state>

5.7.4. on-end

다음 예제는 위와 같은 객체 롹킹을 플로우 시작과 종료(end) 액션으로 하는 것을 보여준다.

<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

    <input name="orderId" />

    <on-start>
        <evaluate expression="orderService.selectForUpdate(orderId, currentUser)"
                  result="flowScope.order" />
    </on-start>

    <view-state id="editOrder">
        <transition on="save" to="finish">
            <evaluate expression="orderService.update(order, currentUser)" />
        </transition>
    </view-state>

    <on-end>
        <evaluate expression="orderService.releaseLock(order, currentUser)" />
    </on-end>
   
</flow>

5.7.5. on-render

다음 예제는 랜더 액션으로 뷰를 랜더링 하기 전에 보여줄 호텔 목록을 로딩한다.

<view-state id="reviewHotels">
    <on-render>
        <evaluate expression="bookingService.findHotels(searchCriteria)"
                  result="viewScope.hotels" result-type="dataModel" />
    </on-render>
    <transition on="select" to="reviewHotel">
        <set name="flowScope.hotel" value="hotels.selectedRow" />
    </transition>
</view-state>
           
5.7.6. on-transition

다음 예제는 하위플로우 결과 이벤트 속성을 컬렉션에 추가하는 트랜지션 액션을 보여준다.

<subflow-state id="addGuest" subflow="createGuest">
    <transition on="guestCreated" to="reviewBooking">
        <evaluate expression="booking.guestList.add(currentEvent.attributes.newGuest)" /> 
    </transition>
</subfow-state>
           
5.7.7. Names actions

다음 예제는 하나의 action-state 내에서 액션 체인을 호출하는 방법을 보여준다. 각각의 액션 이름은 액션 결과 이벤트에 대한 식별자가 된다.

<action-state id="doTwoThings">
    <evaluate expression="service.thingOne()">
        <attribute name="name" value="thingOne" />
    </evaluate>
    <evaluate expression="service.thingTwo()">
        <attribute name="name" value="thingTwo" />
    </evaluate>
    <transition on="thingTwo.success" to="showResults" />
</action-state>
       
이 예제에서, 플로우는 thingTwo가 무사히 완료되면 showResults로 전이할 것이다.

신고

'Spring Web Flow > Chapter 5' 카테고리의 다른 글

SWF 5장 액션 실행하기  (0) 2009.01.30
top


번역 품질을 높이는 방법?



전 잘 모릅니다. 번역을 꾸준히 하고는 있지만 잘 한다고 생각하지도 않으며 잘 하려고 연구를 해보지도 않았습니다. 그럴 시간에 하나라도 더 번역을 해야하기 때문에... 그래도 한 번 생각난 김에 정리해보면 다음과 같습니다.

먼저 번역하는 사람의 문장력이 좋아야 합니다. 번역자가 문장력이 좋고 한글 표현을 잘 사용할 줄 안다면 그만큼 품질 좋은 번역 기사나 번역서가 나올 겁니다.

다음으로 번역자가 영어를 감당할 수 있어야 합니다. 영어를 잘 몰라도 주변에 영어를 잘 알고 있는 친구나 사전을 이용해 가면서 문맥에 비슷한 표현이 나오도록 할 수 있어야 합니다. 기술 서적 중에 쉬운 책들은 중학교 수준 영어 실력만 있어도 번역이 가능하죠.

세번째로는 번역하려는 내용을 알고 있어야 합니다. 저 같은 경우 짧은 글을 번역할 때는 이 규칙은 보통 무시합니다. 새로운 기술이 알고 싶어서 번역할 때도 있거든요. 대신 책이나 레퍼런스 같이 분량이 많아서 일관성이 있어야 하고 어떤 기준이 필요할 때는 해당 기술을 얼마나 잘 이해하고 있는가가 번역 품지을 좌우하기도 합니다. 실제로 제가 메이븐을 하나도 모를 때 메이븐 튜토리얼을 번역한 적이 있는데.. 요즘 들어 가끔보게 되면 민망해 죽을 지경입니다.

네번째로는 재벌이 중요합니다. 다시 한 번 전체 내용을 읽어보면서 날림 번역으로 어색한 문장이나 문맥에 맞지 않는 내용이 눈에 띄면 다시 원문과 비교해서 손보는 작업입니다. 이 작업을 하면서 자신이 읽어도 이해가 된다. 싶을 정도면 일단 OK 입니다. 저는 보통 짧은 글은 재벌도 잘 하지 않는 편입니다. 짧은 글은 애초에 처음 번역할 때 날림 번역을 하지 않고 문맥에 맞추면서 하기 때문에 자잘한 오타와 한 두 문장을 고쳐야 하는데 시간을 소비하고 싶지 않아서 입니다.

마지막으로는 번역을 꾸준히 해서 감을 잃지 말아야 하는건데 이건 잘 지키고 있습니다. 꾸준히 하다보면 비슷한 문장이 등장했을 때 번역 패턴이 생기기도 하고 더 좋은 한글 표현이 떠오르기도 합니다. 예를 들어 오늘 GMP를 듣다가 알게 된건데 All I want for cristmas is you 같은 경우. "내가 크리스마때 원하는 전부는 너야." 라고 All 을 곧이 곧대로 번역하는 거 보다 "내가 크리스마스때 원하는 건 오직 너 뿐이야." 라고 All의 의미를 반전시키면 훨씬 깔끔한 문장이 되는거죠. 이런 건 꾸준히 하다보면 하나 둘 씩 생기는 스킬 같은 거라고 생각합니다.

어쩃거나.. 결론은 번역 품질을 높이려면 번역자가 번역을 잘 해야 한다는 거... 그렇지 않고 베타리딩에 모든 걸 건다?? 글쎄요. 저 같은 경우 베타리딩 할 때 품질이 영 아니면 대충 해버리거나 중간에 포기해버립니다. 베타리더가 돈을 받는 것도 아니고 명예가 생기는 것도 아닌데 감수자나 번역자 역할을 대신 해줄 순 없으니까요.
신고
top

TAG 번역

one A for every B 번역하기



얼핏보면 모든 B에 대한 하나의 A. 즉 모든 B가 하나의 A를 공유하는 것 같이 보이지만, 전혀~~ 전~~~혀 아닙니다. 완전히 틀린 번역이 되버립니다.

"각각의 B에 A 하나씩"

이게 정답입니다.

"the application now maintains one instance of the aspect for every target"

자 그럼 위의 문장은 어떻게 번역 or 이해해야 할까요?

"이제 애플리케이션은 각각의 타겟 객체 마다 애스팩트 객체를 하나씩 사용하게 된다."

이렇게 됩니다. 간혹 원서로 공부를 하시거나 번역을 하실 때 주의 하시기 바랍니다.
신고
top

TAG Every, 번역

번역 할것들이 넘쳤다. 열심히 하자!



9월

- Ajax and Java development made simpler, Part 3: Build UI features based on DOM, JavaScript, and JSP tag files
- JSF for nonbelievers: JSF conversion and validation

10월

- Getting started with JavaServer Faces 1.2, Part 1: Building basic applications (스크린캐스팅)
- JSF for nonbelievers: JSF component development
- Automation for the people: Continual refactoring

11월

- Meet the JavaScript Development Toolkit
- Automation for the people: Hands-free database migration



- Java Persistence With Hibernate
- Pro Spring 2.5

기타

- Spring Dynamic Modules Reference
- The Ted Neward Challenge (AOP without the buzzwords)

신고
top


Ivy로 메이븐 저장소를 사용하기



Maven이 의존성 관리나, 다양한 플러그인들, 기본 와꾸 만들어주기(아키타입) 등의 기능 면에서 좋긴 한데, 학습 곡선 높고(Ant에 비해), 적응 시간이 오래 걸린다는(역시 Ant에 비해) 단점이 있습니다.

의존성 관리는 사실 사람이 개입하지 않고서 자동으로 처리하기는 참으로 난감한데, 그래도 초기에 한 번 고생하고, 주기적으로 한 시간간 정도만 고생하면 모든 라이브러리들을 CVS나 SVN에 들고다니지 않아도 되서 정말 편합니다. 구글코드 같은 경우 jar 파일을 다운로드/업로드 하다가 인터넷 연결 끊어져서 다시 커밋하거나 업데이트 받아야 하는 불상사가 생기기도 하죠. 따라서, 어찌됐든 의존성들은 버전 관리 저장소에서 빼주는게 훨씬 편합니다. 그러면서도 버전관리까지 해주는 Maven이 고맙기도 하지만, 사실 개발자가 고생한거지 Maven은 그냥 시킨대로 해줬을 뿐입니다. 그래도 하나 고마운게 있다면, 저장소. 로컬 저장소에 없으면 원격 저장소에서 다운로드 받을 수 있는 그 저장소가 좋습니다.

그런데 Ant에서도 이 메이븐 저장소(본문에서는 Common Repository라는 단어를 사용하고 있는데, 메이븐만 이용할 수 있는 저장소가 아니니까 이게 더 맞는 표현 같네요. 번역은 "공공 저장소" 라고 하고 있습니다.)를 이용할 수 있습니다. 따라서 굳이 Maven을 학습하지 않고 Ant 서브프로젝트인 Ivy의 task 몇 개만 살펴보시면 됩니다.

http://www.ibm.com/developerworks/java/library/j-ap05068/index.html

현재 번역 중이며 7월 중에 IBM DeveloperWorks에 올라갈 것 같네요.

신고
top


6, 7, 8월에 번역할 기사



6월에 번역할 기사

Plug-in development 101, Part 1: The fundamentals(완료, 한글판)

Automation for the people: Hands-off load testing
(완료, 처리중)

Plug-in development 101, Part 2: Introducing rich-client applications(진행중)

7월에 번역할 기사

Automation for the people: Manage dependencies with Ivy

Ajax and Java development made simpler, Part 1

Automation for the people: Pushbutton documentation

8월에 번역할 기사

Ajax and Java development made simpler, Part 2

Develop Ajax applications like the pros, Part 1


Meet the JavaScript Development Toolkit

매일 저녁 퇴근 후에 번역을하고, 스프링 DM 레퍼런스좀 보다 보면 금방 12시가 넘고 그럼 졸려서 뻗어버립니다. 기사 번역은 정말 재밌는 것 같습니다. 짧은 글들이라 하루 이틀 정도면 번역이 되는데(물론 가끔 말이 많은 기사들은 4일~5일이 걸리기도 합니다.) 다양한 주제로 읽고, 공부도 하고, 번역해서 용돈도 벌 수 있다는게 참 매력적입니다.

위 기사들이 번역되면 한국 IBM developerWorks에 올라옵니다. 요즘은 재벌을 안하고 한방에 번역하고 있어서 오탈자가 많을 듯 한데 편집해주시는 분께서 잘 봐주고 계신것 같아 다행입니다. 어쨋든, 오역이나 오탈자 지적 달게 받겠습니다.
신고
top


롤러스케이트 구현



원문 : http://martinfowler.com/bliki/RollerSkateImplementation.html

롤러스케스 구현 agile 2007년 9월 9일 반응

애자일 개발의 핵심 요소는 어떻게 하면 조그마한 기능 집합으로 시스템을 가동할 수 있을지를 도모하는 것이다. 우리는 비즈니스 가치를 목적으로 소프트웨어를 만든다. 따라서 빠르게 시스템을 가동할수록 그로 인한 비즈니스 가치를 조금이라도 더 빨리 얻을 수 있다.

나의 동료 Dave Leigh는 이런 류에 해당하는 이야기를 해줬다.(이 이야기를 예로 들기 좋아한다.) 중개 회사에서 일을 하고 있을 때였다. 그들은 시장에 들여놓고 싶어하는 새로운 종류의 상품을 가져왔다. 소프트웨어가 할 일은 웹 페이지를 제공해서 고객들이 입력을 하면 뒷단의 시스템에 필요한 거래를 생성하는 것이었다. 그러나 Dave는 그보다 더 빨리 제품을 시장에 들여놓는 방법을 생각해냈다.

•    버전 1은 정적인 웹 페이지로 제품을 설명하고 연락을 할 수 있는 전화번호를 제공한다. 몇몇 임시직을 고용하여 고객의 전화를 받고 주문 정보를 뒷단의 시스템에 입력하도록 한다.
•    버전 2는 고객이 정보를 입력할 수 있는 웹 양식이다. 하지만 이 버전에서는 데이터를 뒷단의 시스템으로 이동시키지 않는다. 그 대신 웹 양식은 팩스를 생성한다. 그들은 임시적으로 팩스기기로부터 주문을 받을 사람을 더 고용하여 팩스의 정보를 가지고 뒷단의 시스템으로 정보를 입력하도록 한다. 팩스 기기가 좀 멀리 떨어져있기 때문에 롤러스케이트라는 용어를 붙였다.
•    버전 3은 웹 양식을 읽어서 뒷단의 시스템으로 직접 연결한다.

처음 두 개의 버전은 최고로 멋진 해결책이 아닐 수도 있다. 그러나 그들은 제품을 시장에 훨씬 더 빨리 내놓을 수 있었다. 롤러스케이트를 사용한 점진적인 개발의 또 다른 예는 생각이 나질 않는다. 그러나 그 이유는 필요가 없어서라기 보다는 상상력의 부족 때문일 것이다.
신고
top


JAX-WS 2.0 웹 서비스 설계와 개발



원문 : Design and develop JAX-WS 2.0 Web services
번역 : 언젠가 한국 IBM developerWorks에 올라올 듯.
후기 : 학교 수업 중에 Axis를 사용해서 웹 서비스를 개발하고 배포, 테스트를 해본 적이 있습니다. 그 당시 XML, XSD를 익히는 것도 낯설었는데, 웹 서비스에 WSDL이니 스텁이니 하는 개념들까지 더하니까 정말 어지러웠습니다. 아마도 이 글로 처음 웹 서비스를 접하시는 분들은 저와 비슷한 현기증을 느낄 수 있을 것입니다. 그러나 만약 한 번이라도 웹 서비스를 개발해보신 적이 있으신 분들은 JAX-WS를 사용하면 자바 클래스에 간단한 애노테이션을 붙여서 WSDL을 생성해 낼 수 있다는 장점이 눈의 띄실 것입니다. 그만큼 편하게 웹 서비스를 개발할 수 있게 도와 줍니다.

번역할 때마다 비슷한 단어에서 어떻게 옮기는 것이 좋을까 고민하게 됩니다. mapped, map to 도 그런 것 중에 하나 인데, '맵핑 되어 있다." "맵핑한다."로 번역을 했습니다. 그리고 artifact는 그냥 '파일'로 번역을 했습니다. JAX-WS 결과물 만들기... 라고 하면 좀 어색해서요.ㅎ;;
신고
top


선언된 순서를 변경하는 것은 리팩터링인가?



원문 : IsDeclarationOrderingRefactoring     refactoring     1 September 2004

RefactoringBoundary.

자바 프로그램 내부에 있는 메소드와 필드가 선언된 순서를 변경하는 것은 리팩터링인가요?

근래의 프로그래밍 언어들은 내부에 선언되어 있는 요소들의 순서가 프로그램에 전혀 영향을 주지 않는다. 만약 텍스트 파일 내부에 있는 두 개의 메소드 위치를 서로 바꾸더라도, 프로그램은 아무런 영향을 받지 않는다. 이것을 리팩터링이라고 할 수 있는 이유는 변경으로 인해서 프로그램이 동작하는 방법에 영향을 주지 않기 때문이다. 이것은 설계를 변경하지 않으며, 만약 설계가 바뀐다면 리팩터링이라고 할 수 없다.

리팩터링 정의에서, “이해하기 쉽고 수정하는 비용을 최소화 하도록” 이라는 구문을 사용했다. 선언된 위치를 변경하는 것이 이에 해당하는가? 몇몇 경우에 그럴 수 있다고 생각한다. 클래스 내부에서 서로 관련되어 있는 속성들을 묶어두는 것이 좋다. 그러한 방법으로 속성들을 정렬하면 클래스가 어떻게 동작하는지 이해하는데 도움이 된다. 특히 테스트 케이스를 작성할 때 유용하다. (비록 특정 xunit 구현체에서는, 순서가 실행 결과에 영향을 줄 수 있지만 말이다.) 결국 코드의 이해도를 높여주기 때문에, 선언된 순서 변경을 리팩터링이라고 볼 수 있다.

이것이 내부 구현을 변경하지 않는다는 사실은 중요하지 않다. 메소드의 이름 변경은 실행하는 내용을 변경하지는 않는다. 하지만 이름 변경은 프로그램의 이해도를 높이는 매우 중요한 리팩터링이다.

신고
top


발견하지 못한 버그를 수정하는 것은 리팩터링인가?



원문 : IsFixingAnUnknownBugRefactoring     refactoring     3 September 2004

RefactoringBoundary.

Przemyslaw Pokrywka가 매우 난해한 질문을 했다. 에서 소개한 리팩터링 중에 하나로 Introduce Null Object라는 것이 있는데, (이것은 매우 유용한 리팩터링으로 Josh의 새 책에서도 다루고 있다) Przemyslaw의 요지는 이 리팩터링이 행동을 바꿀 수 있다는 것이었다. 만약 여러분이 null을 반환하는 메소드를 가지고 있고, 그 반환 값인 null에게 메소드 호출을 하면 null pointer exception을 받게 될 것이다. 이럴 때 Null Object를 사용해서 (예외를 발생시키는 것이 아니라) 기본 행동을 수행하도록 정의할 수 있다.

요즘 다수의 리팩터링들이 행동을 바꾸고 있는데, 이들은 근본적으로 그렇게 하도록 의도된 것 들이다. 예를 들어 Form Template Method를 적용하면, 프로그램이 다르게 동작한다. 핵심적으로 질문해야 할 것은 리팩터링 정의에서 언급한 “주목할 만한” 행동에 해당하는가 이다. “주목할 만한” 행동이란 프로그램이 원래 의도했던 것을 변경했는지 여부를 뜻한다. Introduce Null Object를 사용하면 프로그램에서 반환된 레퍼런스 변수를 조작하는 부분 (특히 null인지 확인하는 부분)을 살펴보아야 한다. 그렇기 때문에 이 리팩터링이 다소 복잡한 것이다.

질문 중에서 가장 흥미로운 부분은 만약에 버그가 있는 부분을 놓치면 어떤 일이 발생하는가 이다. 프로그램 내부의 어디에선가 null 레퍼런스에게 메소드를 호출하는 부분이 있다고 하자. 이 부분을 간과하여 놓쳤고 최종 사용자에게까지 전달되었다면, 리팩터링 전에는 예외를 발생시켰을 것이다. 리팩터링 후에는 기본 행동을 가지게 되고 이것은 사실상 버그를 고친 것에 해당한다. 미처 발견하지 못했던 버그를 고치는 것은 리팩터링일까?

그렇다. 왜냐면 스스로가 지각하지 못했거나 충분히 살펴보지 못한 (버그를 발생시키는) 행동은 “눈에 띄는” 행동이라고 할 수 없기 때문이다. 비록 버그를 알고 있었다 하더라도, 행동을 변함없이 유지하려고 인식했던 버그가 아니라면, 여전히 그것을 리팩터링이라고 불러도 좋다.

이것은 흥미로운 경우이기 때문에, 내 생각을 쉽게 바꿀 수도 있고 이와 비슷한 경계 사례들을 더 조사해 봐야겠다.

여기서 생각해 볼 것 한 가지는 수작업으로 하는 리팩터링과 툴 기반의 리택퍼링의 차이점이다. 손수 하는 리팩터링은 이런 식의 스스로 판단을 할 수 있는 반면에, 툴을 사용할 때는 좀 더 주의해야 한다. 아직은 툴들이 행동을 유지하는 것을 항상 보장하지는 못한다. 심지어, 파일로부터 읽어 들인 이름으로 리플렉션을 사용하여 메소드를 호출하는 경우 rename method는 리팩터링 도중 깨질 수 도 있다.

신고
top


성능 최적화가 리팩터링인가?



RefactoringBoundary.

프로그램의 성능을 향상 시키기 위해서 변경을 가했을 때, 이것을 리팩터링으로 볼 수 있나요?

성능 최적화와 리팩터링은 둘 모두 프로그램에 같은 변경을 가하기도 하며, 특정한 변경은 둘 모두에 해당하기도 하지만, 이 둘을 별개의 것으로 구분한다.

이 둘을 구분하는 이유는 서로 다른 목적을 가지고 있기 때문이다. 리팩터링을 하는 목적은 코드를 좀 더 쉽게 이해하기 위한 것이며, 성능 최적화의 목적은 좀 더 빠르게 동작하도록 하는 것이다. (예를 들어) 변수 하나를 추가하는 것이 둘 모두를 위한 것이 될 수도 있겠지만, 그 작업을 어떻게 수행하느냐에 따라 본질적으로 둘 중 하나에 해당하게 된다. 리팩터링을 할 때는 코드를 깔끔하게 정리하는 것에 집중해야 한다. 이때, 변경을 성공적으로 수행했는지에 대한 판단은 프로그램을 더 쉽게 이해하도록 변경했는가에 대한 자신의 (주관적인) 평가에 기반하게 된다. 성능 최적화를 할 때는 성능에 집중해야 한다. 프로파일러를 이용하여, 변경을 가하기 전과 후의 성능을 비교함으로써 변경 작업이 진짜로 성능 향상에 도움이 되었는지 확인해야 한다. 만약 성능이 매우 중요한 상황이라면, 차후에 (컴파일러, VM 같은)환경이 변경 됐을 때 그것의 효율성을 다시 테스트 할 수 있도록 변경 사항을 로그로 남겨두어야 한다.

결론적으로 리팩터링과 성능 최적화는 서로 비슷하기도 하고, 다수의 변경을 공유하기도 하지만, 그들의 목적이 다르기 때문에 별개의 것으로 구분한다.

원문 : IsOptimizationRefactoring

신고
top


Introduction to Apache Maven 2



원문 : http://www-128.ibm.com/developerworks/edu/j-dw-java-mavenv2.html
번역 : http://www.ibm.com/developerworks/kr/library/tutorial/j-dw-java-mavenv2.html
후기 :
잘 모르던 것(Maven 2)에 대한 글을 번역하는 일이 얼마나 난해한가를 경험할 수 있었습니다.
먼저 Maven에서 사용하는 용어들을 한글로 어떻게 옮겨야 할지 정말 난감했습니다. 그 중 가장 난감했던 것은 'Maven Coordinate' 입니다. 처음에는 "Maven 위치" 그 다음 원문을 조금 읽다가 "Maven 좌표"로 수정하고 다시 또 읽다가 "Maven 이름"으로 바꿨지만 여전히 맘에 안들어서 그냥 "Maven 코디네이트"로 수정할까 생각중입니다.
또 난감했던 것으로는 주로 Artifact(이것도 사실 옮기기 난감했지만 그냥 '이건 옮기지 말자'라고 마음 먹고 고유명사처럼 사용하니까 맘이 편하더군요.)를 나타낼 때 원문에서 자주 depedency라고 하는데 이걸 그냥 '종속성'으로 하면 전혀 문맥이 이어지지가 않습니다. 그래서 '종속성을 가지는 것' 이라고 풀어서 썼는데 참 난해하기 '서울역에 그지 없습니다.'(애욕전선 이상없다. 에서 나오는 대사 중 일부...)
번역 도중 그림과 소스코드가 자주 나오면 그보다 더 반가운 일은 없습니다. 완전 집중 했을 때 평균 1시간에 pdf로 3페이지 정도를 번역할 수 있었습니다. 하지만 글자가 많은 경우 1시간에 1페이지를 간신히 하기도 하고 그림과 코드가 많을 땐 1시간에 다섯 페이지를 번역할 수도 있었습니다.
빨리 하려다 보니 좋은 어구가 떠오르지 않으면 그냥 대강 글로 옮기고 넘어가는 부분이 많아져서 다 하긴 했지만 찝찝한 기분을 떨칠수가 없습니다. 이대로 보내드리는 건 '사실 번역 하기 싫다.' 라고 말하는 거나 다름없기 때문에 좀 더 다듬어서 보내드려야겠습니다.
신고
top


예제로 살펴보는 쓰레드 제어하기

Java : 2007.04.10 20:53


원문 : Controlling Threads by Example (http://www.onjava.com/pub/a/onjava/2007/04/06/controlling-threads-by-example.html)

번역 :
예제로 살표보는 쓰레드 제어하기 - (1)
예제로 살표보는 쓰레드 제어하기 - (2)

신고

'Java' 카테고리의 다른 글

숨어있는 Iterator 찾기  (0) 2007.05.01
Reflections on Java Reflection  (2) 2007.04.25
Collection과 Thread 2  (0) 2007.04.24
Collection과 Thread 1  (0) 2007.04.24
Thread와 Collection 관련 링크 모음  (0) 2007.04.19
예제로 살펴보는 쓰레드 제어하기  (0) 2007.04.10
Java 6.0의 Collection  (4) 2007.03.22
private에 대한 착각  (2) 2007.03.05
Stream 인코딩 바꾸기  (0) 2007.02.28
SWT 프로그램 실행하기  (0) 2007.02.21
제8회 한국 자바 개발자 컨퍼런스  (2) 2007.01.25
top

Java : 2007.04.10 20:53 Trackback. : Comment.

Pros and Cons of language oriented programming



Language Workbenches 번역 중에 일부분입니다. 현재 제대로 된 번역은 영회 형 블러그에 연재 중입니다. :)

먼저 해석 및 번역이 어려웠던 문장들 부터 정리해 봅니다.

However you'll probably come acrossthe embedded term if you look around at more writing on DSLs.

기선 : ???
영회 :
하지만, DSL에 관련된 많은 글에서 포함된 DSL가 같은 표현을 자주 보게 될 것이다.


Parser generator and compiler compiler tools existthat can help you manipulate quite complex languages, andof course the whole point of DSLs is that they are usually quite simple.

기선 :
파서 생성기와 컴파일러 도구는 매우 복잡한 언어에서 작업할 때 도움이 된다. 그리고 물론 DSL의 주요한 점은 매우 간단하다는 것이다.
영회 :
파서 생성기와 컴파일러 저작 도구는 매우 복잡한 언어를 DSL다룰 때 도움을 준다. 게다가 DSL은 대개매우 간단하다.


This lack of integration hitsus in lots of ways with tooling.

기선 :
통합의 결여는 툴을 사용하도록 한다.
영회 :
통합의 결여로 인해 도구가 필요해진다.


We also have the full power of our base languageavailable to us at all times, together with all thetooling that exists in our base language.

기선 :
또한 항상 기반 언어에서 사용가능 한 특성들을 모두 사용할 수 있어야 한다.
영회 :
해당 언어에서 사용 가능한 모든 특성들을 활용할 수 있다.


While much of this machinery is missing from Cbased languages, we're seeing features that can supportsome of this thinking.

기선 : 
C 기반의 언어들은 이러한 기능이 빠져있는 반면, 이러한기능을 지원할 수 있는 특성을 찾는다.
영회 :
C에 기반하고 있는 언어들은 내부 DSL 작성에 적합한기능이 많이 빠져 있지만, 존재하지 않는 것은 아니다.


Ideally you want only the actual tools you need foryour job - certainly no less, but only a few more.

기선 :
이상적으로 사용자가 실제로 작업에 필요로 하는 툴만을 원할 것이다.
영회 :
이상적인 것은 어떤 작업을 수행하는데 꼭 필요한 기능만 제공하는 즉, 부족하지 않으면서 너무 많은 것을 제공하지는 않는 개발도구이다.

아직 끝까지 차이를 정리한 것이 아니라 다음에 추가로 수정을 해야겠습니다.

그리고 아래는 영회형이 지적 해 주신 수정사항입니다.

1. one of ~ 를 ~ 중에 하나로 번역하면.. 매우 어색하다.


2. COBOL inference - that mosttechnologies that are supposed to eliminate professional programmersdo nothing of the sort.
이 부분은 완전히 반대로 해석했다.

mosttechnologies that are supposed to eliminate professional programmers/ do nothing of the sort.
앞 부분 전체가 주어부인.. 상당히 복잡한 구문이지.

This reminds us of what I call the COBOLinference - that most technologies that are supposed to eliminateprofessional programmers do nothing of the sort.

기선 :
COBOL inference란 대부분의 기술들이 전문적인 프로그래머들이아무것도 할 일이 없도록 발전한다는 것이다.
영회 :
이는 내가 코볼 추론(COBOL inference)이라고말하는 것을 떠올리게 한다. 대부분의 기술의 등장으로 전문적인 프로그래머가 사라질 것이라는 소문이 나돌지만그런 일은 벌어지지 않는다.

3. gettingdirect user input into programs
사용자를 참여시키는 것이 아니라 사용자의 입력을 프로그램에 넣는 것이지.



ps ...

기선 :
영회 형:

이렇게 하다보니 어색해서..

기선 :
영회 :

이렇게 했습니다. ^^;;


신고
top

TAG 번역






티스토리 툴바