AJAX와 XML: 다섯 개의 일반적인 Ajax 패턴

원문출처 : http://www.ibm.com/developerworks/kr/library/x-ajaxxml2/

유용한 Ajax 디자인 패턴들

난이도 : 중급

Jack D Herrington, Senior Software Engineer, Leverage Software Inc.

2007 년 4 월 10 일

Asynchronous JavaScript + XML (Ajax)은 단연 2006년의 화두였고, 2007년에도 이러한 기조를 이어갈 전망입니다. 하지만 이것이 여러분의 애플리케이션에는 어떤 영향을 미칠까요? Ajax 애플리케이션에 사용되는 일반적인 아키텍처 패턴에는 무엇이 있을까요? 이 글에서는 다섯 가지 Ajax 디자인 패턴을 소개합니다.

확실히, Ajax는 모든 사람들이 자신들의 사이트에 채택하고 싶어하는 Web 2.0 용어이다. 하지만, 이것이 진정으로 의미하는 것은 무엇일까? 엔지니어들은 아키텍처 레벨에서 이것을 자신들의 사이트에 어떻게 통합할까? 이 글에서, Ajax의 기초를 설명하고, Ajax 개발에서 베스트 프랙티스로 입증된 몇 가지 Ajax 디자인 패턴을 소개하겠다.

Ajax는 Dynamic HTML (DHTML)과 XMLHTTPRequest 객체를 포함한 일련의 기술들을 포괄하는 기술이다. DHTML은 세 가지 엘리먼트들, Hypertext Markup Language (HTML), JavaScript 코드, Cascading Style Sheets (CSS)의 결합이다. 웹 페이지에서 JavaScript 코드를 사용하면서, 콘텐트를 추가, 제거, 수정하기 위해 페이지를 동적으로 수정할 수 있다. 바로 이것이 DHTML의 동적인 요소이다. JavaScript 코드는 XMLHTTPRequest 객체를 사용하여 페이지가 로딩된 후에 서버에서 데이터를 요청한다.

서버에서 데이터를 요청하는 것과, 그 데이터를 사용하기 위해 페이지를 수정하는 것, 이 두 가지 요소들의 결합이 Ajax의 정수이자 Web 2.0 사이트의 동적인 본성이다.

하지만, 이러한 기능들이 실제 세계에서 어떻게 사용되고, 사이트에서 이들을 어떻게 사용하는지에 대해서는 정확히 알 수 있다. 따라서 간단한 디자인 패턴(design patterns.)이 필요하다. 이 용어가 생소하다면, 같은 이름의 책을 참조하기 바란다. (참고자료) 이 책에서는 엔지니어들이 접하는 공통의 태스크에 대한 구현 패턴을 설명하고 있다. 이 책에서는 시스템을 디자인하는 방법에 대한 베스트 프랙티스 뿐만 아니라, 엔지니어들이 사용할 수 있는 용어도 설명한다.

이 글에서는 다섯 가지 일반적인 Ajax 디자인 패턴을 소개한다. HTML, XML, JavaScript 코드를 사용하여 서버에서 데이터를 얻는 방법이 다양하다. 서버에서 새로운 HTML로 페이지를 업데이트 하는 가장 단순한 패턴부터 시작하겠다.

Pattern 1. HTML 세그먼트 대체하기

아마도 가장 일반적인 Ajax 태스크는 서버에서 업데이트 된 HTML을 요청하여, 이것을 사용하여 페이지의 한 부분을 업데이트 하는 것이다. 예를 들어, 주식 시세를 업데이트 하기 위해 이를 주기적으로 수행할 수 있다. 또는, 검색 요청에 대한 응답으로, 온 디맨드(on demand) 방식으로 업데이트 할 수 있다.

Listing 1의 코드는 서버에서 페이지를 요청한 다음 콘텐트를 페이지의 바디에 있는 <div> 태그에 배치하는 부분이다.


Listing 1. Pat1_replace_div.html
				
<html>
<script>
var req = null;
function processReqChange() {
  if (req.readyState == 4 && req.status == 200 ) {
    var dobj = document.getElementById( 'htmlDiv' );
    dobj.innerHTML = req.responseText;
  }
}

