jongfeel / BookReview

Reviews the IT or any books slowly and steady.
MIT License
4 stars 0 forks source link

14장 웹 프레젠테이션 패턴, Two Step View #734

Closed jongfeel closed 5 months ago

jongfeel commented 5 months ago

2단계 뷰

도메인 데이터를 먼저 일종의 논리적 페이지로 변환한 다음 이를 다시 HTML로 변환하는 2단계 과정을 통해 HTML로 변환한다.

image

웹 애플리케이션은 전체 사이트의 외형과 구조에 일관성을 지원해야 하는데 템플릿 뷰변환 뷰를 사용하는 경우 프레젠테이션의 결정이 여러 페이지나 변환 모듈에 중복되는 경우가 많기 때문에 일괄적으로 변경하기가 어려울 수 있다.

2단계 뷰(Two Step View)는 이 문제를 해결하기 위해 변환을 두 가지 단계로 나눠서 수행한다.

두 번째 단계를 변경해 전체적인 변경을 적용하거나 원하는 외형과 느낌을 지원한다.

작동 원리

HTML 변환을 2단계 프로세스로 처리한다.

중간 형식은 논리적 화면이다. 확실한 프레젠테이션 기반으로 특정 스타일을 일관되게 화면에 적용한다. 다양한 위젯과 데이터를 포함하지만 HTML 외형을 지정하지 않는 모델이다.

프레젠테이션 기반 구조는 각 화면을 위해 작성한 특정 코드에 의해 처리된다. 첫 번째 단계의 역할은 도메인 기반 모델에 접근해 현재 화면에 필요한 정보를 가져오고 이 정보를 프레젠테이션 기반 구조로 변환한다.

두 번째 단계는 프레젠테이션 기반 구조를 HTML로 변환한다. 결과 화면은 반드시 프레젠테이션 기반 구조에서 파생되어야 한다는 제약이 있다.

2단계 뷰를 만드는 쉬운 방법은 2단계 XSLT를 사용하는 것이다. 1단계 XSLT는 변환 뷰 방식을 따르며, 2단계 방식은 XSLT 스타일 시트 두 개를 사용한다. 첫 번째 단계의 스타일시트는 도메인 기반 XML을 프레젠테이션 기반 XML로 변환하며 두 번재 단계의 스타일시트는 XML을 HTML로 렌더링 한다.

다른 방법으로 프레젠테이션 기반 구조를 클래스의 집합(테이블 클래스, 행 클래스)으로 정의한다. 첫 번째 단계는 도메인 정보를 받고 이러한 클래스를 인스턴스화해 논리 화면을 모델링 하는 구조를 생성한다. 두 번째 단계는 각 프레젠테이션 기반 클래스가 직접 HTML을 생성하게 하거나 별도의 HTML 렌더러 클래스를 사용해 이러한 클래스를 HTML로 렌더링한다.

두 방식 모두 변환 뷰에 기반을 둔다. 템플릿 뷰 방식을 사용할 수 있으며, 이 경우에는 논리적 화면이라는 개념을 바탕으로 템플릿을 선택한다.

<field label="Name" value="getName" />

템플릿 시스템이 논리적 태그를 HTML로 변환한다.

Figure 14.4. Sample classes for two-step rendering. image

사용 시점

2단계 뷰의 가장 중요한 장점은 단계를 분리했기 때문에 전체적인 변경이 쉽다는 것이다. 다중 외형 앱(Multiappearance apps)은 여러 조직에 동일한 기본 기능이 제공되지만 조직별로 고유한 외형을 가진다. 예로 항공 여행 사이트가 있는데, 동일한 기본 기능을 제공하면서 각자 독특한 개성을 표현하기를 원한다.

단일 외형 앱은 흔하며 전면에 내세우는 조직이 하나이고 사이트 전체에 일관된 외형을 원한다.

1단계 뷰(템플릿 뷰 혹은 변환 뷰)에서는 웹 페이지당 뷰 모듈을 하나씩 만든다. (그림 14.6) 2단계 뷰는 페이지별로 하나씩 있는 첫 번째 단계 모듈과 전체 애플리케이션에 하나가 있는 두 번째 단계 모듈의 두 단계로 작업이 처리된다. (그림 14.7)

Figure 14.6. Single-stage view with one appearance. image

Figure 14.7. Two-stage view with one appearance. image

