올해 한국에서 최초로 열린 WWW 2014 컨퍼런스의 Web API 워크숍에 제출한 논문의 국문 초안을 공유합니다. 한국인에겐 역시 한국말이죠. ;-)
발표 슬라이드는 아래와 같습니다.
영문 논문은 아래 링크에서 확인하실 수 있습니다.
http://ws-rest.org/2014/program
웹 API로 사람과 사물 그리고 모든 것이 연결되는 세상을 꿈꿉니다.
클라이언트 개발 효율화를 위한 REST API에서 JavaScript API 로 설계하기
ABSTRACT
Web-centric era의 도래로 web 서비스와 융합(mashup)이 가능하도록 RESTful web service가 탑재된 다양한 embedded system가 등장하고 있다.
RESTful web service는 기존 web service (WS-* technology)에 비해 가볍고 쉬우며 별도의 추가 protocol 없이 HTTP를 지원하는 모든 형태의 platform에서 사용할 수 있다는 장점이 있다. 대신 개발자는 친숙하지 않은 개념인 GET, PUT, POST, DELETE 같은 표준 HTTP method를 이용해 URI가 가리키는 리소스의 상태 전이(State Transfer)라는 형태로 action을 표현하는 REST API 개념을 이해해야만 한다. 이런 난이함 때문에 모든 도메인의 API 설계에 적용 가능한 완전히 (fully) RESTful API 설계 방법론이 존재하지 않기에 현재 system이 제공하는 REST API 불완전하고 추후 변경될 소지가 많다. 또한 RESTful web service 설계를 돕는 WADL, WSDL 2.0, RDF 와 같은 도메인 언어가 존재하고 이를 이용한 자동화 도구도 소개되고 있으나, 주로 API를 제공(expose)하는 server 개발 측면에 집중되어 있다. 반면에 mediocre client 개발자는 해당 서비스의 도메인 지식이나 RESTful web service 이해가 부족해 개발에 어려움을 겪고 있다. 본 논문에서는 client 개발자 입장에서 어려움을 해결하기 위해서 REST API를 역 분석(reverse engineering)하여 사용하기 쉬운 JavaScript API로 재설계하는 방법을 고안하였다. 이 방법에 따라 REST API 대신 JavaScript API를 도입하고 이를 구현한 wrapper를 client 개발자에게 제공한 사례를 소개한다. 이를 통해 얻을 수 있었던 client 개발 효율화 이득을 설명한다. Future work으로 추가 발생하는 wrapper 개발 비용을 낮추기 위해 stub code를 자동 생성하는 방안 및 server side JavaScript에서도 사용할 수 있는 방안을 연구하려고 한다.
1. INTRODUCTION
모든 기기가 인터넷을 통해 연결되어 상호 연동하는 시대를 맞이하여 embedded system에서 인터넷을 접속하여 정보를 이용하는(information consumer) 형태 뿐만 아니라 마치 웹 서버 처럼 정보를 제공하는 (information producer) 형태가 등장하기 시작했다. 클라우드 컴퓨팅 시대가 본격화 되면서 개인이 가진 단말기에서는 주로 입/출력 작업만 이뤄지고 클라우드라 불리는 가상의 공간에서 정보 분석 및 처리, 저장, 관리, 유통 등의 작업이 이뤄지는 형태가 보편화 될 것으로 보았으나, 반면 단말기 내에 각종 센서가 탑재되고 계산 능력 (computing power)이 비약적으로 향상되어 단말기 자체가 클라우드 공간의 서비스 제공자 (service provider)의 역할을 수행하는 형태도 등장한다. 이를 기반으로 인간의 개입 없이 혹은 최소한의 개입으로 기기 간 협력하여 센싱, 정보 처리 및 교환, 네트워킹 하면서 상호 지능적 관계를 형성하고 서비스화 되는 사물 공간 연결망인 Internet of Things1 (a.k.a. IoT) 가 새로운 시대의 페러다임으로 등장하고 있다.
device vendor는 embedded system에서 인터넷의 정보 제공자 역할을 수행하기 위해 RESTful architecture style2의 web server를 탑재하고 있다. SOAP, WSDL, UDDI 표준을 사용하는 기존 web service architecture 는 embedded system에 구현하기에는 무겁고 복잡하기 때문에, Hypertext Transfer Protocol (HTTP) 기반으로 REST API를 제공하는 웹 애플리케이션 서버(WAS)가 채택 되었다. REST API는 RESTful architecture style에 따라 Uniform Resource Identifiers (URI), HTTP 와 표준 media type (e.g. XML, JSON, ... )로 resource를 구분하고 HTTP method를 이용해 동작하도록 구현하여 제공하는 Application Programming Interface (API)이다. 이 API는 기존 web service와 다르게 서버와 클라이언트 간 낮은 결합도(loosely coupled)를 가지는 장점을 있어 2 tier 구조 (서버-클라이언트) 뿐 아니라 3 tier 구조 (서버 - facade 혹은 gateway - 클라이언트) 이상의 복잡한 architecture로 구성된 다양한 서비스를 생성할 수 있다.
embedded system가 제공하는 REST API를 통해 기존 기기와 차별화된 융복합 (convergence) 서비스를 만들 수 있다. 예를 들어, file storage 및 synchronization 역할을 수행하는 Infra as a Service (IaaS) 없이도 기기 간 seamless data sharing이 가능한 cloud service3 를 제공할 수 있으며, client의 OS/제품군에 상관 없이 연동 가능한 TV 중심의 N-Screen Service4를 제공할 수 있다. 또한, home gateway server로서 원격에서 댁내의 가전 기기를 제어하고 모니터링할 수 있는 서비스5도 가능하다. 반면 REST API는 client 개발자가 사용하기에 난해하고 API 변경에 대응하기 힘들다는 문제를 가지고 있다. REST API를 사용하기 위해서는 친숙하지 않은 개념인 GET, PUT, POST, DELETE 같은 표준 HTTP method를 이용해 URI가 가리키는 리소스의 상태 전이(State Transfer)라는 형태로 action을 표현하는 REST API 개념을 이해해야만 하기 때문이다.
본 논문은 client 개발자에게 REST API의 기능을 JavaScript API를 제공하기 위해 REST API에서 JavaScript API를 설계하는 방법과 이를 구현한 wrapper를 제공함으로써 client 개발 생산성을 높이는 방안을 제시하였다. 이를 위해 Section 2에서는 REST API 와 별도로 JavaScript API를 도입하게 된 배경을 소개하고 Section 3에서는 REST API에서 JavaScript API를 설계하는 방법(approach)를 소개한다. Section 4에서는 Section 3에서 소개한 접근법을 통해 실제 진행한 과제를 소개한다. 마지막으로 Section 5에서 결론과 함께 추후 진행 방향(future work)을 설명한다.
2. MOTIVATION
embedded system에서 REST API를 제공하기 위해 설계할 때 있어서 전형적인 (traditional) REST API보다 고려해야 하는 제약 사항이 많다. 첫번째 제약은 예상 응답 시간이다. 기존 REST API는 대부분 계산(computation)을 요하는 논리적인 서비스(logical service)에 관련된 기능을 제공하는 데 반면 embedded system의 REST API는 기기가 수행(action)하는 실제적인 서비스(physical service)에 관련된 것이기 때문이다. 전자는 분산 환경 하에서 요청을 분배하여 빠르게 처리될 수 있어 응답 시간(response time)이 짧으나 후자는 요청의 동작을 기기가 완료할 때까지 대기해야만 하기 때문에 상대적으로 긴 응답 시간을 고려해야 한다. 두번째는 느린 진화 속도와 호환성이다. embedded system의 제품 생명 주기 (product life-cycle)는 제품 별로 천차 만별이고 대부분 차기 제품에서야 기존 제품의 개선 기능이 포함된다. 또한 차기 제품에서 기존 제품과 호환될 수 있도록 확장성 있는 설계가 필요하지만 실제 그렇게 구현하기가 어렵다. 그 이유는 호환성을 위해 고려해야 할 사항이 많아지면 제품 개발 비용이 증가하기 때문이다.
진정한(truly) REST API는 설계하는 것은 어렵고 기기 간 표준화된 REST API를 도출하는 것은 더욱 어렵다. REST API를 설계하는 명확한 가이드(concrete guide)와 설계 방법론(process)이 없고 대신 설계 과정 중 검토해야 할 복잡한 design decision들만이 존재한다. 때문에 동일 제품군의 동일 기능에 대해 규격 별로 상이한 API가 등장하고 있으며 Table 1에서는 동일 기능에 대해 가전 (consumer electronics, CE) 기기가 제공하는 다양한 형태의 API를 통해 보여주고 있다.
Table 1. 동일 기능에 대한 규격 별 URI 설계 사례
Feature | A 규격 | B 규격 | C 규격 |
Retrieving device status | /allctrl/status | /GetDeviceInfo | /sd |
Manipulating power status | /allctrl/power | / SendCommandToDevice | /sd |
Retrieving power status | /allctrl/power | /GetDeviceInfo | /sd |
Retrieving available commands | N.A. | /GetCommandListOfDevice | /sd |
REST API는 API를 제공하는 측면에서 보면 진화 가능성(evolvability) 와 유지 보수성(maintainability)의 장점을 갖지만 도리어 API를 사용하는 측면에서 보면 사용하기 힘들게 만들고 작성한 client 코드의 가독성(readability)이 떨어지는 단점을 지닌다. 특히 REST API가 가지는 parameters가 많거나 다양한 media type을 지원하는 등의 조건에 따라 가질 수 있는 payload 형태가 달라지도록 REST API를 설계한 경우에 그렇다.
API 사용성은 도메인(domain)의 이해를 돕는 문서(document) 수준에 달려 있다. 엄밀히 말해 API를 제공하는 device 혹은 플랫폼에 대해 기본 지식이 있어야 API를 사용할 수 있다. 이를 위해서는 이런 기본 지식을 잘 정리된 문서가 필요하다. 이 문서에는 API에서 사용된 용어에 대해 기반 지식(background knowledge)이 없는 개발자도 이해할 수 있는 수준으로 기술되어 있어야 한다.
REST API guide 문서의 유지 보수에는 비용이 많이 든다. WADL6, WSDL 2.07, RDF8 와 같은 모델링 언어를 통해 설계한 경우 API reference 문서를 생성해주는 자동화 도구가 존재하나 programming guide를 생성해주는 도구는 없다. 개발자는 programming guide에 따라 개발하다가 세부 내용(detail)을 확인하기 위해 API reference를 참조하기 때문에 programming guide이 완전성(completeness)과 무결성(integrity)을 가질 수 있도록 관리해야 한다. 현실은 API 추가, 변경, 삭제로 인해 문서에 대한 관리 비용이 늘어나게 된다. 특히 다양한 media type를 지원하는 REST API의 경우, client 개발 시 예외가 발생할 가능성이 많아지고 예외 처리를 위해 client 코드는 복잡해 진다.
신뢰할 만한 서비스를 제공하기 위해서는 다양한 client 상에서 API 테스트가 필요하다. REST API는 server-client간 결합도가 낮고 (loosely coupled) API를 호출하는 측(caller)의 platform에 독립적이다. 이런 이유는 다양한 형태의 API caller에서 다양한 REST API의 호출 조합으로 정상 동작 여부를 테스트해야 한다. REST API의 경우 또다른 server에서 호출할 수도 있고 client 에서 호출할 수도 있는데 호출하는 server-client를 개발하는 언어/플랫폼은 매우 다양하기 때문에 모든 경우의 수에 대해 검증하기 위해서는 많은 비용이 든다. 특히, REST API를 통해 resource를 생성하거나 변경, 삭제하는 등의 device 상태를 변경하는 동작을 수행하는 경우에는 호출하는 쪽(caller)이 이를 수행할 수 있는 권한이 있는 지 확인하는 보안 정책이 고려되어야 한다. 이런 보안 정책의 부재로 web 표준을 따르는 web browser에서는 REST API가 호출되지 않는 현상이 제품 출시 후 발견된 사례를 Section 4에서 소개하겠다.
Web platform이 탑재된 기기 간 interaction을 위해 서로 다른 형태의 (heterogeneous) API가 필요하다. 현재 web application에서는 내부 resource를 이용하기 위해 JavaScript API를 사용하고 외부의 resource를 이용할 때는 REST API를 사용한다. 다시 말해, client application는 REST API를 통해 메세지를 host application에게 전달하고 host application는 JavaScript API로 client와 메세지를 받게 되는 구조가 된다. 따라서, web application 간의 상호 작용을 위해서 JavaScript - REST API 쌍(pair)이 필요하다.
현대 client 개발자에게 친숙한 API의 형태는 JavaScript API이다. 전형적인 web application는 Model-View-Controller (MVC) 개념으로 보면 모델(Model) 역할의 HTML, View 역할의 CSS 그리고 HTML 혹은 CSS의 정보를 가공(manipulation)하거나 간단한 연산을 수행하는 Control 역할로써 JavaScript 로 구성되어 있다. Ajax9, DHTML10 기술이 도입되면서 JavaScript로 대화형(interactive) application을 구현할 수 있게 되었고, HTML 511를 통해 멀티미디어 컨텐츠를 쉽고 용이하게 사용 수 있게 되면서 JavaScript API가 주목을 받게 되었다. REST API를 web application내에서 호출하기 위해서는 Figure 1.과 같이 Ajax JavaScript API를 사용해야 한다.
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4 && xmlhttp.status==200) {
console.log(xmlhttp.responseText);
}
}
xmlhttp.open("GET", url, true);
xmlhttp.send();
Figure 1. JavaScript Ajax API로 REST API 호출 코드 예제
JavaScript는 높은 수준의 (high-level) 추상화된 함수를 설계할 수 있도록 제공한다. 이는 JavaScript가 객체 지향 언어이며 또한 함수형 프로그래밍을 지원하는 특성을 가지기 때문이다. client 개발자는 business logic을 구현하는 중간에 Ajax API를 이용해 REST API를 호출하여 구현할 수 있다. 하지만, JavaScript 언어의 장점을 이용해 생산성을 높이기 위해서 Figure 4 와 같은 함수를 설계해서 활용할 수 있다. Figure 2에서 주의 깊게 볼 부분은 ajaxCall 함수의 6번째 argument로 successCallback를 전달 받는데 이는 함수 형 인자라는 사실이다. JavaScript에서는 이처럼 함수를 전달받아 호출이 가능하기 때문에 함수형 언어처럼 재사용성이 높은 함수를 만들 수 있다.
var addr = "http://foo.com/";
function ajaxCall(method, uri, headers, queries, payload, successCallback, isAsync) {
var httpObj = new XMLHttpRequest();
var url = addr + uri + "?" + queries;
for (var i = 0; i < headers.length ; i++) {
httpObj.setRequestHeader("header", headers[i]);
}
httpObj.onreadystatechange = function() {
if (httpObj.readyState == 4 && xmlhttp.status == 200) {
successCallback(xmlhttp.responseText);
}
}
httpObj.open(method, url, isAsync);
httpObj.send(payload);
}
Figure 2. JavaScript Ajax API를 함수로 은닉화 (encapsulation)한 코드 예제
JavaScript 로 작성한 logic를 공유와 재사용이 가능하다. 앞서 소개한 Ajax 함수와 DHTML 를 통해 JavaScript file 자체를 web application에 가져와서 활용할 수 있다. web application 개발 생산성이 높은 이유는 jQuery12 과 같은 open source JavaScript library를 활용하기 때문이다.
3. APPROACH
객체 지향적으로 설계한 JavaScript API를 역 공학(reverse engineering)를 통해 REST API 대신 만들어 제공한다. JavaScript의 문법(syntax)의 대부분은 객체 지향 언어인 Java에서 차용했다. Java는 객체 지향 특성을 이용해 구현한 다양한 종류의 API를 제공하고 개발자들은 이 API를 사용함으로써 개발 생산성을 향상시킬 수 있다. 따라서 JavaScript에서도 객체 지향 API를 통해 생산성을 향상시킬 수 있다. Figure 2 에서 소개한 수준으로 REST API와 JavaScript API를 맵핑하는 수준으로 JavaScript API를 만들 수 있으나 이렇게 생성된 JavaScript API는 기존 REST API와 비교해 장점이 없다. 하지만 REST API가 제공하는 기능을 역 분석을 통해 객체지향 적으로 재 설계하면 사용하기 쉬운 API로 탈바꿈하게 된다. 이를 위한 일반화된 변환 매커니즘을 소개하면 다음과 같다.
1) REST API의 사용 사례를 도출한다. 대개 REST API의 programming guide에서 사용 사례에 따라 필요한 API와 예시를 설명하고 있기 때문에 이를 통해 손쉽게 알 수 있다.
2) 사용 사례에서 client에서 가져야 할 state와 state relationship을 도출한다. REST API는 server에서 client의 상태 정보를 유지하지 않도록 API를 설계하기 때문에 client에서 해당 상태 정보를 유지해야 한다. 이에 착안하여 client가 유지해야 하는 state와 그 상태로 변경되는 조건 (state transition condition)을 유추할 수 있다. 방법은 간단하다. 먼저 특정 URI에서 GET method가 제공되는 경우 해당 resource에 대한 state를 가져온다고 보고 이에 해당하는 적절한 state를 명명한다. 그리고 이 state의 attribute(s)가 다른 REST API의 POST, PUT, DELETE method에 참조되는 지를 확인하면 state 사이의 relationship을 도출할 수 있다. 또한 URI는 resource의 상하 구조(hierarchy)로 구성되기 때문에 상위 resource가 존재하는 상태에서만 하위 resource가 존재할 수 있다. 때문에 이런 관계도 state relationship에 나타낸다.
3) 도출된 states와 state transition에 대한 책임(responsibility)을 담당할 entity를 도입한다. 객체 지향 설계 원칙13에 따라 client에서 해당 state(s)를 관리할 interface를 도입하는 것이다. 이때, 하나의 interface가 의미상으로는 여러 states와 연관 관계를 가질 수 있으나 하나의 책임을 맡도록 하는 단일 책임 원칙(Single Responsibility Principle)을 따라 설계하는 것이 좋다.
4) 도입한 interface에 state transition(s)에 해당하는 method를 추가한다. REST API 설계 시 POST, PUT, DELETE method에 생성, 업데이트, 삭제의 의미를 부여하기 때문에 method 이름을 지을 때 참조한다.
5) method의 parameter를 설계할때 해당 REST API의 parameters를 구분하여 parameter 수가 많지 않도록 맵핑한다. REST API에서는 parameter를 HTTP의 URI 일부 resource(s) 이름으로, HTTP header의 parameter로, URI 다음에 ? 토큰을 붙여 사용하는 query parameter로, payload body에 다양하게 설정할 수 있다. Figure 3.의 Sample REST API request를 예로 들면, resValue, Header-Param1, Header-Param2, query1, query2, JSON 형태의 media type을 같는 parameters로 bodyParam1, bodyParam2가 이에 해당된다. REST API 설계 원칙에 따르면 URI 혹은 header의 parameters로는 범용 목적의 attribute(s)를 지정하고 query에는 보조적인 (ancillary) 기능에 해당하는 attribute(s)를 그 외는 특정 기능에 필요한 parameter(s) 지정한다. 따라서, JavaScript API로 설계할 때 이런 특성을 이용해 entity의 attribute로 parameter를 mapping할 지 아니면 method의 argument로 mapping하는 게 나을 지를 판단할 수 있다. 가능하면 범용 attributes는 별도의 interface를 도입해서 재사용할 수 있도록 하고 보조적인 attributes는 method의 optional argument(s)로, 나머지는 method의 mandatory parameter(s)로 설계한다. method의 parameter 개수가 너무 많아지면 API 사용성이 떨어지기 때문에 유사한 정보를 지니는 attributes는 묶어서 interface로 설계한 뒤 argument로 전달되도록 한다.
PUT http://127.0.0.1/ws/boo/resValue?query1=value1&query2=value2 HTTP/1.1
Accept: application/json
Accept-Language: ko-KR
Header-Param1 : headerValue1
Header-Param2 : headerValue2
{
bodyParam1 : "bodyValue1",
bodyParam2 : "bodyValue2"
}
Figure 3. REST API에서의 parameters 예
[Constructor (DOMSting id)]
interface Boo {
attribute DOMString id; // originated from resValue
attribute DOMString headerParam1; // originated from Header-Param1
attribute DOMString headerParam2; // originated from Header-Param2
void update(BodyParam params, // grouping payload JSON data as BodyParam interface
optional DOMString query1, // orginated from query1 parameter
optional DOMString query2); // originated from query2 parameter
};
[NoInterfaceObject]
interface BodyParam {
attribute DOMString param1; // originated from bodyParam1
attribute DOMString param2; // originated from bodyParam2
};
Figure 4. Web IDL로 Figure 3의 REST API를 JavaScript API로 설계한 사례
구조 설계가 완료되면 JavaScript API를 모델링 언어로 상세 설계한다. Figure 4는 Figure 3의 REST API를 Web IDL14 언어로 설계한 사례이다. JavaScript API 설계 및 구현의 일관성을 돕기 위해 Web IDL을 존재하는데 이를 활용하면 구현 및 문서화에 도움을 받을 수 있다. 예를 들면 Web IDL에서 XML를 생성하는 open source tool인 widlproc15를 통해 모델링의 완결성(completeness) 검증이 가능하고 이를 확장하면 stub code 및 API 문서 자동 생성 등이 가능하기 때문에 이를 사용하는 것을 추천한다.
JavaScript API를 REST API와 mapping해 주는 wrapper를 구현한다. wrapper는 JavaScript API를 제공하는 web platform 종류에 따라 다양한 개발 언어로 구현될 수 있으며 JavaScript 언어로 구현할 경우 다양한 web platform에서 수정없이 재사용할 수 있다. JavaScript로 wrapper를 구현할 경우, Figure 4의 Ajax 함수를 사용한다.
개념 증명(proof of concept)를 통해 API 사용성을 검증하고 개선사항을 도출해 다음 설계에 반영한다. API 설계를 평가하는 최선의 방법은 설계한 API를 사용해서 직접 web application을 개발해 보는 것이다. 특히 기존 REST API가 제공하는 모든 기능을 한번에 JavaScript API로 설계하기보다 일부를 빠르게 설계하고 해당 기능을 구현해 보는 점진적인(iterative) 방법이 좋다. 좋은 설계 패턴을 찾아 다음 설계에 기준(criteria)으로 삼아 빠르게 API 사용성을 향상 시킬 수 있기 때문이다.
4. CASE STUDY
이번 case study에서는 Smart TV가 제공하는 convergence app API 중 client API를 REST API에서 JavaScript API로 변환해서 host-client 모두 JavaScript API로 제공한 사례를 통해 앞서 소개한 approach의 유용성을 다룬다.
TV Convergence App API (a.k.a N-Service)는 web platform를 탑재한 Smart TV 기기와 주변의(nearby) 모바일 기기가 연동하여 N-Screen 서비스를 개발할 수 있도록 애플리케이션 간의 통신 (inter application communication) 인터페이스를 제공하는 API이다.
기존에는 Smart TV 기기의 application에서는 host 기능에 대한 JavaScript API를 제공하고 모바일 기기는 TV가 제공하는 REST API를 사용해서 client 기능의 application을 구현하도록 제공하고 있었으나 이번 case study를 통해 Figure 5와 같이 client에서도 host처럼 JavaScript API를 사용하여 application을 구현할 수 있도록 API와 wrapper를 개발자에게 제공하였다. 이 결과물은 Android16, Tizen17 OS에서 사용할 수 있는 API로써 samsung developer site18를 통해 공개되었다.
Figure 5. N-Service API의 system architecture
앞서 Section 3에서 소개한 접근 방식에 따라 첫번째, REST API의 기능을 파악하기 위해 API guide 문서에서 API 사용 사례를 정리한다. 일반적으로 API를 외부에 공개할 때 사용법을 가이드해주는 문서를 함께 공개한다. 이 문서는 API를 사용해서 개발자가 구현할 수 있는 기능을 설명하고 있는데 [19] 에서 client를 위한 REST API를 확인할 수 있다. 편의 상 Table 2.는 REST API가 제공하는 기능에 대한 사용 사례를 요약한 것이고 여기서 {appId}, {deviceId}, {groupId}는 각각 host application의 ID, host에 접속되어 있는 (connected) client device의 ID, 생성한 group 의 ID를 의미한다.
Table 2. 발췌한 client 를 위한 REST API list
HTTP method | URI | Use case |
POST | /ws/apps/{appId}/connect | connect to host application |
POST | /ws/apps/{appId}/disconnect | disconnect with host application |
GET | /ws/apps/{appId}/info | get host application information |
POST | /ws/apps/{appId}/queue | push a message to host application or upload a file |
GET | /ws/apps/{appId}/queue/devices/{deviceId} | pop a message of a specific client device from host application |
POST | /ws/apps/{appId}/queue/devices/{deviceId} | push a message to a specific client device |
POST | /ws/apps/{appId}/queue/groups/{groupId} | push a message to a specific group |
GET | /ws/apps/{appId}/queue/groups/{groupId} | retrieve a group members |
POST | /ws/apps/{appId}/queue/groups/{groupId}/join | join a group |
POST | /ws/apps/{appId}/queue/groups/{groupId}/leave | leave a group |
두번째 단계인 사용사례에서 client에서 가져야 할 state와 state relationship는 도출하면 Figure 5와 같다. 앞서 설명한 방법대로 Table 2의 use case list 중 GET method를 통해 도출한 state를 host application, message queue, group 으로 정의하고 각각의 state로 전이(transition)하기 위한 REST API를 맵핑하였다. 특히, URI hierarchy 상으로 {appId} 아래 queue, groups 가 존재하기 때문에 이점을 state diagram의 transition에 반영하였다.
Figure 6. State diagram from use cases
세번째, state와 state transition에 대한 responsibility를 담당할 entry를 도입한다. 앞서 소개한 바와 같이 host application을 위한 JavaScript API를 [20]에서 제공하고 있고 Table3.은 이를 발췌한 것이다. 따라서, 가능한 유사한 기능을 담당하는 신규 entity가 등장하지 않도록 기존 API에 등장하는 entity를 활용하도록 한다. 각 entity가 가져야 할 책임을 고려했을 때 host application를 담당할 NServiceHost interface를 새로 도입하기로 판단하고 message queue와 group은 기존 interface인 NServiceManager, NServiceDeviceGroup, NServiceDevice이 지니는 책임에 맞춰 기능을 나누기로 결정했다.
Table 3. host를 위한 JavaScript API list
Interface | Constant/Attribute/Method | Use Case |
NServiceDeviceManager | void getNServiceDevices (successCallback, errorCallback) | host와 연결된 (connected) client device 객체 (NServiceDevice)를 가져옴 |
NServiceDeviceManager | void registerManagerCallback (callbackFn(ManagerEvent)) | client connection, disconnection 등의 이벤트를 수신하는 callback을 등록함 |
NServiceDeviceManager | Number broadcastMessage (DOMString message) | host와 연결된 모든 client 에게 메세지를 전달 |
NServiceDeviceManager | Number multicastMessage (DOMString groupID, DOMString messasge) | 특정 group에게 메세지를 전달 |
NServiceDevice | DOMString getUniqueID () | 특정 client의 고유 ID를 확인 |
NServiceDevice | DOMString getDeviceID () | 특정 client의 device ID를 확인 |
NServiceDevice | DOMString getName () | 특정 client의 이름을 확인 |
NServiceDevice | Number getType () | 특정 client의 유형 확인 |
NServiceDevice | Number sendMessage (DOMString message) | 특정 client에게 메세지를 전달 |
NServiceDevice | void registerDeviceCallback ( callbackFn(NServiceDeviceEventInfo) ) | 특정 client가 전달하는 메세지를 host에서 수신하는 callback을 등록함 |
NServiceDevice | void unregisterDeviceCallback () | 특정 client에서 전달하는 메세지 수신을 해제함 |
ManagerEvent | Number eventType | connection 혹은 disconnection 이벤트 유형을 확인 |
ManagerEvent | DOMString deviceName | 이벤트를 발생시킨 client 이름 |
ManagerEvent | DOMString uniqueID | 이벤트를 발생시킨 client의 고유 ID |
ManagerEvent | Number deviceType | 이벤트를 발생시킨 client 유형 |
NServiceDeviceEventInfo | Number eventType | 특정 client의 메세지 전송, 그룹 가입 탈퇴 이벤트 유형을 확인 |
NServiceDeviceEventInfo | NServiceDeviceMessageInfo eventData or NServiceDeviceGroupInfo eventData | 메세지 내용 |
NServiceDeviceGroupInfo | DOMString groupName | 특정 client가 가입 혹은 탈퇴한 그룹 이름 |
NServiceDeviceMessageInfo | DOMString message | client가 발송한 메세지 |
NServiceDeviceMessageInfo | DOMString context | client가 발송한 컨텍스트 |
네번째로 state transition에 해당하는 method를 설계하는데 있어 Figure 6에서 나타난 connect, disconnect, info, queue, join, leave를 JavaScript의 메소드로 설계하면 된다. 여기서 REST API는 ajax로 전송되기 때문에 응답 시간을 고려하면 되도록 비동기 함수로 설계하는 것이 좋다. 비동기 함수로 설계하기로 결정할 경우, 동작의 결과가 성공적이였을 때 호출될 success callback 과 실패했을 때에 호출될 error callback이 parameter로 추가되어야 한다.
마지막으로 REST API 특성 상 참조하는 parameters가 많기 때문에 범용 parameters나 유사한 의미를 지닌 parameters에 대해 새로운 인터페이스 안에 은닉화(encapsulation)함으로써 parameter 수를 줄인다. 예를 들어 host application과 connection를 하는 REST API의 예시는 Figure 7과 같다. 여기에 등장하는 parameters 중 application Id인 sampleWidget, header parameters인 SLDeviceID, VendorID, DeviceName, ProductID는 모두 공용 변수라고 판단하고 connection을 위한 메소드 뿐 아니라 다른 목적으로 사용할 수 있도록 별도의 인터페이스로 설계 한다. parameter가 가진 의미에 따라 해당 인터페이스를 도입한다. application Id는 host application에 관한 정보이고 SLDeviceID, VendorID, DeviceName, ProductID는 client에 관한 정보이기 때문에 전자는 NServiceHostInfo 인터페이스, 후자는 NServiceOwnDeviceInfo 인터페이스의 attribute로 설계한다. NServiceOwnDeviceInfo 인터페이스를 위한 get /set 함수를 도입해서 connection 하는 method를 호출하기 이전에 설정하도록 설계하고 연결에 필요한 추가 정보인 host server의 IP address와 port number를 NServiceHostInfo 인터페이스에 추가해서 connection method에 전달하고 응답 시간을 고려해서 비동기로 수행되도록 한다. 이렇게 설계한 JavaScript API를 사용하는 예시는 Figure 8과 같다.
POST /ws/app/sampleWidget/connect HTTP/1.1
SLDeviceID: 12345
VendorID: VenderMe
DeviceName: IE-Client
ProductID: SMARTDev
Figure 7. N-Service REST API 중 Connect to Application에서 발췌
var ownInfo = new webapis.NServiceOwnDeviceInfo({
deviceID : 12345,
deviceName : "IE-Client",
productID : "SMARTDev"
});
webapis.nservice.setOwnDeviceInfo(ownInfo);
var hostInfo = new webapis.NServiceHostInfo({
ipAddress : "162.168.1.2",
appID : "sampleWidget"
});
webapis.nservice.connectToHost(hostInfo,
function (hostObj) {
console.log("successfully connected to host");
},
function (err) {
console.log("fail to connect to host");
});
Figure 8. Figure 7의 REST API를 JavaScript API로 설계했을 때의 예시
Figure 9 는 REST API를 변환해서 client API로 만들고 기존의 host API와 융화(harmonize)되도록 설계한 ER-diagram이다. common I/F라고 표시한 부분은 host application에 존재하는 interface를 재사용 혹은 확장한 것을 의미하고 client specific I/F로 표시한 부분은 client API를 위해 추가한 interface를 의미한다. 이로써 common API라고 표시된 부분처럼 host의 JavaScript API에서 제공하던 method의 일부를 client API에서도 지원할 수 있게 되어 client-host 구분 없는 한 종의 API로 거듭하게 되었다.
Figure 9. N-Service API 의 overall entry-relation diagram
sequence diagram를 통해, API 사용 흐름(call flow)과 API를 구현함에 있어 논리적인 문제가 있는지 확인한다. Figure 10는 Figure 7의 REST API를 Figure 8처럼 JavaScript API를 통해 호출하는 과정을 표현하고 있다. 이런 과정을 통해 명확한 API 설계가 가능하고 구현 상에서 발생할 가능성이 있는 설계 오류를 줄일 수 있다.
Figure 10. N-Service 의 host-client connection를 위한 API sequence flow
구조 설계 (high level design) 과정이 끝났으면 모델링 언어로 상세 설계(detailed design)한다. 여기서는 Web IDL를 이용하여 설계하였고 Figure 11은 Figure 9, Figure 10를 토대로 Web IDL 문법으로 정의한 사례이다. 이 모델링 언어로 상세 설계하면 타입이 없는 언어(untyped language)인 JavaScript에서는 표시할 수 없는 argument type를 명시할 수 있어 API를 구현하거나 사용할 때 있어서 이해를 도울 수 있으며 자동화 도구를 통해 API의 완결성 검증이나 API 문서를 자동 생성 할 수 있다는 장점을 취할 수 있다. Web IDL로 설계하여 생성한 문서는 [21] 에서 확인할 수 있다.
[Constructor (NServiceOwnDeviceInfoInit init)]
interface NServiceOwnDeviceInfo {
attribute DOMString deviceID;
attribute DOMString vendorID;
attribute DOMString deviceName;
attribute DOMString productID;
};
[Constructor (NServiceHostInfoInit init)]
interface NServiceHostInfo {
attribute DOMString ipAddress;
attribute unsigned short portNumber;
attribute DOMString appID;
readonly attribute DOMString? version;
readonly attribute DOMString? appName;
readonly attribute NServiceHostStatus status;
};
[NoInterfaceObject] interface NServiceManager {
boolean setOwnDeviceInfo(NServiceOwnDeviceInfo info);
NServiceOwnDeviceInfo? getOwnDeviceInfo();
void connectNServiceHost(NServiceHostInfo hostInfo,
NServiceHostConnectSuccessCallback onsuccess,
optional ErrorCallback? onerror);
};
Figure 11. Web IDL 언어로 설계한 Connect to Application 관련 API
상세 설계와 같이 API를 제공할 wrapper를 구현한다. 변환 메커니즘의 산출물을 이용하면 REST API와 맵핑되는 JavaScript API의 logic를 구현하는 것은 어렵지 않다. 단, API를 구현할 플랫폼을 결정하고 이에 따라 발생할 수 있는 web application의 보안 정책에 대한 이해는 선행되어야 한다. 첫번째, JavaScript API를 제공하는 주체는 다양하다. 기본적으로 web browser부터 android native application에서 web application를 수행하는 webview22, 설치 형(installable) web application를 지원하는 Tizen, Firefox OS 같은 web platform등이 존재한다. API를 제공할 client 플랫폼을 무엇으로 할 지에 따라 API를 구현하는 언어가 달라질 수 있다.
개념 증명(Proof of concept)을 통해 시스템 제약 사항을 도출하고 개선한다. wrapper를 이용하는 sample client application을 개발해 수행해 본 결과 ajax를 이용해 wrapper를 구현하면 웹 보안 정책으로 인해 다음과 문제가 발생함을 알 수 있었다. cross-site scripting 이라고 알려진 보안 취약점 (security vulnerability)를 막기 위해 web application에서 수행하는 script는 모두 같은 도메인 주소에서 제공되어야 하는데 REST API는 REST API를 호출하는 wrapper와 같은 도메인에 존재할 수 없기 때문에 Ajax로 REST API 호출이 이뤄지지 않는 현상이 발생한 것이다. 이를 해결하기 위해 REST API를 제공하는 web server에서 W3C 표준 Cross Origin Resource Sharing (CORS)을 위해 [23]를 구현하도록 했다.
JavaScript API를 제공함으로써 얻는 client 개발 생산성 및 효과를 검토한다. 동일한 application에 대해 REST API를 사용해서 개발한 경우와 JavaScript API를 사용해서 개발한 경우와의 비교가 이뤄지지는 않았으나 client 개발자에게 JavaScript API를 통해 손쉽게 TV와 mobile 기기 간 convergence 기능을 만들 수 있도록 제공함으로써 이전에 비해 다양한 web application을 개발할 수 있음을 확인했다. 이 application들을 [24], [25], [26], [27], [28]에서 다운로드 받아 확인해 볼 수 있다.
마지막으로 case study를 통해 Section 3의 유효성을 평가하기 위해 Section 2에서 제기한 문제점들이 해결되었는지를 되짚어 보자. 첫째, 개념 증명 과정에서 발견한 REST API에 대한 제약사항을 반영해 제공함으로써 효과적으로 대처할 수 있었다. 둘째, 도출된 JavaScript API는 잘 알려진 객체 지향 설계 원칙에 따라 설계되어 client 개발자가 사용하기 편리했다. 셋째, JavaScript API를 사용한 client 코드는 REST API를 직접 호출한 것보다 가독성이 좋았다. 넷째, JavaScript API의 reference 문서로도 REST API의 programming guide 수준의 정보를 제공할 수 있다. client에서 가능한 동작(action)으로 API가 재설계되었기 때문이다. 마지막으로 비용 대비 효과적인 테스트가 가능했다. 기존의 REST API의 경우 테스트를 마치고 출시되었음에도 불구하고 web browser에서는 호출이 안되는 현상이 발생했는데 이번 case study를 통해 설계한 JavaScript API를 테스트 하면서 발견할 수 있었기 때문이다.
5. CONCLUSION AND FUTURE WORK
본 논문에서는 embedded system에서 제공하는 REST API가 client 개발의 입장에서는 사용하기 어렵다는 점에 주목했다. 따라서, client 개발 생산성을 향상시키고 추후 REST API 변경에도 적절히 대응할 수 있는 방안으로 REST API 대신 JavaScript API를 제공하는 방안을 제시했다. REST API의 사용 사례 분석과 client가 state를 가지는 REST API의 특성에서 부터 도출한 state diagram에서 객체 지향 방법론을 통해 사용성이 높은 JavaScript API를 설계하는 변환 메커니즘을 설명하고 JavaScript API 모델링 언어인 Web IDL로 상세 설계를 진행함으로써 설계 완결성 검증 및 자동 문서화 도구의 도움을 받을 수 있었다. 실제 수행한 case study를 통해 REST API 대신 JavaScript API를 제공하기 위해 발생하는 추가 개발 비용과 JavaScript API를 통한 client 개발 측면의 생산성 이득을 보여 주었다.
REST API를 fully RESTful 하게 설계하기 위한 방법론은 아직 정립되지 않았고 특히 resource 제약이 있는 embedded system의 web server에서 제대로 구현하기도 어렵다. 따라서, 사물 간 통신, 즉 사물 인터넷(IoT)를 enabling 하는 기술로써 evolvability, maintainability, self-descriptiveness 등을 만족시키는 REST API가 device에 탑재되기까지는 더 많은 시간이 필요하리라 보인다.
따라서, REST API를 client 개발자에게 공개하는 대신에 JavaScript API 형태로 포장(wrapping) 해서 제공하면 추후 embedded system의 RESTful API가 변경되거나 다른 모델로 변경되었을 때라도 손쉽게 client 유지 보수가 할 수 있으리라 기대한다.
future work으로 case study를 통해 얻은 API wrapper 개발 경험을 토대로 우리는 Web IDL로 설계한 문서에 REST API에 대한 정보를 추가(annotation)한 후 widlproc 기반으로 해당 stub 코드를 생성하는 도구를 개발하고자 한다. 또한 REST API 변경 및 삭제로 인해 JavaScript에서 발생하는 오류를 수집하는 logic를 추가해서 API wrapper의 수정이 이뤄질 수 있도록 하겠다. 마지막으로 API wrapper를 client 뿐 아니라 Node.JS28와 같은 server에서도 사용할 수 있도록 제공하여 IoT에서도 활용할 수 있는 방안을 연구하겠다.
6. ACKNOWLEDGEMENTS
We would like to thank the Samsung Web API team members for the case studies which they had been conducted together.
7. REFERENCES
[1] 사물 인터넷 기술, 서비스 그리고 정책, 조철회 외 www.nipa.kr
[2] Fielding RT (2000), Architectural styles and the design of network-based software architectures, Ph.D. Thesis, University of California. Irvine, USA
[3] Samsung Link. http://link.samsung.com/
[4] Smart TV Convergence App API. http://www.samsungdforum.com/Guide/ref00003/index.html
[5] Samsung Home Sync. http://developer.samsung.com/homesync
[6] M. Hadley. Web application description language (WADL). Techincal report, W3C, August 2009. http://www.w3.org/Submission/wadl/
[7] R. Chinnici, J. Moreau, A. Ryman, and S. Weerawarana. Web Service Description Language (WSDL) Version 2.0 Part 1 : Core Language. Technical report, W3C, June 2007. http://www.w3.org/TR/wsdl20
[8] Resource Description Framework. http://www.w3.org/RDF/
[9] Asynchronous JavaScript and XML http://en.wikipedia.org/wiki/Ajax_(programming)
[10] Dynamic HTML. http://www.w3.org/Style/#dynamic
[11] HTML5 http://www.w3.org/TR/html5/
[12] jQuery http://jquery.com/
[13] Robert C. Martin, Principles Of Object Oriented Desigin. http://c2.com/cgi/wiki?PrinciplesOfObjectOrientedDesign
[14] Cameron McCormack. Web Interface Definition Language. http://www.w3.org/TR/WebIDL/
[15] Tim Renouf, Paddy Byers, et al. widlproc https://github.com/dontcallmedom/widlproc
[16] Google. Android http://www.android.com/
[17] Linux Foundation. Tizen. http://tizen.org
[18] Samsung web API on developer site. http://developer.samsung.com/samsung-web-api
[19] Client (HHP) to TV Application Communication on samsung d forum site.http://www.samsungdforum.com/Guide/ref00003/convergence_app_clienttotvappcomm.html
[20] NService on samsung d forum site. http://www.samsungdforum.com/Guide/ref00008/nservice/dtv_nservice.html
[21] NService API Reference on Samsung developer site. http://img-developer.samsung.com/onlinedocs/samsung_webapi_guide_public_1.0/html/index.html
[22] WebView reference on Android Developers. http://developer.android.com/reference/android/webkit/WebView.html
[23] Anne van Kesteren. Cross-Origin Resource Sharing. http://www.w3.org/TR/cors/
[24] Colapicto http://developer.samsung.com/samsung-web-api/samples/Colapicto
[25] Draw Together http://developer.samsung.com/samsung-web-api/samples/Draw-Together
[26] Family Album http://developer.samsung.com/samsung-web-api/samples/Family-album
[27] YouTube Continue Play http://developer.samsung.com/samsung-web-api/samples/Continuous-Youtube-play
[28] Puzzle Game http://developer.samsung.com/samsung-web-api/samples/Puzzle-game
[28] Joyent.Inc. node.js. http://nodejs.org/