function loadUrl( url ) {
  if(window.XMLHttpRequest) {
    try { req = new XMLHttpRequest();
    } catch(e) { req = false; }
  } else if(window.ActiveXObject) {
    try { req = new ActiveXObject('Msxml2.XMLHTTP');
    } catch(e) {
    try { req = new ActiveXObject('Microsoft.XMLHTTP');
    } catch(e) { req = false; }
  } }
  if(req) {
    req.onreadystatechange = processReqChange;
    req.open('GET', url, true);
    req.send('');
  }
}

var url = window.location.toString();
url = url.replace( /pat1_replace_div.html/, 'pat1_content.html' );
loadUrl( url );
</script>
<body>
Dynamic content is shown between here:<br/>
<div id="htmlDiv" style="border:1px solid black;padding:10px;">
</div>
And here.<br/>
</body>
</html>

Listing 2는 코드가 요청하고 있는 콘텐트이다.


Listing 2. Pat1_content.html
				
HTML encoded content goes here.

실제 환경에서는 어떻게 보이는지 알고 싶다면 pat1_replace_div.html의 온라인 버전을 참조하라.pat1_replace_div.html 새 창으로 보기 링크

이 페이지를 Firefox에 로딩할 때, 그림 1과 같은 결과를 볼 수 있다.


그림 1. <div> 태그가 있는 페이지
태그 페이지

Listing 1의 코드로 가서 몇 가지를 살펴보자. 첫 번째로 알아야 할 것은 loadUrl() 함수인데, 이것은 서버에서 URL을 요청한다. 이 함수는 XMLHTTPRequest 객체를 사용하여 서버에 새로운 콘텐트를 요청한다. 또한, 브라우저가 콘텐트를 받을 때 호출되는 콜백 함수(이 경우, processReqChange)도 지정한다.

processReqChange 함수는 객체를 검사하여 요청이 완료되었는지 여부를 확인한다. 완료되었다면, 이 함수는 페이지의 <div> 태그의 innerHTML을 응답 텍스트에 설치한다.

동적 콘텐트용 플레이스홀더로서 <div> 태그를 사용하는 것은 Ajax 코드의 주요한 특징이다. 이 태그는 (여러분이 보더 같은 것을 추가하지 않는 한) 눈에 띠는 특징은 없지만 콘텐트가 어디로 가야 할 지를 알려주는 좋은 마커로서 작동한다. 엔지니어들은 대체 가능한 세그먼트에 <span> 태그를 사용한다. 나중에 설명하겠다. <div><span> 태그의 차이점은 전자가 라인 브레이크(패러그래프)를 사용하는 반면, 후자는 인라인 텍스트의 섹션을 정확하게 구분한다는 점이다.

잠시, processReqChange 함수로 가서, 함수가 statusreadyState 값을 체크하는 것이 중요하다. 일부 브라우저는 요청이 완료될 때에만 함수를 호출하지만, 다른 브라우저는 요청이 실행 중이라는 것을 나타내면서 지속적으로 콜백한다.

Tab 디스플레이 변수

이 패턴의 또 다른 변종은 Tab 스타일의 디스플레이를 생성하는 것이다. Listing 3은 Tab Ajax 인터페이스이다.


Listing 3. Pat1_tabs.html
				
<html>
<script>
var req = null;
function processReqChange() {
  if (req.readyState == 4 && req.status == 200 ) {
    var dobj = document.getElementById( 'tabDiv' );
    dobj.innerHTML = req.responseText;
  }
}

function loadUrl( tab ) {
  var url = window.location.toString();
  url = url.replace( /pat1_tabs.html/, tab );
  ...
}

function tab1() { loadUrl( 'pat1_tab1_content.html' ); }
function tab2() { loadUrl( 'pat1_tab2_content.html' ); }
tab1();
</script>
<body>
<a href="javascript: void tab1();">Tab 1<a> 
<a href="javascript: void tab2();">Tab 2<a>
<div id="tabDiv" style="border:1px solid black;padding:10px;">
</div>
</body>
</html>

Listing 4는 첫 번째 탭의 콘텐트이다.