다중 외형 앱의 경우 화면과 외형의 조합별로 단계 뷰 하나가 사용된다. (그림 14.8) 화면 10개, 외형 3개라면 단일 단계 뷰 모듈이 30개 필요하지만 2단계 뷰를 사용하면 첫 번째 단계 10개와 두 번째 단계 3개로 해결 가능하다 (그림 14.9)

Figure 14.8. Single-stage view with two appearances. image

Figure 14.9. Two-stage view with two appearances. image

프레젠테이션 기반 구조를 외형의 필요성에 맞게 생성할 수 있어야 한다. 사이트 디자인은 프레젠테이션 기반 구조에 의해 제한을 받는데, 이는 심각한 제한이다.

또 다른 단점으로 별도 툴을 사용해야 한다는 점이다. 2단계 뷰는 프로그래머가 렌더러와 컨트롤러 객체를 작성해야 한다. 그래서 템플릿 뷰와 달리 디자인을 변경할 때마다 프로그래머가 관여해서 수정해야 한다.

2단계 뷰는 다중 계층을 사용하므로 프로그래밍 모델을 배우는데 조금 어렵다. 하지만 반복적인 보일러플레이트 코드(boilerplate code)를 줄이는데 유리하다.

예제: 2단계 XSLT(XSLT)

2단계 뷰 방식은 2단계 XSLT 변환을 사용한다. 첫 번째 단계는 도메인별 XML을 논리적 화면 XML로 변환하며 두 번째 단계는 논리적 화면 XML을 HTML로 변환한다.

도메인 기반 XML 예

<album>
    <title>Zero Hour</title>
    <artist>Astor Piazzola</artist>
    <trackList>
        <track>
            <title>Tanguedia III</title>
            <time>4:39</time>
        </track>
        <track>
            <title>Milonga del Angel</title>
            <time>6:30</time>
        </track>
        <track>
            <title>Concierto Para Quinteto</title>
            <time>9:00</time>
        </track>
        <track>
            <title>Milonga Loca</title>
            <time>3:05</time>
        </track>
        <track>
            <title>Michelangelo '70</title>
            <time>2:50</time>
        </track>
        <track>
            <title>Contrabajisimo</title>
            <time>10:18</time>
        </track>
        <track>
            <title>Mumuki</title>
            <time>9:32</time>
        </track>
    </trackList>
</album>

첫 번째 단계로 화면 기반 XML로 변환한다.

<screen>
    <title>Zero Hour</title>
    <field label="Artist">Astor Piazzola</field>
    <table>
        <row>
            <cell>Tanguedia III</cell>
            <cell>4:39</cell>
        </row>
        <row>
            <cell>Milonga del Angel</cell>
            <cell>6:30</cell>
        </row>
        <row>
            <cell>Concierto Para Quinteto</cell>
            <cell>9:00</cell>
        </row>
        <row>
            <cell>Milonga Loca</cell>
            <cell>3:05</cell>
        </row>
        <row>
            <cell>Michelangelo '70</cell>
            <cell>2:50</cell>
        </row>
        <row>
            <cell>Contrabajisimo</cell>
            <cell>10:18</cell>
        </row>
        <row>
            <cell>Mumuki</cell>
            <cell>9:32</cell>
        </row>
    </table>
</screen>

이 변환은 다음의 XSLT 코드로 한다.

<xsl:template match="album">
    <screen>
        <xsl:apply-templates/>
    </screen>
</xsl:template>
<xsl:template match="album/title">
    <title>
        <xsl:apply-templates/>
    </title>
</xsl:template>
<xsl:template match="artist">
    <field label="Artist">
        <xsl:apply-templates/>
    </field>
</xsl:template>
<xsl:template match="trackList">
    <table>
        <xsl:apply-templates/>
    </table>
</xsl:template>
<xsl:template match="track">
    <row>
        <xsl:apply-templates/>
    </row>
</xsl:template>
<xsl:template match="track/title">
    <cell>
        <xsl:apply-templates/>
    </cell>
</xsl:template>
<xsl:template match="track/time">
    <cell>
        <xsl:apply-templates/>
    </cell>
</xsl:template>

화면 기반 XML은 평범하므로, HTML로 변환하기 위해 두 번재 단계 XSLT를 적용한다.

<xsl:template match="screen">
    <HTML>
        <BODY bgcolor="white">
            <xsl:apply-templates/>
        </BODY>
    </HTML>
