디온 알마에르, 파멜라 폭스(Google)
2007년 6월
편집자 주: Google Gears API는 더 이상 사용할 수 없습니다.
소개
Google Base와 Google Gears를 결합하여 오프라인으로 사용할 수 있는 애플리케이션을 만드는 방법을 보여줍니다. 이 도움말을 읽고 나면 Google Base API에 대해 더 잘 알게 되고 Google Gears를 사용하여 사용자 환경설정 및 데이터를 저장하고 액세스하는 방법을 이해할 수 있습니다.
앱 이해
이 앱을 이해하려면 먼저 Google Base에 대해 잘 알아야 합니다. Google Base는 기본적으로 제품, 리뷰, 레시피, 이벤트 등 다양한 카테고리에 걸친 항목의 대규모 데이터베이스입니다.
각 항목에는 제목, 설명, 데이터의 원본 링크 (있는 경우)와 카테고리 유형에 따라 달라지는 추가 속성이 주석으로 표시됩니다. Google Base는 동일한 카테고리의 상품이 공통 속성 집합을 공유한다는 사실을 활용합니다. 예를 들어 모든 레시피에는 재료가 있습니다. Google Base 항목은 Google 웹 검색 또는 Google 제품 검색의 검색 결과에 가끔 표시되기도 합니다.
데모 앱인 Base with Gears를 사용하면 Google Base에서 실행할 수 있는 일반적인 검색(예: '초콜릿'이 포함된 레시피 찾기(맛있음) 또는 '해변 산책'이 포함된 개인 광고 찾기(로맨틱함))을 저장하고 표시할 수 있습니다. 검색을 구독하고 앱을 다시 방문하거나 앱이 15분마다 업데이트된 피드를 찾을 때 업데이트된 결과를 볼 수 있는 'Google Base 리더'라고 생각하면 됩니다.
앱을 확장하려는 개발자는 검색 결과에 새로운 결과가 포함된 경우 사용자에게 시각적으로 알리고, 사용자가 즐겨찾는 항목 (오프라인 + 온라인)을 북마크 (별표)로 지정하고, 사용자가 Google Base와 같은 카테고리별 속성 검색을 할 수 있도록 하는 등의 기능을 추가할 수 있습니다.
Google Base 데이터 API 피드 사용
Google Base는 Google 데이터 API 프레임워크를 준수하는 Google Base 데이터 API를 사용하여 프로그래매틱 방식으로 쿼리할 수 있습니다. Google Data API 프로토콜은 웹에서 읽고 쓰는 간단한 프로토콜을 제공하며 Picasa, 스프레드시트, 블로거, Calendar, Notebook 등 다양한 Google 제품에서 사용됩니다.
Google Data API 형식은 XML 및 Atom 게시 프로토콜을 기반으로 하므로 대부분의 읽기/쓰기 상호작용은 XML로 이루어집니다.
Google 데이터 API를 기반으로 하는 Google Base 피드의 예는 다음과 같습니다.
http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera
snippets
피드 유형은 공개적으로 제공되는 상품 피드를 제공합니다. -/products
를 사용하면 피드를 제품 카테고리로 제한할 수 있습니다. bq=
매개변수를 사용하면 '디지털 카메라' 키워드가 포함된 결과만 표시되도록 피드를 추가로 제한할 수 있습니다. 브라우저에서 이 피드를 보면 일치하는 결과가 있는 <entry>
노드가 포함된 XML이 표시됩니다. 각 항목에는 일반적인 작성자, 제목, 콘텐츠, 링크 요소가 포함되어 있지만 제품 카테고리의 항목에 대한 '가격'과 같은 추가 카테고리별 속성도 함께 제공됩니다.
브라우저의 XMLHttpRequest 교차 도메인 제한으로 인해 JavaScript 코드에서 www.google.com의 XML 피드를 직접 읽을 수 없습니다. XML을 읽고 앱과 동일한 도메인의 위치에 다시 출력하는 서버 측 프록시를 설정할 수 있지만 서버 측 프로그래밍은 완전히 피하고 싶습니다. 다행히도 대안이 있습니다.
다른 Google Data API와 마찬가지로 Google Base 데이터 API에는 표준 XML 외에 JSON 출력 옵션이 있습니다. 앞서 살펴본 피드의 JSON 형식 출력은 다음 URL에 있습니다.
http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera&alt=json
JSON은 계층적 중첩과 다양한 데이터 유형을 허용하는 경량 교환 형식입니다. 하지만 더 중요한 점은 JSON 출력이 기본 JavaScript 코드 자체이므로 스크립트 태그에서 참조하여 교차 도메인 제한을 우회하여 웹페이지에 로드할 수 있다는 것입니다.
Google Data API를 사용하면 JSON이 로드된 후 실행할 콜백 함수와 함께 'json-in-script' 출력을 지정할 수도 있습니다. 이렇게 하면 페이지에 스크립트 태그를 동적으로 추가하고 각 태그에 대해 서로 다른 콜백 함수를 지정할 수 있으므로 JSON 출력을 훨씬 쉽게 사용할 수 있습니다.
따라서 Base API JSON 피드를 페이지에 동적으로 로드하려면 피드 URL (alt
callback
값이 추가됨)로 스크립트 태그를 만들고 페이지에 추가하는 다음 함수를 사용할 수 있습니다.
function getJSON() { var script = document.createElement('script'); var url = "http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera"; script.setAttribute('src', url + "&alt=json-in-script&callback=listResults"); script.setAttribute('type', 'text/JavaScript'); document.documentElement.firstChild.appendChild(script); }
이제 콜백 함수 listResults
가 유일한 매개변수로 전달된 JSON을 반복하고 글머리 기호 목록에 있는 각 항목의 정보를 표시할 수 있습니다.
function listTasks(root) { var feed = root.feed; var html = ['']; html.push('<ul>'); for (var i = 0; i < feed.entry.length; ++i) { var entry = feed.entry[i]; var title = entry.title.$t; var content = entry.content.$t; html.push('<li>', title, ' (', content, ')</li>'); } html.push('</ul>'); document.getElementById("agenda").innerHTML = html.join(""); }
Google Gears 추가
이제 Google Data API를 통해 Google Base와 통신할 수 있는 애플리케이션이 있으므로 이 애플리케이션이 오프라인으로 실행되도록 설정하려고 합니다. 이때 Google Gears가 사용됩니다.
오프라인으로 전환할 수 있는 애플리케이션을 작성할 때는 다양한 아키텍처를 선택할 수 있습니다. 온라인과 오프라인에서 애플리케이션이 어떻게 작동해야 하는지 (예: 정확히 동일하게 작동하는가? 검색과 같은 일부 기능이 사용 중지되어 있나요? 동기화는 어떻게 처리할 건가요?)
Google에서는 Gears가 없는 브라우저의 사용자가 앱을 계속 사용할 수 있도록 하면서 플러그인이 있는 사용자에게는 오프라인 사용과 더 반응성이 높은 UI의 이점을 제공하고자 했습니다.
아키텍처는 다음과 같습니다.
- Google에는 검색어를 저장하고 이러한 검색어의 결과를 다시 반환하는 JavaScript 객체가 있습니다.
- Google Gears가 설치되어 있으면 모든 항목을 로컬 데이터베이스에 저장하는 Gears 버전이 제공됩니다.
- Google Gears가 설치되어 있지 않으면 쿠키에 쿼리를 저장하고 전체 결과를 전혀 저장하지 않는 버전이 표시됩니다. 결과가 너무 커서 쿠키에 저장할 수 없기 때문에 응답 속도가 약간 느립니다.
if (online) {}
를 전체 매장에서 확인할 필요가 없다는 것입니다. 대신 애플리케이션에는 하나의 Gears 확인이 있으며 올바른 어댑터가 사용됩니다.
Gears 로컬 데이터베이스 사용
Gears의 구성요소 중 하나는 삽입되어 바로 사용할 수 있는 로컬 SQLite 데이터베이스입니다. 이전 서버 측 데이터베이스(예: MySQL 또는 Oracle)용 API를 사용한 적이 있다면 친숙하게 느껴질 간단한 데이터베이스 API가 있습니다.
로컬 데이터베이스를 사용하는 단계는 매우 간단합니다.
- Google Gears 객체 초기화
- 데이터베이스 팩토리 객체를 가져오고 데이터베이스를 엽니다.
- SQL 요청 실행 시작
이러한 내용을 간단히 살펴보겠습니다.
Google Gears 객체 초기화
애플리케이션은 /gears/samples/gears_init.js
의 콘텐츠를 직접 읽거나 코드를 자체 JavaScript 파일에 붙여넣어야 합니다. <script src="..../gears_init.js" type="text/JavaScript"></script>
가 실행되면 google.gears 네임스페이스에 액세스할 수 있습니다.
데이터베이스 팩토리 객체 가져오기 및 데이터베이스 열기
var db = google.gears.factory.create('beta.database', '1.0'); db.open('testdb');
이 호출 한 번으로 데이터베이스 스키마를 열 수 있는 데이터베이스 객체를 얻을 수 있습니다. 데이터베이스를 열면 동일한 출처 정책 규칙을 통해 범위가 지정되므로 내 'testdb'가 내 'testdb'와 충돌하지 않습니다.
SQL 요청 실행 시작
이제 데이터베이스에 SQL 요청을 보낼 준비가 되었습니다. 'select' 요청을 보내면 원하는 데이터를 반복할 수 있는 결과 세트가 반환됩니다.
var rs = db.execute('select * from foo where name = ?', [ name ]);
다음 메서드를 사용하여 반환된 결과 집합을 조작할 수 있습니다.
boolean | isValidRow() |
void | next() |
void | close() |
int | fieldCount() |
string | fieldName(int fieldIndex) |
variant | field(int fieldIndex) |
variant | fieldByName(string fieldname) |
자세한 내용은 데이터베이스 모듈 API 문서를 참고하세요. (편집자 참고: Google Gears API는 더 이상 사용할 수 없습니다).
GearsDB를 사용하여 하위 수준 API 캡슐화
일반적인 데이터베이스 작업을 캡슐화하여 더 편리하게 만들고 싶었습니다. 예를 들면 다음과 같습니다.
- 애플리케이션을 디버깅할 때 생성된 SQL을 로깅하는 좋은 방법을 원했습니다.
- 여기저기에서
try{}catch(){}
을 수행하는 대신 한곳에서 예외를 처리하고 싶었습니다. - 데이터를 읽거나 쓸 때 결과 세트 대신 JavaScript 객체를 처리하고 싶었습니다.
이러한 문제를 일반적인 방식으로 처리하기 위해 데이터베이스 객체를 래핑하는 오픈소스 라이브러리인 GearsDB를 만들었습니다. 이제 GearsDB를 사용하는 방법을 보여드리겠습니다.
초기 설정
window.onload 코드에서 사용하는 데이터베이스 테이블이 있는지 확인해야 합니다. 다음 코드가 실행될 때 사용자가 Gears를 설치한 경우 GearsBaseContent
객체가 생성됩니다.
content = hasGears() ? new GearsBaseContent() : new CookieBaseContent();
다음으로 데이터베이스를 열고 테이블이 아직 없으면 테이블을 만듭니다.
db = new GearsDB('gears-base'); // db is defined as a global for reuse later! if (db) { db.run('create table if not exists BaseQueries' + ' (Phrase varchar(255), Itemtype varchar(100))'); db.run('create table if not exists BaseFeeds' + ' (id varchar(255), JSON text)'); }
이제 쿼리와 피드를 저장할 테이블이 있습니다. new GearsDB(name)
코드는 지정된 이름으로 데이터베이스를 여는 작업을 캡슐화합니다. run
메서드는 하위 수준 execute
메서드를 래핑하지만 콘솔에 대한 디버깅 출력과 예외 포착도 처리합니다.
검색어 추가
앱을 처음 실행하면 검색 내역이 없습니다. 제품에서 Nintendo Wii를 검색하면 이 검색어가 BaseQueries 테이블에 저장됩니다.
addQuery
메서드의 Gears 버전은 입력을 가져와 insertRow
를 통해 저장하여 이를 실행합니다.
var searchterm = { Phrase: phrase, Itemtype: itemtype }; db.insertRow('BaseQueries', searchterm);
insertRow
는 JavaScript 객체 (searchterm
)를 가져와서 테이블에 삽입하는 작업을 처리합니다. 또한 제약 조건 (예: 'Bob'을 두 개 이상 삽입하는 것을 차단)을 정의할 수 있습니다. 하지만 대부분의 경우 데이터베이스 자체에서 이러한 제약 조건을 처리합니다.
모든 검색어 가져오기
이전 검색 목록을 채우기 위해 selectAll
라는 멋진 선택 래퍼를 사용합니다.
GearsBaseContent.prototype.getQueries = function() { return this.db.selectAll('select * from BaseQueries'); }
그러면 데이터베이스의 행과 일치하는 JavaScript 객체의 배열 (예: [ { Phrase: 'Nintendo Wii', Itemtype: 'product' }, { ... }, ...]
)이 반환됩니다.
이 경우 전체 목록을 반환해도 괜찮습니다. 하지만 데이터가 많은 경우 반환된 각 행이 수신될 때 작업을 수행할 수 있도록 select 호출에서 콜백을 사용하는 것이 좋습니다.
db.selectAll('select * from BaseQueries where Itemtype = ?', ['product'], function(row) { ... do something with this row ... });
다음은 GearsDB의 유용한 선택 메서드입니다.
selectOne(sql, args) | 일치하는 첫 번째/하나의 JavaScript 객체 반환 |
selectRow(table, where, args, select) | 일반적으로 SQL을 무시하는 간단한 경우에 사용됩니다. |
selectRows(table, where, args, callback, select) | selectRow와 동일하지만 여러 결과를 대상으로 합니다. |
피드 로드
Google Base에서 결과 피드를 가져오면 데이터베이스에 저장해야 합니다.
content.setFeed({ id: id, JSON: json.toJSONString() }); ... which calls ... GearsBaseContent.prototype.setFeed = function(feed) { this.db.forceRow('BaseFeeds', feed); }
먼저 JSON 피드를 가져와 toJSONString
메서드를 사용하여 문자열로 반환합니다. 그런 다음 feed
객체를 만들어 forceRow
메서드에 전달합니다. forceRow
는 항목이 아직 없는 경우 항목을 삽입하고 기존 항목을 업데이트합니다.
검색 결과 표시
앱은 페이지의 오른쪽 패널에 특정 검색의 결과를 표시합니다. 검색어와 연결된 피드를 가져오는 방법은 다음과 같습니다.
GearsBaseContent.prototype.getFeed = function(url) { var row = this.db.selectRow('BaseFeeds', 'id = ?', [ url ]); return row.JSON; }
이제 행의 JSON이 있으므로 이를 eval()
하여 객체를 다시 가져올 수 있습니다.
eval("var json = " + jsonString + ";");
이제 JSON에서 콘텐츠를 페이지로 innerHTML할 수 있습니다.
오프라인 액세스를 위한 리소스 스토어 사용
로컬 데이터베이스에서 콘텐츠를 가져오므로 이 앱은 오프라인에서도 작동해야 합니다.
아니요. 이 앱을 시작하려면 JavaScript, CSS, HTML, 이미지와 같은 웹 리소스를 로드해야 합니다. 현재 사용자가 다음 단계를 수행한 경우 앱이 계속 작동할 수 있습니다. 온라인으로 시작하고, 검색을 몇 번 실행하고, 브라우저를 닫지 않고 오프라인으로 전환합니다. 항목이 브라우저의 캐시에 계속 저장되어 있으므로 이 방법이 작동할 수 있습니다. 하지만 그렇지 않은 경우에는 어떻게 해야 할까요? 사용자가 재부팅 후 처음부터 앱에 액세스할 수 있어야 합니다.
이를 위해 LocalServer 구성요소를 사용하고 리소스를 캡처합니다. 애플리케이션을 실행하는 데 필요한 HTML 및 JavaScript와 같은 리소스를 캡처하면 Gears는 이러한 항목을 저장하고 브라우저에서 이러한 항목을 반환하도록 요청하는 것을 트랩합니다. 로컬 서버는 트래픽 관리자 역할을 하며 저장된 콘텐츠를 스토어에서 반환합니다.
또한 시스템에 캡처할 파일을 수동으로 알려야 하는 ResourceStore 구성요소를 사용합니다. 많은 시나리오에서 애플리케이션을 버전 관리하고 트랜잭션 방식으로 업그레이드를 허용하려고 합니다. 리소스 집합은 함께 버전을 정의하며, 새 리소스 집합을 출시할 때 사용자가 파일을 원활하게 업그레이드할 수 있도록 해야 합니다. 이 모델을 사용하는 경우 ManagedResourceStore API를 사용하게 됩니다.
리소스를 캡처하기 위해 GearsBaseContent 객체는 다음을 실행합니다.
- 캡처해야 하는 파일 배열 설정
- LocalServer 만들기
- ResourceStore를 열거나 새로 만듭니다.
- 호출하여 페이지를 스토어에 캡처
// Step 1 this.storeName = 'gears-base'; this.pageFiles = [ location.pathname, 'gears_base.js', '../scripts/gears_db.js', '../scripts/firebug/firebug.js', '../scripts/firebug/firebug.html', '../scripts/firebug/firebug.css', '../scripts/json_util.js', 'style.css', 'capture.gif' ]; // Step 2 try { this.localServer = google.gears.factory.create('beta.localserver', '1.0'); } catch (e) { alert('Could not create local server: ' + e.message); return; } // Step 3 this.store = this.localServer.openStore(this.storeName) || this.localServer.createStore(this.storeName); // Step 4 this.capturePageFiles(); ... which calls ... GearsBaseContent.prototype.capturePageFiles = function() { this.store.capture(this.pageFiles, function(url, success, captureId) { console.log(url + ' capture ' + (success ? 'succeeded' : 'failed')); }); }
여기서 중요한 점은 자체 도메인의 리소스만 캡처할 수 있다는 것입니다. SVN 트렁크의 원래 'gears_db.js' 파일에서 GearsDB JavaScript 파일에 직접 액세스하려고 할 때 이 제한이 발생했습니다. 물론 해결 방법은 간단합니다. 외부 리소스를 다운로드하여 도메인 아래에 배치하면 됩니다. LocalServer는 200 (성공) 또는 304 (수정되지 않음) 서버 코드만 허용하므로 302 또는 301 리디렉션은 작동하지 않습니다.
여기에는 의미가 있습니다. 이미지를 images.yourdomain.com에 배치하면 이미지를 캡처할 수 없습니다. www1과 www2는 서로를 볼 수 없습니다. 서버 측 프록시를 설정할 수도 있지만, 이렇게 하면 애플리케이션을 여러 도메인으로 분할하는 목적이 무의미해집니다.
오프라인 애플리케이션 디버깅
오프라인 애플리케이션 디버깅은 약간 더 복잡합니다. 이제 테스트할 시나리오가 더 많아졌습니다.
- 앱이 캐시에서 완전히 실행되고 있는 상태로 온라인에 접속함
- 온라인 상태이지만 앱에 액세스하지 않았고 캐시에 아무것도 없음
- 오프라인 상태이지만 앱에 액세스했습니다.
- 오프라인 상태이며 앱에 액세스한 적이 없습니다 (좋은 상황이 아님).
작업을 더 쉽게 하기 위해 다음 패턴을 사용했습니다.
- 브라우저가 캐시에서 항목을 선택하지 않도록 해야 하는 경우 Firefox (또는 선택한 브라우저)에서 캐시를 사용 중지합니다.
- Firebug (다른 브라우저에서 테스트할 때는 Firebug Lite)를 사용하여 디버그하고,
console.log()
를 여기저기 사용하며, 혹시 모를 상황에 대비해 콘솔을 감지합니다. - 다음 위치에 헬퍼 JavaScript 코드를 추가합니다.
- 데이터베이스를 정리하고 깨끗한 상태로 시작할 수 있습니다.
- 캡처된 파일을 삭제하므로 다시 로드할 때 인터넷에 연결하여 파일을 다시 가져옵니다 (개발을 반복할 때 유용).
디버그 위젯은 Gears가 설치된 경우에만 페이지 왼쪽에 표시됩니다. 코드 정리 콜아웃이 있습니다.
GearsBaseContent.prototype.clearServer = function() { if (this.localServer.openStore(this.storeName)) { this.localServer.removeStore(this.storeName); this.store = null; } } GearsBaseContent.prototype.clearTables = function() { if (this.db) { this.db.run('delete from BaseQueries'); this.db.run('delete from BaseFeeds'); } displayQueries(); }
결론
Google Gears를 사용하는 것은 실제로 매우 간단합니다. GearsDB를 사용하여 데이터베이스 구성요소를 더욱 쉽게 만들었고, 예시에서는 수동 ResourceStore를 사용했는데 잘 작동했습니다.
가장 많은 시간을 할애하는 부분은 데이터를 온라인으로 가져오는 시기와 오프라인으로 저장하는 방법을 위한 전략을 정의하는 것입니다. 데이터베이스 스키마를 정의하는 데 시간을 투자하는 것이 중요합니다. 향후 스키마를 변경해야 하는 경우 현재 사용자가 이미 데이터베이스 버전을 보유하고 있으므로 변경사항을 관리해야 합니다. 즉, 데이터베이스 업그레이드와 함께 스크립트 코드를 제공해야 합니다. 이러한 상황을 최소화하는 것이 좋으며, 수정사항을 관리하는 데 도움이 되는 작은 라이브러리인 GearShift를 사용해 볼 수 있습니다.
ManagedResourceStore를 사용하여 파일을 추적할 수도 있습니다. 이 경우 다음과 같은 결과가 발생합니다.
- 향후 원활한 업그레이드를 위해 파일을 버전 관리합니다.
- ManagedResourceStore에는 URL을 다른 콘텐츠에 별칭으로 지정할 수 있는 기능이 있습니다. 유효한 아키텍처 선택은 gears_base.js가 Gears 버전이 아닌 버전이고 Gears 자체가 모든 오프라인 지원이 있는 gears_base_withgears.js를 다운로드하도록 별칭을 지정하는 것입니다.
애플리케이션 준비가 재미있고 쉬웠기를 바랍니다. 궁금한 점이 있거나 공유할 앱이 있는 경우 Google Gears 포럼에 참여해 주세요.