Listing 4. Pat1_tab1_content.html
				
Tab 1 content

Listing 5는 두 번째 탭의 콘텐트이다.


Listing 5. Pat1_tab2_content.html
				
Tab 2 content

실제 환경에서는 어떻게 보이는지 알고 싶다면 pat1_tabs.html의 온라인 버전을 참조하라.pat1_tabs.html 새 창으로 보기 링크

이 페이지를 브라우저에 로딩하면, 그림 2와 같은 첫 번째 탭을 볼 수 있다.


그림 2. 첫 번째 탭의 콘텐트
첫 번째 탭의 콘텐트

두 번째 탭 링크를 클릭한다. 브라우저는 두 번째 탭의 콘텐트를 가져와서, 이를 탭 영역에 보여준다. (그림 3)


그림 3. 두 번째 탭의 콘텐트
두번째 탭의 콘텐트

사용자에게서 요청을 가져와서 새로운 내용으로 디스플레이의 일부를 업데이트 하는 것, 이 경우에는 Tab 디스플레이를 만드는 것이야 말로 이 디자인 패턴의 전형적인 사용법이다. 애플리케이션 측에서 얻을 수 있는 가치는 사용자에게 훨씬 가벼운 페이지를 제공할 수 있고, 사용자는 자신들이 원하는 자료들에 온 디맨드 방식으로 액세스 할 수 있다.

Ajax 이전의 일반적인 기술은 페이지에 두 개의 탭을 두고, 온 디맨드 방식으로 이들을 숨기거나 보여주는 것이었다. 다시 말해서, 두 번째 탭용 HTML은 결코 보이지 않더라도 생성되었으며, 서버 시간과 대역폭의 낭비만 초래했다. 이제 새로운 Ajax 방식으로, 두 번째 탭용 HTML은 사용자가 요청할 때에만 생성된다.

Read more 변형

실제 환경에서는 어떻게 보이는지 알고 싶다면 의 온라인 버전을 참조하라. pat1_readmore.html pat1_readmore.html 새 창으로 보기 링크

또 하나의 예로 Read more 링크를 들 수 있다. (그림 4)


그림 4. 내 지루한 블로그 엔트리의 Read more 링크
Read more 링크

강아지 산책에 대해 더 읽고 싶다고 가정해 보자. Read more 링크를 클릭하고 그 링크를 전체 스토리로 대체할 수 있다. (그림 5)


그림 5. Read more 링크를 클릭한 후의 페이지 모습
Read more 클릭 후

사용자들은 페이지 리프레시 없이도 더 많은 자료들을 볼 수 있다.

Listing 6은 이 페이지에 대한 코드이다.


Listing 6. Pat1_readmore.html
				
<html>
<script>
var req = null;
function processReqChange() {
  if (req.readyState == 4 && req.status == 200 ) {
    var dobj = document.getElementById( "moreSpan" );
    dobj.innerHTML = req.responseText;
  }
}

function loadUrl( url ) { ... }

function getMore()
{
  var url = window.location.toString();
  url = url.replace( /pat1_readmore.html/, 'pat1_readmore_content.html' );
  loadUrl( url );
}
</script>
<body>
<h1>Walking the dog</h1>
I took my dog for a walk today. 
<span id="moreSpan">
<a href="javascript: void getMore()">Read more...</a>
</span>
</body>
</html>

Listing 7은 "read more" 섹션에 대한 콘텐트이다.


Listing 7. Pat1_readmore_content.html
				
It was a nice day out. Warm and sunny. My dog liked getting out for a stretch.

이 코드는 <div> 태그 대신 <span> 태그의 사용법을 보여준다. 사용 방법은 사용자 인터페이스(UI)의 요구 사항에 따라 다르다. 여러분도 보다시피 어떤 방법이든 사용하기 쉽다.

이 페이지에 새로운 HTML을 만드는 것은 해결되었고, JavaScript가 데이터를 사용하여 보다 지능적인 작업을 수행하도록 하려면? 구조적인(structured) 방법으로 데이터를 브라우저에 어떻게 가져올 것인가? 당연히, XML이 필요하다.






Pattern 2. XML 데이터 읽기