</xsl:template>
<xsl:template match="title">
    <h1>
        <xsl:apply-templates/>
    </h1>
</xsl:template>
<xsl:template match="field">
    <P>
        <B>
            <xsl:value-of select = "@label"/>: 
        </B>
        <xsl:apply-templates/>
    </P>
</xsl:template>
<xsl:template match="table">
    <table>
        <xsl:apply-templates/>
    </table>
</xsl:template>
<xsl:template match="table/row">
    <xsl:variable name="bgcolor">
        <xsl:choose>
            <xsl:when test="(position() mod 2) = 1">linen</xsl:when>
            <xsl:otherwise>white</xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <tr bgcolor="{$bgcolor}">
        <xsl:apply-templates/>
    </tr>
</xsl:template>
<xsl:template match="table/row/cell">
    <td>
        <xsl:apply-templates/>
    </td>
</xsl:template>

2단계를 조립하는 단계에서 프런트 컨트롤러를 사용한다.

class AlbumCommand {
    public void process() {
        try {
            Album album = Album.findNamed(request.getParameter("name"));
            album = Album.findNamed("1234");
            Assert.notNull(album);
            PrintWriter out = response.getWriter();
            XsltProcessor processor = new TwoStepXsltProcessor("album2.xsl", "second.xsl");
            out.print(processor.getTransformation(album.toXmlDocument()));
        } catch (Exception e) {
            throw new ApplicationException(e);
        }
    }
}

변환 뷰와 달리 2단계 뷰는 2단계 XSLT 만 수정하면 행마다 교대로 색을 변경하는 코드를 한 번만 작성하면 된다. 이 방식의 단점은 최종 HTML 화면이 XML에 의해 상당한 제약을 받는다는 점이다.

예제: JSP와 커스텀 태그(자바)

XSLT 방식은 2단계 뷰를 구현하는 개념상 가장 쉬운 방식이지만 다른 방법으로 JSP와 커스텀 태그를 사용하는 예를 알아본다. 두 가지 모두 XSLT보다 다소 불편하고 덜 강력하지만 다른 방법으로 구현할 수 있다는 걸 아는 게 중요하다.

첫 번째 단계로 jsp 페이지 하나와 도우미로 처리하고 두 번째 단계로 커스텀 태그로 처리한다.

<%@ taglib uri="2step.tld" prefix = "2step" %>
<%@ page session="false"%>
<jsp:useBean id="helper" class="actionController.AlbumConHelper"/>
<%helper.init(request, response);%>
<2step:screen>
    <2step:title>
        <jsp:getProperty name = "helper" property = "title"/>
    </2step:title>
    <2step:field label = "Artist">
        <jsp:getProperty name = "helper" property = "artist"/>
    </2step:field>
    <2step:table host = "helper" collection = "trackList" columns = "title, time"/>
</2step:screen>

jsp에 있는 태그는 도우미에서 값을 얻기 위한 빈 조작 태그와 두 번째 단계 태그 뿐이다. 태그는 총 3개가 있다.

두 번째 단계에서 타이틀 관련 태그는 가장 간단하게 구현할 수 있으며 다음과 같다.

class TitleTag {
    public int doStartTag() throws JspException {
        try {
            pageContext.getOut().print("<H1>");
        } catch (IOException e) {
            throw new JspException("unable to print start");
        }
        return EVAL_BODY_INCLUDE;
    }
    public int doEndTag() throws JspException {
        try {
            pageContext.getOut().print("</H1>");
        } catch (IOException e) {
            throw new JspException("unable to print end");
        }
        return EVAL_PAGE;
    }
}

이 태그는 < H1 > 태그의 본문 내용을 둘러싼다. 태그를 지정한 텍스트의 시작과 끝에서 호출되는 후크 메서드를 구현하는 방법으로 작동한다.

조금 더 복잡한 태그인 필드 태그의 경우 속성을 받을 수 있다. 속성은 설정 메서드를 사용해 태그 클래스에 연결된다. 값이 설정되면 이를 출력에 사용할 수 있으며, < P > 태그로 감싸고 < B > 태그로 볼드 처리를 한다.

class FieldTag {
    private String label;
    public void setLabel(String label) {
        this.label = label;
    }
    public int doStartTag() throws JspException {
        try {
            pageContext.getOut().print("<P>" + label + ": <B>");
        } catch (IOException e) {
            throw new JspException("unable to print start");
        }
        return EVAL_BODY_INCLUDE;
    }
    public int doEndTag() throws JspException {
        try {
            pageContext.getOut().print("</B></P>");
        } catch (IOException e) {
            throw new JspException("how are checked exceptions helping me here?");
        }
        return EVAL_PAGE;
    }
}