몇 가지 이유로, Ajax는 XML의 동의어처럼 되어버렸다. XML은 절대로 그렇지 않은데 말이다. 위 예제에서 보듯, 텍스트 또는 HTML 조각 또는 Extensible HTML (XHTML) 코드를 리턴할 수 있다. 하지만 XML을 보내도 응답을 받을 수 있다.

Listing 8은 서버에서 책에 대한 기록을 요청하고, 페이지 내의 테이블에 그 데이터를 디스플레이 하는 Ajax 코드이다.


Listing 8. Pat2_xml.html
				
<html>
<head>
<script>
var req = null;
function processReqChange() {
  if (req.readyState == 4 && req.status == 200 && req.responseXML ) {
    var dtable = document.getElementById( 'dataBody' );
    var nl = req.responseXML.getElementsByTagName( 'book' );
    for( var i = 0; i < nl.length; i++ ) {
      var nli = nl.item( i );
      var elAuthor = nli.getElementsByTagName( 'author' );
      var author = elAuthor.item(0).firstChild.nodeValue;
      var elTitle = nli.getElementsByTagName( 'title' );
      var title = elTitle.item(0).firstChild.nodeValue;

      var elTr = dtable.insertRow( -1 );

      var elAuthorTd = elTr.insertCell( -1 );
      elAuthorTd.innerHTML = author;

      var elTitleTd = elTr.insertCell( -1 );
      elTitleTd.innerHTML = title;
} } }

function loadXMLDoc( url ) {
  if(window.XMLHttpRequest) {
    try { req = new XMLHttpRequest();
    } catch(e) { req = false; }
  } else if(window.ActiveXObject) {
    try { req = new ActiveXObject('Msxml2.XMLHTTP');
    } catch(e) {
    try { req = new ActiveXObject('Microsoft.XMLHTTP');
    } catch(e) { req = false; }
  } }
  if(req) {
    req.onreadystatechange = processReqChange;
    req.open('GET', url, true);
    req.send('');
  }
}

var url = window.location.toString();
url = url.replace( /pat2_xml.html/, 'pat2_xml_data.xml' );
loadXMLDoc( url );
</script>
</head>
<body>
<table cellspacing="0" cellpadding="3" width="100%">
<tbody id="dataBody">
<tr>
  <th width="20%">Author</th>
  <th width="80%">Title</th>
</tr>
</tbody>
</table>
</body>
</html>

Listing 9는 이 페이지의 데이터이다.


Listing 9. Pat2_xml_data.xml
				
<books>
  <book>
    <author>Jack Herrington</author>
    <title>Code Generation in Action</title>
  </book>
  <book>
    <author>Jack Herrington</author>
    <title>Podcasting Hacks</title>
  </book>
  <book>
    <author>Jack Herrington</author>
    <title>PHP Hacks</title>
  </book>
</books>

실제 환경에서는 어떻게 보이는지 알고 싶다면 pat2_xml.html의 온라인 버전을 참조하라.pat2_xml.html 새 창으로 보기 링크

이 페이지를 브라우저에 로딩하면, 그림 6과 같은 결과를 볼 수 있다.


그림 6. XML 디스플레이 페이지
XML 디스플레이 페이지

이 페이지와 이전 패턴의 페이지와의 큰 차이는 processReqChange 함수에 있다. responseText를 보는 대신, 이제는 서버에서 온 응답이 XML로 올바르게 인코딩 될 경우에만 사용할 수 있는 XML Document Object Model (DOM)인 responseXML을 본다.

responseXML을 사용하여, XML 문서에서 <book> 태그들의 리스트를 요청한다. 이들 각각에 대해 <title><author> 엘리먼트를 받는다. 그런 다음, 각 책의 테이블에 행을 추가하고, 각 행에 셀을 추가하여 저자와 제목 데이터를 포함시킨다.

이는 전형적인 XML 데이터의 사용법이다. 보다 고급의 JavaScript 코드는 리턴된 데이터에 기반하여 클라이언트 측 소팅(sorting) 또는 검색을 수행한다.

안타깝게도, XML 데이터를 전송할 때의 단점은 브라우저가 XML 문서를 파싱하는데 시간이 걸린다는 점이다. 또한, XML에서 데이터를 찾는 JavaScript 코드는 복잡해 질 수 있다. (Listing 8) 대안은 서버에서 JavaScript 코드를 요청하는 것이다.






Pattern 3. JavaScript 데이터 읽기

서버에서 JavaScript 데이터를 요청하는 것은 JavaScript Object Notation (JSON)으로 통용되는 기술이다. JavaScript 데이터를 리턴하면 브라우저가 효율적으로 JavaScript 데이터 구조를 파싱하고 생성할 수 있고, 사용이 훨씬 쉬워진다.

서버에서 XML 데이터를 읽는 Listing 8의 코드를 서버에서 JavaScript 데이터를 읽는 것으로 수정했다. 새로운 코드는 Listing 10과 같다.


Listing 10. Pat3_js.html
				
<html><head><script>
var req = null;
function processReqChange() {
  if (req.readyState == 4 && req.status == 200 ) {
    var dtable = document.getElementById( 'dataBody' );
    var books = eval( req.responseText );
    for( var b in books ) {
      var elTr = dtable.insertRow( -1 );

      var elAuthorTd = elTr.insertCell( -1 );
      elAuthorTd.innerHTML = books[b].author;

      var elTitleTd = elTr.insertCell( -1 );
      elTitleTd.innerHTML = books[b].title;
} } }

...

모든 HTML 코드는 같다. processReqChange 함수는 JavaScript 데이터가 서버에서 리턴했던 eval을 읽기 위해 변경된다. 이 함수는 eval에서 나온 JavaScript 객체들을 데이터 소스로서 사용하고, 이것은 테이블에 추가된다.

Listing 11은 서버에서 온 JavaScript 데이터이다.


Listing 11. Pat3_js_data.js
				
[ { author: 'Jack Herrington', title: 'Code Generation in Action' },
{ author: 'Jack Herrington', title: 'Podcasting Hacks' },
{ author: 'Jack Herrington', title: 'PHP Hacks' }
]

실제 환경에서는 어떻게 보이는지 알고 싶다면 pat3_js.html의 온라인 버전을 참조하라. pat3_js.html 새 창으로 보기 링크

왜 많은 Ajax 애플리케이션 엔지니어들이 데이터를 인코딩할 때 XML 대신 JavaScript를 선호하는지를 쉽게 알 수 있다. JavaScript 코드는 읽기와 관리하기가 더 쉽고 브라우저가 처리하기에도 더 쉽다.

데이터 수집과 디스플레이도 중요하지만, Ajax의 핵심은 현재 데이터의 디스플레이이다. 여기에서는 현재(current.)가 중요하다. 여러분은 서버에서 늘 새로운 데이터를 가져오고 있다고 어떻게 확신할 수 있는가?






Pattern 4. 브라우저 캐시 피하기

브라우저는 웹 트래픽을 최적화하려고 하므로, 같은 URL을 두 번 요청하면, 페이지를 다시 요청하는 대신, 브라우저 캐시에 저장된 페이지를 사용하게 될 것이다. 따라서, Ajax 애플리케이션의 또 다른 패턴은 URL에서 무작위 엘리먼트를 사용하여 그 브라우저가 캐싱된 결과를 리턴하지 않도록 하는 것이다.

내가 좋아하는 방법은 현재 시간의 숫자 값을 URL에 추가하는 것이다. Listing 12는 그 방법이다.


Listing 12. Pat4_cache.html
				
<html>
<script>
...

function loadUrl( url ) {
  url = url + "?t="+((new Date()).valueOf());
  ...
}

...

실제 환경에서는 어떻게 보이는지 알고 싶다면 pat4_cache.html의 온라인 버전을 참조하라.pat4_cache.html 새 창으로 보기 링크

이 것은 Listing 1에서 참조한 코드이지만, URL 스트링에서 JavaScript 텍스트가 추가되었다. 시간 값을 갖고 있는 t라고 하는 새로운 매개변수를 URL에 추가했다. 서버가 그 값을 인지하는지 여부는 상관 없다. 이것은 브라우저가 URL 기반 페이지 캐시를 무시하는지를 확인하는 방법일 뿐이다.