Table 태그는 jsp 작성자가 테이블에 넣을 열을 선택할 수 있게 해 주고 교대로 다른 색을 사용해 행을 강조 표시한다. 강조 표시에 대한 변경은 시스템 전체에 일괄 적용된다.

Table 태그는 jsp에 설정한 대로 세 개의 속성을 받는다. setCollection(), setHost(), setColumns() 객체에서 프로퍼티를 가져오는 도우미 메서드인 getProperty()를 만든다. doStartTag() 에서 컬렉션 이름을 가져와 순회한 후 테이블의 행을 생성한다.

class TableTag {
    private String collectionName;
    private String hostName;
    private String columns;
    public void setCollection(String collectionName) {
        this.collectionName = collectionName;
    }
    public void setHost(String hostName) {
        this.hostName = hostName;
    }
    public void setColumns(String columns) {
        this.columns = columns;
    }
    private Object getProperty(Object obj, String property) throws JspException {
        try {
            String methodName = "get" + property.substring(0, 1).toUpperCase() +
                property.substring(1);
            Object result = obj.getClass().getMethod(methodName, null).invoke(obj, null);
            return result;
        } catch (Exception e) {
            throw new JspException("Unable to get property " + property + " from " + obj);
        }
    }
    public int doStartTag() throws JspException {
        try {
            JspWriter out = pageContext.getOut();
            out.print("<table>");
            Collection coll = (Collection) getPropertyFromAttribute(hostName,
                collectionName);
            Iterator rows = coll.iterator();
            int rowNumber = 0;
            while (rows.hasNext()) {
                out.print("<tr");
                if ((rowNumber++ % 2) == 0) out.print(" bgcolor = " + HIGHLIGHT_COLOR);
                out.print(">");
                printCells(rows.next());
                out.print("</tr>");
            }
            out.print("</table>");
        } catch (IOException e) {
            throw new JspException("unable to print out");
        }
        return SKIP_BODY;
    }
    private Object getPropertyFromAttribute(String attribute, String property) throws JspException {
        Object hostObject = pageContext.findAttribute(attribute);
        if (hostObject == null)
            throw new JspException("Attribute " + attribute + " not found.");
        return getProperty(hostObject, property);
    }
    public static final String HIGHLIGHT_COLOR = "'linen'";
}

XSLT 구현과 비교해 사이트 레이아웃에 대한 제약이 다소 적다. 이런 특성은 디자인 중심 페이지를 만드는데 도움이 되지만 내부 동작 방식을 이해하지 못하면 부적절하게 사용할 수도 있다.

경우에 따라 제약이 실수를 예방하는 효과도 있다. 이 점은 팀에서 결정을 내릴 대 고려해야 할 이 방식의 장단점이다.

jongfeel commented 5 months ago

2단계 뷰의 XSLT 역시 변환 뷰에서 언급한 에러가 그대로 있다.

따라서 같은 부분만 수정하면 동작한다.

jongfeel commented 5 months ago

HTML로 변환한 코드

<HTML>
   <BODY bgcolor="white">

      <h1>Zero Hour</h1>

      <P><B>Artist: 
                    </B>Astor Piazzola
      </P>

      <table>

         <tr bgcolor="linen">

            <td>Tanguedia III</td>

            <td>4:39</td>

         </tr>

         <tr bgcolor="white">

            <td>Milonga del Angel</td>

            <td>6:30</td>

         </tr>

         <tr bgcolor="linen">

            <td>Concierto Para Quinteto</td>

            <td>9:00</td>

         </tr>

         <tr bgcolor="white">

            <td>Milonga Loca</td>

            <td>3:05</td>

         </tr>

         <tr bgcolor="linen">

            <td>Michelangelo '70</td>

            <td>2:50</td>

         </tr>

         <tr bgcolor="white">

            <td>Contrabajisimo</td>

            <td>10:18</td>

         </tr>

         <tr bgcolor="linen">

            <td>Mumuki</td>

            <td>9:32</td>

         </tr>

      </table>

   </BODY>
</HTML>
jongfeel commented 5 months ago

렌더링된 HTML 페이지

image