Pattern 5. 여러 HTML 세그먼트 대체하기

마지막 패턴은 첫 번째 패턴의 고급 버전이라고 볼 수 있다. <div> 태그를 서버에서 가져온 콘텐트로 대체하는 것이다. 웹 애플리케이션의 일반적인 문제는 사용자 인풋에 응답할 때, 여러 디스플레이 영역들이 업데이트되어야 한다는 점이다. 예를 들어, 주식 시세 애플리케이션에서, 디스플레이의 한 부분이 최신 시세를 보여주고, 또 다른 부분은 최신 가격 리스트를 보여준다.

디스플레이의 여러 부분들을 업데이트 하기 위해, 두 섹션에 대한 데이터를 포함하고 있는 서버에서 XML 응답을 사용한다. 정규식을 사용하여 응답에서 개별 섹션들을 분리한다. Listing 13은 그 방법이다.


Listing 13. Pat5_multi_segment.html
				
<html>
<head>
<script>
var req = null;
function processReqChange() {
  if (req.readyState == 4 && req.status == 200 ) {
    var one = req.responseText.match( /\<one\>(.*?)\<\/one\>/ );
    document.getElementById( 'divOne' ).innerHTML = one[1];
    var two = req.responseText.match( /\<two\>(.*?)\<\/two\>/ );
    document.getElementById( 'divTwo' ).innerHTML = two[1];
} }

function loadXMLDoc( url ) { ... }

var url = window.location.toString();
url = url.replace( /pat5_multi_segment.html/, 'pat5_data.xml' );
loadXMLDoc( url );
</script>
</head>
<body>

This is the content for segment one:<br/>
<div id="divOne" style="border:1px solid black;padding:10px;">
</div>
And segment two:<br/>
<div id="divTwo" style="border:1px solid black;padding:10px;">
</div>

</body>
</html>

Listing 14는 서버에서 가져온 데이터이다.


Listing 14. Pat5_data.xml
				
<segments>
  <one>Content for segment one</one>
  <two>Content for segment <b>two</b></two>
</segments>

실제 환경에서는 어떻게 보이는지 알고 싶다면 pat5_multi_segment.html의 온라인 버전을 참조하라. pat5_multi_segment.html 새 창으로 보기 링크

이 코드를 브라우저에 로딩하면 그림 7과 같은 결과를 볼 수 있다.


그림 7. 서버에서 온 데이터로 업데이트 된 두 세그먼트
두 세그먼트 디스플레이

페이지 코드에서, 서버에서 리턴된 자료는 유효 XML이기 때문에 XML 응답을 사용할 수도 있었따. 하지만, XML 코드에서 개별 세그먼트를 크래킹 하는 대신 정규식을 사용하는 것이 더 쉬웠다.


결론

Ajax는 강력한 만큼, 오용 및 오해의 여지도 많이 있다. 이 글에서 내가 설명한 패턴들은 웹 애플리케이션에서 Ajax를 사용하는데 좋은 지침이 될 것이다. 이 글에서 제공한 코드를 사용하는 것 외에도, Ajax에서 파생된 Ajax와 Web UI 라이브러리도 사용해 보는 것도 좋다. 이중 가장 중요한 것은 Prototype.js 라이브러리로서, 서버에서 데이터를 쉽게 가져올 수 있으며, 크로스 브라우저 호환 방식을 사용하여 웹 페이지 콘텐트를 업데이트 한다. 이 라이브러리를 사용하면 전담 엔지니어들은 다양한 브라우저와 플랫폼에서 이들을 관리 및 테스트 할 수 있다.

Ajax에는 여러분의 애플리케이션에 동적인 작동을 추가할 수 있는 분명 무엇인가가 있다.

기사의 원문보기

필자소개

Jack D. Herrington은 20년 경력을 지닌 소프트웨어 엔지니어이다. Code Generation in Action, Podcasting Hacks, PHP Hacks 의 저자이다. 30개 이상의 기술자료를 기고했다. (jherr@pobox.com)

2007/05/17 23:10 2007/05/17 23:10
Trackback Address:이 글에는 트랙백을 보낼 수 없습니다