[번역 : AOSA Volume 1, 20장] 텔레파시(Telepathy)

이 내용은 AOSA(The Architecture of Open Source Applications) Volume 1의 20장을 번역한 내용입니다. 참고로, 오역이 상당히 많으니 원문을 꼭 참고해서 보시기를 강추합니다.

텔레파시(Telepathy) 1는 음성, 비디오, 텍스트, 파일 전송 등을 처리하는 실시간 통신을 위한 모듈러(modular) 프레임워크이다. 텔레파시의 특별한 점은 다양한 IM(인스턴트 메시징) 프로토콜의 세부 사항을 추상화하는 것 때문이 아니라, 많은 애플리케이션에서 즉시 사용할 수 있는 인쇄 서비스와 대체로 비슷한, 서비스로서의 통신 개념을 제공하는 것이다. 텔레파시를 완수하기 위해, 디버스(D-Bus) 메시징 버스와 모듈러 디자인을 광범위하게 사용한다.

텔레파시가 단일 애플리케이션에서 통신을 중단할 수 있게 하기에 서비스로서의 통신에 정말로 유용하다. 이것은 흥미로운 많은 사례를 가능하게 한다: 이메일 애플리케이션에서 연락처의 존재 여부를 알 수 있고; 누군가와 통신을 시작하고; 파일 브라우저에서 연락처로 직접 파일을 전송하고; 또는 텔레파시에서 튜브(Tubes)로 알려진 애플리케이션에서 연락처와 연락처(contact-to-contact)로 협업을 제공하고 있다. 2005년 로버트 맥퀸(Robert McQueen)이 텔레파시를 만들었고, 이후에 맥퀸이 공동 창업한 회사인 콜레보라(Collabora)를 포함해서 여러 회사와 개인 기여자들이 개발과 유지를 하고 있다.

디버스 메시지 버스(The D-Bus Message Bus)

디버스(D-Bus)는 GNOME과 KDE 데스크톱 환경을 포함한 대부분의 GNU/Linux 시스템의 중추적인 형태로 프로세스 간 통신을 위한 비동기 메시지 버스이다. 디버스는 원래 공유 버스 아키텍처이다: 애플리케이션은 버스(소켓 주소로 식별된)에 연결하고 또한 원하는 메시지를 다른 애플리케이션에 전송하거나 버스의 모든 구성원에게 시그널을 브로드캐스트(broadcast) 한다. 버스에서 애플리케이션은 IP 주소와 비슷한 버스 주소를 가지고, DNS 이름(예, org.freedesktop.Telepathy.AccountManager) 처럼 잘 알려진 이름으로 선언할 수 있다. 모든 프로세스는 메시지 전달과 이름 등록을 처리하는 디버스 서비스(D-Bus daemon)를 이용해서 통신한다.
사용자의 관점에서, 모든 시스템에서 사용할 수 있는 두 개의 버스가 있다. 시스템 버스는 사용자가 시스템 전반(system-wide)의 컴포넌트(프린터, 블루투스, 하드웨어 관리 등)와 통신할 수 있게 하고, 모든 사용자에게 시스템을 공유한다. 세션 버스는 사용자(예, 로그인하는 사용자별 세션 버스가 있다) 에게 유일하고 사용자의 애플리케이션이 서로 통신하기 위해서 사용한다. 버스로 상당한 트래픽이 전송될 때, 애플리케이션이 자신의 버스를 만들거나, 디버스 서비스(dbus-daemon)를 사용하지 않는 비 조정 버스인, P2P(peer-to-peer)를 만들 수 있다.

libdbus, GDBus, QtDBus와 python-dbus를 포함해서, 여러 라이브러리가 디버스 프로토콜을 구현하고 디버스 서비스와 통신할 수 있다. 이 라이브러리들은 디버스 메시지 전송과 수신, 언어별 타입에서 디버스 타입으로 타입의 마샬링(marshalling) 그리고 버스에 객체를 등록하는 역할을 한다. 라이브러리 대부분은 또한 연결된 애플리케이션과 활성화할 수 있는 애플리케이션을 목록화하고, 버스에 이름으로 요청하는 편리한 API를 제공한다. 디버스 관점에서, 이런 것들 모두는 디버스 서비스에 등록된 객체의 메서드를 호출해서 완료한다. 디버스에 대한 자세한 내용은 http://www.freedesktop.org/wiki/Software/dbus를 보라.

20.1. 텔레파시 프레임워크 컴포넌트

텔레파시에서 각 모듈은 디버스 메시징 버스로 다른 모듈과 통신하는 모듈러이다. 일반적으로 대부분은 사용자의 세션 버스를 사용한다. 모듈 간 통신은 텔레파시 스펙문서(specification2)에 자세히 설명하고 있다. 텔레파시 프레임워크 컴포넌트는 그림 20.1이 보여주고 있다.

● 커넥션 매니저(Connection Manager)는 텔레파시와 개별 통신 서비스와의 인터페이스를 제공한다. 예로, XMPP 커넥션 매니저, SIP 커넥션 매니저, IRC 커넥션 매니저 등이 있다. 새로운 프로토콜을 텔레파시에 추가하는 것은 단순히 새로운 커넥션 매니저를 작성하는 것이다.
● 계정 매니저(Account Manager) 서비스는 사용자의 통신 서비스별 계정을 저장하고, 요청 시에 적절한 커넥션 매니저로 각 계정에 연결하는 역할을 한다.
● 채널 디스패처(Channel Dispatcher)의 역할은 각 커넥션 매니저가 보내는 시그널을 수신하는 수신(incoming) 채널을 리스닝 하고, 수신한 시그널을 텍스트, 음성, 비디오, 파일 전송, 튜브와 같은 채널의 타입을 처리할 수 있다고 지정한 클라이언트에 디스패칭한다. 또한, 채널 디스패처는 텔레파시 클라이언트가 아닌 가장 중요한 애플리케이션인 서비스를 제공한다. 이 서비스는 발신(outgoing) 채널에 요청하고 적절한 클라이언트가 로컬에서 처리하도록 할 수 있다. 이 방식은 이메일 애플리케이션과 같은 애플리케이션에, 연락처로 문자 채팅을 요청하고, IM 클라이언트가 채팅 창을 보여주도록 한다.
● 텔레파시 클라이언트는 통신 채널을 처리하거나 관찰한다. 텔레파시 클라이언트는 IM과 VoIP와 같은 사용자 인터페이스와 채팅 로거(logger)와 같은 서비스 모두 포함한다. 클라이언트는 처리하거나 관찰하고자 하는 채널 타입의 목록을 제공해서, 채널 디스패처에 자신을 등록한다.

현재 텔레파시의 구현은, 계정 매니저와 채널 디스패처 모두 미션 컨트롤(Mission Control) 이라는 단일 프로세스가 제공하고 있다.

그림 20.1:텔레파시 컴포넌트 예

모듈러 설계는 더그 맥길로이(Doug McIlroy’s)의 사상인 “한 가지를 잘하는 프로그램을 작성하자”에 기반을 두고 있고, 여러 가지 중요한 장점이 있다.

● 견고성: 일부 컴포넌트의 결함이 전체 서비스를 중단시키지 않을 것이다.
● 개발 용이성: 실행 중인 시스템에서 컴포넌트 교체시에 다른 컴포넌트에 영향이 없다. 특정 모듈의 개발 버전을 테스트할 수 있어서, 다른 시스템과 비교해서 좋다.
● 언어 독립성: 컴포넌트는 디버스가 바인딩하는 어떤 언어로도 작성할 수 있다. 특정 통신 프로토콜의 최상이 특정 언어로 구현되어 있다면, 구현된 언어로 커넥션 매니저를 작성할 수 있고, 여전히 모든 텔레파시 클라이언트에서 사용할 수 있다. 마찬가지로, 특정 언어로 유저 인터페이스 개발을 원한다면, 사용할 수 있는 모든 프로토콜에 접근할 수 있다.
● 라이선스 독립성: 모든 것을 한 프로세스로 실행하면, 컴포넌트는 호환되지 않는 다른 소프트웨어 라이선스에 영향을 받을 수 있다.
● 인터페이스 독립성: 멀티 유저 인터페이스는 같은 텔레파시 컴포넌트 상에서 개발할 수 있다. 이것은 데스크톱 환경과 하드웨어 디바이스(예, GNOME, KDE, Meego, Sugar)에 네이티브 인터페이스를 허용한다.
● 보안성: 컴포넌트는 분리된 주소 공간에서 그리고 매우 제한된 권한(privileges)으로 실행한다. 예로, 일반적인 커넥션 매니저는 단지 컴포넌트가 접근할 수 있는 무언가를 제한하는 SELinux와 같이 어떤 것을 사용할 수 있게 하는 네트워크와 디버스 세션 버스에 접근이 필요하다.

커넥션 매니저는 많은 커넥션을 관리하고, 각 커넥션은 통신 서비스에 논리적인 연결을 나타낸다. 서비스에 연결한 계정당 하나의 커넥션이 있다. 커넥션은 다중 채널을 가지고 있을 것이다. 채널은 통신을 수행하는 방법이다. 한 채널은 IM 대화, 음성 또는 비디오 통화, 파일 전송이나 일부 다른 상태기반(stateful) 기능일 수 있다. 커넥션과 채널은 20.3절에서 자세히 살펴본다.

20.2. 텔레파시가 디버스를 사용하는 방법

일반적으로, 텔레파시 컴포넌트는 사용자의 세션 버스인, 디버스 메시징 버스로 통신한다. 디버스는 많은 IPC 시스템에 공통 기능을 제공한다: 각 서비스는 /org/freedesktop/Telepathy/AccountManager3과 같이 엄격하게 네임스페이스로 구조화된 경로를 가지고 있는 객체를 등록한다. 각 객체는 상당수의 인터페이스를 구현한다. 엄격하게 네임스페이스로 구조화된 것은 org.freedesktop.DBus.Properties와 ofdT.Connection과 같은 형태이다. 각 인터페이스는 호출, 대기 또는 요청할 수 있는 메서드, 시그널 그리고 속성(변수)을 제공한다.

그림 20.2: 디버스 서비스에 등록된 객체의 개념적 표현

디버스 객체 등록

디버스 객체 등록은 디버스 라이브러리를 사용해서 완전하게 처리한다. 실제로, 객체 등록은 인터페이스를 구현한 소프트웨어 객체와 객체 경로를 매핑한 것이다. 서비스가 등록한 객체의 경로는 선택적으로 org.freedesktop.DBus.Introspectable 인터페이스에서 노출한다.
서비스가 특정 목적지 경로(예로, /ofdT/AccountManager)로 수신 메서드 호출을 받았을 때, 디버스 라이브러리는 디버스 객체를 제공하는 소프트웨어 객체를 위치시키고 객체에 적절한 메서드를 호출하는 역할을 한다.

텔레파시가 제공하는 인터페이스, 메서드, 시그널 그리고 속성은 추가 정보를 포함하기 위해 확장한 XML 기반의 디버스 IDL에 자세히 설명되어 있다. 스펙은 문서 생성과 언어 바인딩으로 분석할 수 있다.

텔레파시 서비스는 버스에 많은 객체를 등록한다. 미션 컨트롤(Mission Control)은 서비스에 접근할 수 있도록 계정 매니저와 채널 디스패처를 위한 객체를 등록한다. 클라이언트는 채널 디스패처가 접근할 수 있는 클라이언트 객체를 등록한다. 마지막으로, 커넥션 매니저는 많은 객체(계정 매니저가 새 연결, 열린 커넥션별 객체, 그리고 열린 채널별 객체를 요청하는 데 사용하는 서비스 객체)를 등록한다.

디버스 객체가 타입(오직 인터페이스)을 가지고 있진 않지만, 텔레파시는 다양한 방법으로 타입을 확인한다. 일반적으로 디버스 객체의 프락시를 요청할 때, 이미 알고 있을지라도, 객체의 경로는 객체가 커넥션, 채널, 클라이언트 등인지를 알려준다. 각 객체는 그 타입(예로, ofdT.Connection 또는 ofdT.Channel)에 대한 기본 인터페이스를 구현한다. 채널은 추상 기본 클래스와 같은 종류이다. 다음으로, 채널 객체는 채널 타입을 나타내는 구현(concrete) 클래스를 가지고 있다. 다시, 채널 객체는 디버스 인터페이스로 나타내진다. 채널 타입은 채널 인터페이스에 ChannelType 속성을 읽어서 알 수 있다.

마지막으로, 각 오브젝트는 프로토콜과 커넥션 매니저의 기능에 따라 일부 추가 인터페이스(당연히 디버스 인터페이스로 나타낸다)를 구현한다. 특정 객체에서 사용할 수 있는 인터페이스는 객체의 기본 클래스에서 Interfaces 속성으로 사용할 수 있다.

ofdT.Connection 타입의 커넥션 객체에 대해, 추가 인터페이스로 ofdT.Connection.Interface.Avatars(프로토콜에 아바타 개념이 있다면), odfT.Connection.Interface.ContactList(프로토콜이 연락처 명단을 제공한다면 —전부가 아니라) 그리고 odfT.Connection.Interface.Location(프로토콜이 지리 정보를 제공한다면)과 같은 이름을 가지고 있다. ofdT.Channel 타입의 채널 객체에 대해, 구현 클래스는ofdT.Channel.Type.Text, odfT.Channel.Type.Call 그리고 odfT.Channel.Type.FileTransfer 형태의 인터페이스 이름을 가진다. 커넥션과 같은, 추가 인터페이스는 odfT.Channel.Interface.Messages (채널이 텍스트 메시지를 주고 받을 수 있다면) 그리고 odfT.Channel.Interface.Group(채널이 여러 연락처를 가지고 있는 그룹이라면, 예로 그룹 채팅)과 같은 이름을 가진다. 예로, 텍스트 채널은 적어도 ofdT.Channel, ofdT.Channel.Type.Text 그리고 Channel.Interface.Messages 인터페이스를 구현한다. 채널이 다중-사용자 채팅이라면, odfT.Channel.Interface.Group 또한 구현할 것이다.

디버스 인트로스펙션(Introspection)이 아니라 인터페이스 속성을 사용하는 이유?

어떤 인터페이스를 사용할 수 있는지 알기 위해 디버스의 인트로스펙션(introspection) 기능을 사용하지 않고, 기본 클래스가 Interfaces 속성을 구현하는 이유가 궁금할 것이다. 답변은 채널이나 커넥션의 기능에 따라 채널과 커넥션 객체 각자는 다른 인터페이스를 제공할 것이다. 그러나, 디버스 인트로스펙션 구현 대부분이 같은 클래스의 모든 객체는 동일한 인터페이스를 가질 것이라고 간주한다. 예로, telepathy-glib에서, 디버스 인트로스펙션이 열거한 디버스 인터페이스는 클래스가 구현하고 있고, 컴파일하는 시점에 정적으로 정의한 객체 인터페이스에서 가져온다. 디버스 인트로스펙션이 객체에 있는 모든 인터페이스에 대한 데이터를 제공하고, 실제로 어느 메서드가 동작하는지 여부를 제공하기 위해Interface 속성을 사용해서 해결한다.

비록 디버스 자체가 커넥션 객체에 오직 연결 지향(connection-related) 인터페이스(디버스는 타입에 대한 개념이 없고, 임의로 명명한 인터페이스만 있기 때문에)만 있다는 것을 온전하게 확인하는 수단을 제공하지 않더라도, 텔레파시 언어 바인딩에서 온전한 확인을 제공하기 위해서 텔레파시 스펙이 포함하고 있는 정보를 사용할 수 있다.

IDL 스펙을 확장하는 이유와 방법

기존의 디버스 IDL 스펙은 이름, 인자, 접근 권한과 메서드의 디버스 타입 시그니처, 속성 그리고 시그널을 정의한다. 스펙은 문서, 바인딩 힌트나 명명된 타입을 위한 지원을 제공하지 않는다.

이 한계점을 해결하기 위해, 새로운 XML 네임 스페이스가 필요한 정보를 제공하기 위해 추가되었다. 이 네임 스페이스는 다른 디버스 API에서 사용할 수 있도록 포괄적(일반적)으로 설계되었다. 새로운 요소(element)가 일련의 document, rationales, introduction 그리고 단종 버전 및 메서드의 잠재적인 예외를 포함해서 추가되었다.

디버스 타입 시그니처는 버스로 직렬화되는 것의 저 수준(low-level) 타입의 표시이다. 디버스 타입 시그니처는 (ii)(두개의 int32를 포함한 구조)처럼 보이거나 더 복잡할 수 있다. 예로, a{sa(usuu)}는 문자열부터 uint32, string, uint32, uint32를 포함하는 구조로 배열의 맵이다(그림 20.3). 데이터 포맷을 기술하면서, 타입은 타입에 포함된 정보에 의미론적인 의미를 제공하지 않는다.

프로그래머에게 의미의 명료성을 제공하고, 언어 바인딩을 위해 타이핑을 강화하기 위해, 새로운 요소가 타입 시그니쳐를 제공하면서 단순한 타입, 구조체, 맵, 열거형(enums ) 그리고 플래그를 명명하기 위해 추가되었고, 문서도 마찬가지이다. 디버스 객체에 대한 객체 상속을 확인하기 위한 요소 또한 추가되었다.

그림 20.3: 디버스 타입 (ii)와 타입a{sa(usuu)}

20.2.1. 핸들

텔레파시에서 식별자(예로, 연락처와 방 번호)를 나타내기 위해 핸들을 사용한다. 정해진 연락처나 방을 유일하게 나타내는 튜플(커넥션, 핸들 타입, 핸들)같은 것처럼, 커넥션 매니저가 부호 없는 정수 값을 할당한다.

다른 통신 프로토콜은 다른 방법(예로, 대소문자 구분, 리소스)으로 식별자를 정규화하기 때문에, 핸들이 클라이언트에게 서로 다른 식별자가 같은지 결정하는 방법을 제공한다. 클라이언트가 서로 다른 식별자에 대한 핸들을 요청할 수 있고, 핸들 넘버가 일치한다면, 식별자는 같은 연락처나 방을 가리킨다.

식별자 정규화 규칙은 프로토콜마다 달라서, 클라이언트가 식별자를 구별하기 위해 식별자 문자열을 비교하는 것은 잘못된 방법이다. 예로, escher@tuxedo.cat/bed 과 escher@tuxedo.cat/litterbox는 XMPP 프로토콜에서 같은 연락처(escher@tuxedo.cat)의 두 객체라서, 같은 핸들을 가진다. 클라이언트가 식별자나 핸들에 채널을 요청할 수 있지만, 비교는 오직 핸들만 사용해야 한다.

20.2.2. 텔레파시 서비스 발견하기

계정 매니저와 채널 디스패처와 같이 항상 존재하는 일부 서비스는, 텔레파시 스펙에서 알려진 이름으로 정의하고 있다. 그러나 커넥션 매니저와 클라이언트 이름은 알려진 이름이 아니라서, 발견해야 한다.

텔레파시에서 실행하고 있는 커넥션 매니저와 클라이언트를 등록시키는 역할을 하는 서비스는 없다. 대신, 디버스에서 새로운 서비스의 알림으로 관심 있는 서비스(interested parties)에 대해 수신 대기(리스닝)한다. 디버스 버스 서비스(데몬)는 버스에 새롭게 명명된 디버스 서비스가 나타나는 즉시 시그널을 보낼 것이다. 클라이언트와 커넥션 매니저의 이름은 스펙에서 정의한 알려진 접두사(prefixes)로 시작하고, 새 이름은 이것들과 일치될 수 있다.

이 설계의 장점은 완전히 상태가 없다(stateless)는 것이다. 텔레파시 컴포넌트가 시작할 때, 최근에 어느 서비스를 실행했는지 버스 서비스(오픈 커넥션에 기반을 둔 표준 목록을 가지고 있다)에 문의할 수 있다. 예로, 계정 매니저가 충돌한다면, 어떤 커넥션이 실행 중인지 볼 수 있고, 계정 객체와 다시 연관 지을 수 있다.

커넥션도 서비스다

커넥션 매니저뿐 아니라, 커넥션 또한 디버스 서비스로 보급(광고)한다. 가설적으로 이것은 커넥션 매니저가 각 커넥션을 분리된 프로세스로 분기하는 것을 가능하게 하지만, 이렇게 구현된 커넥션 매니저는 지금까지 없다. 더 구체적으로, 실행 중인 커넥션이 모두가 ofdT.Connection으로 시작하는 서비스임에도, 디버스 버스 서비스에 질의해서 발견하게 한다. 채널 디스패처 역시 텔레파시 클라이언트를 발견하기 위해 이 메서드를 사용한다. 이것은 ofdT.Client 이름으로 시작하고, 예를 들면, ofdT.Client.Logger가 있다.

20.2.3. 디버스 트래픽 줄이기

텔레파시 스펙의 초기 버전은 많은 소비자가 버스에서 원하는 정보를 요청하는 메서드 호출 형태로 과도한 디버스 트래픽을 만들었다. 최신 텔레파시 버전에서 많은 최적화로 과도한 트래픽을 해결했다.

개별 메서드 호출은 디버스 속성으로 교체됐다. 초기 스펙은 객체 속성을 위해 속성별 메서드 호출을 포함했다: GetInterfaces, GetChannelType 등. 필요한 객체의 모든 속성을 요청하는 수차례의 메서드 호출은, 호출하는 것 자체가 오버헤드다. 디버스 속성을 사용해서, 기본 GetAll 메서드 호출로 한 번에 모든 것을 요청할 수 있다.

게다가, 채널의 꽤 많은 속성이 채널의 생애 주기에 대한 것으로 불변(immutable)이다. 이 속성은 채널의 타입, 인터페이스, 누구와 연결했고 요청자가 누군 지와 같은 것들을 포함한다. 파일 전송 채널을 예로 들면, 파일의 크기와 타입과 같은 것 역시 포함된다.

불변 속성의 해시 테이블을 포함하는 채널(수신과 발신 요청의 응답 모두)의 생성을 알리기 위해 새로운 시그널이 추가되었다. 이 시그널은 관심 있는 클라이언트가 개별적으로 이러한 정보를 요청하지 않아도 되는, 채널 프락시 생성자(Section 20.4 참조)에게 직접 전달할 수 있다.

사용자 아바타(avatar)는 바이트 배열로 버스를 지나 전송된다. 텔레파시는 이미 클라이언트에게, 언제 새로운 아바타가 필요한지 알게 하고 필요없는 아바타를 다운로드하여 저장하기 위해, 아바타를 가리키고 있는 토큰을 사용하고 있지만, 각 클라이언트는 응답으로 아바타를 반환하는 RequestAvatar 메서드로 각각 요청해야 한다. 따라서 커넥션 매니저가 아바타를 업데이트했다고 시그널을 보내면, 아바타에 대한 몇 가지 개별 요청은, 메시지 버스로 아바타가 여러 번 전송되는 것이 필요할 것이다.

이 문제는 아바타를 반환하지 않는 새로운 메서드를 추가해서 해결했다(아무것도 반환하지 않는다). 대신에, 아바타를 요청 큐에 추가한다. 네트워크로 아바타를 검색하는 것은 관심 있는 모든 클라이언트가 수신할 수 있는 AvatarRetrieved 시그널이 될 것이다. 이것은 아바타 데이터는 버스로 단지 한 번만 전송이 필요하고, 관심 있는 모든 클라이언트가 사용할 수 있으리라는 것이다. 클라이언트의 요청이 큐에 추가되면, 차후의 모든 클라이언트 요청은 AvatarRetrieved가 큐에서 삭제될 때까지 무시할 수 있다.

다수의 연락처를 로드해야 할 때마다, 상당한 양의 정보가 필요하다(별칭, 아바타, 능력, 그리고 그룹 멤버십, 그리고 아마도 연락처 위치, 주소, 그리고 전화번호). 이전 텔레파시는, 정보 그룹(GetAliases가 이미 연락처 목록을 가지고 있는 것처럼, 대부분 API 호출)당 한 메서드 호출이 필요했을 것이라서, 결과적으로 대여섯 또는 그 이상의 메소드를 호출한다.

이 문제를 해결하기 위해, Contacts 인터페이스가 추가되었다. 이 인터페이스는 단일 메서드 호출로 반환되는 다중 인터페이스의 정보에 접근하게 한다. 텔레파시 스펙은 연락처 속성을 포함해서 확장했다: 네임스페이스 속성은 연락처 정보 검색에 사용하는 그림자처럼 따라다니는 메서드인GetContactAttributes메서드가 반환한다. 클라이언트는 관심 있는 연락처와 인터페이스 목록으로 GetContactAttributes 메서드를 호출하고, 연락처들에서 연락처 속성의 맵을 값으로 하는 맵을 다시 얻는다. 한 조각의 코드가 이것을 더 명확하게 할 것이고, 요청은 다음과 같다:

connection[CONNECTION_INTERFACE_CONTACTS].GetContactAttributes(
  [ 1, 2, 3 ], # contact handles
  [ "ofdT.Connection.Interface.Aliasing",
    "ofdT.Connection.Interface.Avatars",
    "ofdT.Connection.Interface.ContactGroups",
    "ofdT.Connection.Interface.Location"
  ],
  False # don't hold a reference to these contacts
)

그리고, 응답은 다음과 같을 것이다:

{ 1: { 'ofdT.Connection.Interface.Aliasing/alias': 'Harvey Cat',
       'ofdT.Connection.Interface.Avatars/token': hex string,
       'ofdT.Connection.Interface.Location/location': location,
       'ofdT.Connection.Interface.ContactGroups/groups': [ 'Squid House' ],
       'ofdT.Connection/contact-id': 'harvey@nom.cat'
     },
  2: { 'ofdT.Connection.Interface.Aliasing/alias': 'Escher Cat',
       'ofdT.Connection.Interface.Avatars/token': hex string,
       'ofdT.Connection.Interface.Location/location': location,
       'ofdT.Connection.Interface.ContactGroups/groups': [],
       'ofdT.Connection/contact-id': 'escher@tuxedo.cat'
     },
  3: { 'ofdT.Connection.Interface.Aliasing/alias': 'Cami Cat',
        ⋮    ⋮    ⋮
     }
}

20.3. 커넥션, 채널 그리고 클라이언트

20.3.1. 커넥션

커넥션은, 커넥션 매니저가 한 프로토콜/계정에 커넥션을 설정하기 위해 만든다. 예로, XMPP 계정 escher@tuxedo.cat 와 cami@egg.cat에 연결하는 것은 결과로 두 개의 커넥션이 생길 것이고, 각 커넥션은 디버스 객체로 나타내진다. 일반적으로 계정 매니저가 최근에 이용할 수 있는 계정에 커넥션을 설정한다.

커넥션은 커넥션의 상태 관리와 모니터링 그리고 채널에 요청하기 위한 필수 기능을 몇 개 제공한다. 또한, 프로토콜의 기능에 따라 상당한 선택적 기능을 제공할 수 있다. 이 기능들은 선택적인 디버스 인터페이스로 제공(전 섹션에서 논의한 것처럼)하고 커넥션의 Interfaces 속성에 목록화한다.

일반적으로, 커넥션은 계정의 속성을 사용해서 만들고, 계정 매니저가 관리한다. 계정 매니저는 또한 각 계정을 위해 사용자의 존재 여부를 각자의 연결에 동기화할 것이고, 특정 계정을 위해 커넥션 경로의 제공을 요청받을 수 있다.

20.3.2. 채널

채널은 어떤 통신을 수행하는지에 대한 방법(mechanism)이다. 일반적으로 채널은 IM 대화, 음성, 비디오 통화 또는 파일 전송이지만, 또한 서버 자체와 일부 상태 기반(stateful) 통신을 제공하기 위해서 사용한다(예로, 채팅 방이나 연락처 검색하기). 각 채널은 디버스 객체로 나타낸다.

일반적으로 채널은 둘 이상의 사용자 사이에 있고, 그 중의 한 채널은 자신이다. 일반적으로 채널은 일대일 통신에서는 서로 상대방의 연락처를, 다중 사용자(multi-user) 통신은 채팅 방을 대상 식별자로 사용한다. 다중 사용자 채널은 현재 채널에 있는 연락처를 확인(track)할 수 있게 Group 인터페이스로 드러낸다.

채널은 커넥션에 속해있고, 일반적으로 커넥션 매니저가 채널 디스패처에게 채널을 요청한다; 또는 네트워크 이벤트에 응답하는 커넥션이 채널을 만들고, 디스패칭 하기 위해 채널 디스패처로 전달한다.

채널의 타입은 채널의 ChannelType 속성으로 정의한다. 이 채널 타입이 필요한 것은 핵심 기능, 메서드, 속성 그리고 시그널이고, 적절한 Channel.Type 디버스 인터페이스에서 정의하고 있고, 예로, Channel.Type.Text가 있다. 일부 채널 타입은 채널의 Interfaces 속성이 목록화한, 추가된 인터페이스로 나타내는 선택적 추가 기능(예로, 암호화)을 구현할 것이다. 다중 사용자 채팅 방에 연결한 사용자의 예제 텍스트(text) 채널은 표 20.1에 보이는 인터페이스를 가질 것이다.

속성 용도
odfT.Channel 모든 채널에 대한 공통 기능
odfT.Channel.Type.Text 공통 기능을 포함하는 텍스트 채널의 채널 타입
odfT.Channel.Interface.Messages 리치(Rich) 텍스트 메시징
odfT.Channel.Interface.Group 채널에서 회원의 목록, 확인, 초대 및 승인하기
odfT.Channel.Interface.Room 채팅 방의 주제와 같은 속성들 읽기 쓰기
표 20.1: 테스트 채널 예제

연락처 목록 채널: 실수

텔레파시 스펙의 첫 버전에서는, 연락처 목록을 채널의 타입으로 고려했다. 서버가 정의하고 있는 연락처 목록(등록한 사용자, 사용자에게 공표, 차단한 사용자)이 몇 가지 있었고, 커넥션은 목록을 요청할 수 있다. 목록의 회원은 다중-사용자 채팅과 같은, Group 인터페이스를 사용해서 발견했다.

이것은 원래 일부 프로토콜에서 시간이 소요되며, 오직 한번만 콘텍트 목록을 검색하게 한다. 클라이언트가 원한다면 언제든지 채널을 요청할 수 있고, 준비 되는대로 전달하겠지만, 많은 콘텍트를 가지고 있는 사용자에게 요청이 때때로 타임아웃일 수 있다는 것은 의미했다. 클라이언트의 가입/공표(publish)/차단 상태를 결정에는 세 채널의 확인이 필요했다.

연락처 그룹(예, 친구들)은 역시 그룹당 한 채널이 채널로써 드러낸다. 이것은 클라이언트 개발자가 같이 일하는 매우 어렵다는 것을 보여준다. 클라이언트에서 연락처의 그룹 목록을 가져오는 것과 같은 기능은 상당한 양의 코드가 필요했다. 게다가, 채널을 통해 오직 사용할 수 있는 정보로, 연락처의 그룹이나 가입(subscription)상태와 같은 속성은 연락처 인터페이스로 공표할 수 없다.

두 채널 타입은 연락처 목록의 가입 상태, 연락처가 속한 그룹과 그룹의 연락처들을 포함해서, 클라이언트 저자(authors)에게 더 유용한 방법으로 연락처 명단을 노출하는 것이, 커넥션 자체에서 인터페이스로 대체되었다. 시그널은 연락처 목록이 언제 준비되는지를 가리킨다.

20.3.3. 채널, 채널 속성과 디스패칭 요청

채널은 보유하기 원하는 채널의 속성 맵(map of properties)을 사용해서 요청한다. 일반적으로, 채널 요청은 채널 타입, 대상 핸들 타입(연락처 또는 방) 그리고 대상(target)를 포함할 것이다. 그러나 채널 요청은 또한 예를 들어 파일 전송에서 파일 이름과 크기, 호출에서 음성과 비디오를 처음에 포함할 것인지, 기존의 채널을 컨퍼런스 호출에 결합할지, 또는 연락처를 알려주기 위해 어떤 연락처 서버를 검색해야 하는지와 같은 속성을 포함할 것이다.

채널 요청에서 속성은 ChannelType 속성(표 20.2)과 같이, 텔레파시 스펙에서 인터페이스가 정의한 속성이다.
이 속성은 텔레파시 스펙에서 요청 가능(requestable)으로 표시된 채널 요청에 포함될 수 있는 속성(Properties)에 있는 인터페이스의 네임 스페이스로 한정하고 있다.

속성 값
ofdT.Channel.ChannelType ofdT.Channel.Type.Text
ofdT.Channel.TargetHandleType Handle_Type_Contact (1)
ofdT.Channel.TargetID escher@tuxedo.cat
표 20.2: 채널 요청 예

더 복잡한 예는, 표 20.3에 있는 파일 전송 채널 요청이다. 채널 요청에서 인터페이스가 한정하고 있는 필수 속성을 주의해서 봐라. (줄여서, 필수 속성 전부는 아니다)

속성 값
ofdT.Channel.ChannelType ofdT.Channel.Type.FileTransfer
ofdT.Channel.TargetHandleType Handle_Type_Contact (1)
ofdT.Channel.TargetID escher@tuxedo.cat
ofdT.Channel.Type.FileTransfer.Filename meow.jpg
ofdT.Channel.Type.FileTransfer.ContentType image/jpeg
표 20.3: 파일 전송 채널 요청

채널은 만들어지거나 보장할 수 있다. 채널을 보장한다는 것은 채널이 없다면, 채널을 만든다는 것이다. 채널 생성을 요청하는 것은 결과적으로 완전히 새롭고 구분된 채널을 만드는 것이거나 또는 채널의 다중 복사본이 없다면 에러를 발생시킬 것이다. 일반적으로 텍스트 채널과 호출(예로, 한 사람과 하나의 대화를 원하고, 실제로 많은 프로토콜에서 같은 연락처로 다중으로 분리한 대화를 지원하지 않는다)은 보장받기를 원하고, 파일 전송과 상태 있는(stateful) 채널을 만들기 원한다.

최근에 생성한 채널은 커넥션이 시그널로 알린다. 이 시그널은 채널의 불변(immutable) 속성의 맵을 포함한다. 불변 속성은 채널의 생애 주기 내내 변경되지 않는다는 것을 보장하는 속성이다. 불변으로 고려한 속성은 텔레파시 스펙에 불변으로 표시되어 있지만, 일반적으로 채널의 타입, 대상 핸들 타입, 대상, 개시자(initiator, 채널을 생성하는 사람)와 인터페이스를 포함한다. 채널의 상태와 같은 속성은 확실히 포함하지 않는다.

예전형태로 채널 요청하기

원래 채널은 타입, 핸들 타입 그리고 대상 핸들로 단순하게 요청된다. 이것은 모든 채널이 대상(예로, 연락처 검색 채널)을 가지지 않으며, 일부 채널은 초기 채널 요청에 포함된 추가 정보(예로, 파일 전송, 음성 메일 요청과 SMS를 보내는 채널)가 필요하므로 충분히 유연하지 않았다.

또한, 채널이 요청될 때(보장된 유일한 채널을 만들거나 단순히 채널의 존재를 보장하거나), 그리고 커넥션이 어떤 반응이 일어날지 결정할 책임이 있을 때까지 두 개의 다른 행동이 아쉬운 점이 발견되었다. 이런 이유로, 구식의(오래된) 메서드는 새롭고, 더 유연하고, 더 명확한 메서드로 교체되었다.

채널을 만들거나 보장할 때 채널의 불변 속성을 반환하는 것은 채널에 대한 프락시 객체를 만드는 것을 더 빠르게 할 수 있다. 이것은 지금 요청하지 않아도 되는 정보이다. 표 20.4의 속성 맵은 텍스트 채널(예로, 표 20.3에서 채널 요청 사용하기)을 요청했을 때 포함될 수 있는 불변 속성을 보여준다. 일부 속성(TargetHandle과 InitiatorHandle을 포함하는)은 간결하게 하려고 제외했다.

속성 값
ofdT.Channel.ChannelType Channel.Type.Text
ofdT.Channel.Interfaces {[} Channel.Interface.Messages, Channel.Interface.Destroyable, Channel.Interface.ChatState {]}
ofdT.Channel.TargetHandleType Handle_Type_Contact (1)
ofdT.Channel.TargetID escher@tuxedo.cat
ofdT.Channel.InitiatorID danielle.madeley@collabora.co.uk
ofdT.Channel.Requested True
ofdT.Channel.Interface.Messages.SupportedContentTypes {[} text/html, text/plain {]}
표 20.4: 새로운 채널이 반환하는 불변 속성의 예

일반적으로 채널을 요청하는 프로그램은 요청을 위한 계정, 채널 요청, 그리고 선택적으로 원하는 핸들러의 이름(만약 프로그램이 자체적으로 채널을 처리하기 원한다면 유용하다)을 제공하고, 채널 디스패처에 채널을 요청한다. 커넥션 대신 계정의 이름을 전달하는 것은, 필요한 경우 채널 디스패처가 계정 매니저에게 계정을 온라인으로 가져오도록 요청할 수 있다는 것이다.

요청이 완료되면, 채널 디스패처는 채널을 명명된 핸들러에게 전달하거나, 또는 적당한 핸들러에 둘 것이다(핸들러와 클라이언트에 대한 논의는 아래에서 보자). 적당한 핸들러 이름을 만드는 것은 통신 채널에 전혀 관심 없는 프로그램이 채널 요청을 위한 처음 요청을 넘어서, 사용 가능한 가장 좋은 프로그램으로 처리된다(즉, 이메일 클라이언트에서 텍스트 채팅을 개시하는 것).

그림 20.4: 채널 요청과 디스패칭

요청하는 프로그램은 적절한 커넥션에 요청을 차례로 전달하는 채널 디스패처에게 채널을 요청한다. 커넥션은 NewChannels 시그널을 만들고, 채널을 다루기 위한 적절한 클라이언트를 발견하는 채널 디스패처가 시그널을 받게 된다. 확실히 프로그램의 초기 요청 없이, 수신, 요청받지 않은 채널도 채널 디스패처가 받은 커넥션의 시그널로, 대체로 동일하게 디스패치 된다.

20.3.4. 클라이언트

클라이언트(Clients)는 수신과 발신 통신 채널을 처리하거나 관찰한다. 클라이언트는 채널 디스패처에 등록한 어떤 것이다. 클라이언트는 세 가지 타입(개발자가 원한다면, 단일 클라이언트는 두 개 또는 모두 세 개 타입이 될 수 있지만)이 있다.

● 관찰자(Observers): 채널과 상호 작용 없이 채널을 관찰한다. 관찰자는 대화나 활동(activity) 로깅을 위해서 사용하는 경향이 있다(예로, 수신과 발신 VoIP 통화).
● 승인자(Approvers): 수신 채널을 수락하거나 거절하는 역할을 하는 특정 사용자.
● 핸들러(Handlers): 실제로 채널과 상호 작용한다. 핸들러는 텍스트 메시지 수신을 알리고 전송하는 것, 파일을 전송하거나 수신하는 것 등을 할 것이다. 핸들러는 유저 인터페이스(UI)와 연관되는 경향이 있다.

클라이언트는 최대 세 가지 인터페이스(Client.Observer, Client.Approver 그리고 Client.Handler)로 디버스 서비스를 제공한다. 각 인터페이스는 관찰, 승인 또는 처리하기 위한 채널을 클라이언트에게 알리기 위해 채널 디스패처가 호출할 수 있는 메서드를 제공한다.

채널 디스패처는 채널을 차례대로 클라이언트의 각 그룹에 디스패치한다. 처음에, 채널은 적절한 관찰자(Observers) 모두에게 디스패치된다. 모두 반환하면, 채널은 적절한 승인자(Approvers) 모두에게 디스패치된다. 첫 승인자(Approvers)가 채널을 승인 또는 거절하면, 다른 승인자 모두에게 알리고 채널은 마지막으로 핸들러에게 디스패치된다. 핸들러가 채널 변경을 시작하기 전에 관찰자가 설정할 시간이 필요할 수 있기 때문에 채널 디스패칭은 각 단계에서 완료된다.

클라이언트는 어떤 종류의 채널에 관심 있는지 알도록 채널 디스패처가 읽는 필터 목록인 채널 필터 속성을 드러나게 한다. 필터는 최소한 클라이언트가 관심 있는 채널 타입과 대상 핸들 타입(예로, 연락처나 방)을 포함해야 하지만 더 많은 속성을 내포할 수 있다. 매칭(Matching)은 비교(comparison)를 위해 단순 비교(equality)를 사용해서, 채널의 불변 속성에 대해 완료된다. 표 20.5의 필터는 모두 일대일 텍스트 채널을 매치한다.

속성 값
ofdT.Channel.ChannelType Channel.Type.Text
ofdT.Channel.TargetHandleType Handle_Type_Contact (1)
표 20.5: 채널 필터 예

클라이언트가 등록한 서비스는 잘 알려진 이름ofdT.Client(예, ofdT.Client.Empathy.Chat )으로 시작하기 때문에, 디버스를 이용해서 발견할 수 있다. 또한, 채널 디스패처가 특정 채널 필터를 읽을 파일을 선택적으로 위치시킬 수 있다. 이것은 클라이언트가 실행 중이 아닌데도 불구하고, 채널 디스패처가 클라이언트를 시작할 수 있게 한다. 이런 방법으로 발견할 수 있는 클라이언트가 있다는 것은 텔레파시의 어느 부분의 교체 없이도 언제든지 설정하고 변경할 수 있는 유저 인터페이스를 선택할 수 있게 한다.

양자택일

관심 있는 모든 채널에 필터를 지정할 수 있지만, 실제로 채널을 관찰하는 사례로만 유용하다. 실제 클라이언트는 특정 채널 타입 코드를 포함하고 있다.

빈 필터는 핸들러가 어떤 채널 타입에도 관심이 없다는 것을 지정한다. 그러나 이름으로 설정한 경우, 여전히 핸들러가 채널을 디스패치할 가능성이 있다. 특정 채널을 처리하기 위해, 요구에 따라 만들어진 임시 핸들러가 빈 필터를 사용한다.

20.4. 언어 바인딩 규칙

텔레파시는 디버스 API이기 때문에, 디버스를 지원하는 어떤 프로그래밍 언어로도 동작할 수 있다. 텔레파시에서 언어 바인딩이 필수는 아니지만, 언어 바인딩을 사용하기 위한 편리한 방법을 제공하는데 사용될 수 있다.

언어 바인딩은 두 개의 그룹으로 나눌 수 있다: 스펙, 상수(constants), 메서드 이름 등에서 만들어진 코드를 포함하는 저 수준(low-level) 바인딩; 그리고 프로그래머가 텔레파시를 사용해서 어떤 것을 하기 쉽게 만들어 주는 직접 쓴(hand-written) 코드인 고 수준(high-level) 바인딩. 고 수준 바인딩의 예로 GLib와 Qt4 바인딩이 있다. GLib와 Qt4 바인딩이 저 수준 바인딩을 포함하긴 하지만, 저 수준 바인딩의 예로 파이썬(Python) 바인딩과 기존의 libtelepathy C 바인딩이 있다.

20.4.1. 비 동기 프로그래밍

언어 바인딩에서, 디버스에 요청하는 모든 메서드 호출은 비 동기이다: 요청을 하고, 응답은 콜백으로 정해진다. 비 동기 프로그래밍이 필요한 이유가, 바로 디버스 자체가 비 동기이기 때문이다. 대부분 네트워크와 유저 인터페이스 프로그래밍과 같이, 디버스는 수신 시그널과 메서드 반환을 위해 콜백을 디스패치하는 이벤트 루프의 사용이 필요하다 . 디버스는 GTK+와 Qt 툴킷이 사용하고 있는 GLib 메인루프로 잘 통합한다.

일부 디버스 언어 바인딩(dbug-glib)은 메서드 응답이 반환될 때까지 메인 루프를 중단하는 의사-비 동기(pseudo-synchronous) API를 제공한다. 이것은 예전에 telepathy-glib API 바인딩으로 드러냈다. 불행히도 의사-비 동기 API 사용이 많은 문제점을 일으키는 것으로 판명되었고, 결국 telepathy-glib에서 삭제되었다.

의사-비 동기 디버스 호출이 동작하지 않는 이유

dbus-glib와 다른 디버스 바인딩이 제공했던 의사-비 동기(pseudo-synchronous) 인터페이스는 요청-중단(request-and-block) 기술을 사용했다. 중단된 동안, 디버스 소켓(socket)은 다음 처리를 위해 큐에 대기하고 있는 요청에 응답하지 않고, 오직 새로운 I/O와 디버스 메시지만 폴링했다.

이것은 여러 주요하고 피할 수 없는 문제를 일으킨다:

● 호출자(caller)는 요청의 응답을 기다리는 동안 중단된다. 완전히 반응이 없을 수 있다(유저 인터페이스와 모든 경우). 요청이 시간이 필요한 네트워크 접근이 필요한 경우; 피호출자(callee)가 락이 걸린 경우, 호출자는 호출 대기시간(time out)이 지날 때 까지 반응이 없을 것이다. 쓰레드 사용이 비 동기 호출하는 또 다른 방법이기에, 여기에서 쓰레드 사용은 해결책이 아니다. 대신, 기존의 이벤트 루프로 들어오는 응답을 비 동기 호출하는 것이 제일 좋다.
● 메시지는 재정렬될 것이다. 응답을 기다리기 전에 수신한 메시지는 큐에 있을 것이고, 응답 후에 클라이언트에게 전달될 것이다. 이것은 상태의 변화(예로, 객체가 파괴되었음)를 나타내는 시그널이 객체 실패(예로, UnknownMethod예외처리 된)로 메서드 호출 후에 수신된 상황에서 문제를 일으킨다. 이 상황에서, 사용자에게 표시하고 있는 에러가 무엇인지 알기 어렵다. 반면에, 처음에 에러 시그널을 수신한다면, 보류된 디버스 메서드 호출을 취소하거나, 응답을 무시할 수 있다.
● 서로 의사-중단(pseudo-blocking) 호출하는 두 개의 프로세스는, 서로 질의에 응답을 기다리는 교착상태(deadlock)가 될 수 있다. 이 시나리오는 디버스 서비스와 디버스 서비스를 호출하는 둘 다의 프로세스에서 발생할 수 있다(예로, 텔레파시 클라이언트). 채널 디스패처는 채널을 디스패치하기 위해서 클라이언트의 메서드를 호출하지만, 클라이언트 또한 새로운 채널을 여는 요청을 하려고 채널 디스패처의 메서드를 호출한다(또는, 마찬가지로 같은 프로세스의 일부인 계정 매니저를 호출한다).

C로 만든, 첫 텔레파시 바인딩에서 메서드 호출은, 단순히 typedef 콜백 함수를 사용했다. 콜백 함수는 단순히 같은 타입 시그니처를 구현해야 한다.

typedef void (*tp_conn_get_self_handle_reply) (
    DBusGProxy *proxy,
    guint handle,
    GError *error,
    gpointer userdata
);

이 생각은 단순하고, C에서 동작했다, 그래서 바인딩의 다음 세대에도 계속되었다. 최근에, GObject -인트로 스펙션이라는 툴로GLib/GObject 기반 API를 사용하는, C#과 비슷한 Vala 라는 언어와 더불어, 자바스크립트, 파이썬과 같은 스크립트 언어를 사용하는 방법을 개발했다. 불행하게도, 이것은 다른 언어에 콜백의 타입을 재바인딩(rebind)하는 것을 매우 어렵게 해서, 새로운 바인딩은 언어와 Glib가 제공하는 비 동기 콜백 기능을 사용하도록 설계되었다.

20.4.2. 객체 준비

저 수준 텔레파시 바인딩과 같은 단순 디버스 API에서, 메서드를 호출하거나 간단하게 시그널에 대한 프락시 객체를 만들어서 디버스 객체에서 시그널을 수신할 수 있다. 객체 준비는 특정 객체 경로와 인터페이스 이름으로 시작하기에 간단하다.

그러나, 텔레파시 고 수준 API에서, 객체 프락시가 사용할 수 있는 인터페이스는 무엇이 있고, 검색할(채널 타입, 대상, 개시자) 수 있는 객체 타입에 대해 필요한 공통 속성, 그리고 객체의 상태나 상황을 결정하고 추적하기 원한다.

따라서 준비(readiness)의 개념은 모든 프락시 객체를 위해 존재한다. 프락시 객체에 메서드를 호출해서, 객체의 상태를 비 동기적으로 검색할 수 있고, 상태가 검색되거나 객체가 준비된 때에 알림을 할 수 있다.

모든 클라이언트가 구현하지 않거나 관심 없어서, 특정 객체의 모든 기능, 객체 타입에 대한 준비는 꽤 많이 할 수 있는 기능으로 분리되어 있다. 각 객체는 객체(예로, 인터페이스 속성과 기본 상태)에 대한 중요한 정보를 준비하게 될 핵심(core) 기능을 구현하고, 추가 속성이나 상태 추적을 포함할 수 있는 부가적인 상태에 대해 많은 선택적 기능을 추가한다. 부가적인 기능의 구체적인 사례는 연락처 정보, 능력, 위치 정보, 채팅 상태(“Escher is typing…”과 같은)와 사용자 아바타이다.
예로, 커넥션 객체 프락시는 다음의 기능을 가지고 있다:

● 인터페이스와 연결 상태를 검색하는 핵심 기능;
● 요청할 수 있는 채널 클래스를 검색하거나 연락처 정보를 지원하는 기능;
● 커넥션에 연결하기 위한 기능과 연결된 경우 준비를 반환하는 기능.

프로그래머는 객체가 관심 있는 것의 기능 목록과 그 기능 모두가 준비되었을 때 호출하려는 콜백의 준비가 완료되었는지 요청한다. 이미 모든 기능이 준비되었으면, 콜백은 즉시 호출될 수 있지만, 그렇지 않으면 이 기능에 대한 모든 정보가 검색되자마자 호출된다.

20.5. 견고성
텔레파시의 주요 장점 중의 하나가 견고성(robustness)이다. 컴포넌트가 모듈러 방식이라서, 한 컴포넌트의 충돌이 전체 시스템을 다운시키지 않는다. 다음은, 텔레파시를 견고하게 하는 일부 기능이다.

● 계정 매니저와 채널 디스패처는 자신의 상태를 복구할 수 있다. 미션 컨트롤(Mission Control)이 시작할 때(계정 매니저와 채널 디스패처를 포함한 단일 프로세스), 미션 컨트롤은 사용자의 세션 버스에 최근에 등록한 서비스의 이름을 살펴본다. 어떤 커넥션은 알려진 계정과 연관된 것을 찾고 계정에 다시 연관 짓고(새로운 커넥션을 설정하는 것보다), 실행 중인 클라이언트는 처리하는 채널의 목록을 질의한다.
● 채널 자체의 처리가 열려있는 동안 클라이언트가 사라진다면, 채널 디스패처가 그것을 다시 리젠(respawn)하고 채널을 다시 발행한다. 클라이언트가 반복적으로 충돌하는 경우, 가능하다면, 채널 디스패처가 다른 클라이언트 시작을 시도해 볼 수 있고, 그렇지 않다면, 채널을 닫을 것이다(클라이언트가 처리할 수 없는 데이터로 인해 반복적으로 충돌하는 것을 막기 위해).
● 텍스트 메시지는 보류된 메시지 목록에서 사라지기 전에 승인이 필요하다. 클라이언트는 일단 사용자가 메시지를 확인했다고 확신했을 때 오직 메시지를 승인하기 위한 것이다(즉, 활성화된 윈도우에 메시지를 표시하는 것). 이 방법은 클라이언트가 메시지를 만들려는 시도가 충돌할 때, 채널은 보류된 메시지 큐에 이전에 표시되지 않은 메시지를 여전히 가지고 있다.
● 커넥션이 충돌한다면, 계정 매니저는 커넥션을 리젠(respawn) 할 것이다. 분명히 상태 기반(stateful) 채널의 콘텐츠가 손실되겠지만, 단지 해당 프로세스에서 실행하고 있는 커넥션에만 영향을 줄 것이다. 클라이언트는 커넥션 상태를 모니터할 수 있고, 간단하게 연락처 명단과 상태가 없는(stateless) 채널과 같은 정보를 재요청할 수 있다.

20.6. 텔레파시 확장(사이드카)하기

텔레파시 스펙이 통신 프로토콜이 내보이는 다양한 기능을 다루기 위해 노력했지만, 일부 프로토콜은 자체적으로 확장할 수 있다4. 텔레파시 개발자는 스펙 자체를 확장하지 않고, 텔레파시 커넥션이 확장(extensions)을 사용해서 확장하기(extend)를 원했다. 확장은 사이드카(sidecars) 사용으로 해결했다.

일반적으로 사이드카는 커넥션 매니저에서 플러그인으로 구현한다. 클라이언트가 특정 디버스 인터페이스를 구현한 사이드카를 요청하는 메서드를 호출한다. 예로, XEP-0016 개인 목록의 구현은 com.example.PrivacyLists로 명명된 인터페이스를 구현할 것이다. 다음으로 인터페이스 메서드는 이 인터페이스를 구현해야 하는, 플러그인으로 제공한 디버스 객체를 반환한다. 이 객체는 주 커넥션 객체와 함께 존재한다(이런 이유로, 모터 사이클처럼 이름이 사이트카다).

사이트카의 연혁

텔레파시 초기에, OLPC(One Laptop Per Child) 프로젝트 에서 장치간에 정보를 공유하기 위해 사용자 정의 XMPP 확장(XEPs)의 지원이 필요했다. 이것은 직접Telepathy-Gabble에 추가하였고, 커넥션 객체에서 문서화되지 않은(undocumented) 인터페이스로 드러낸다. 결과적으로, 더 많은 개발자가 다른 통신 프로토콜에서 아날로그를 가지고 있지 않은 명확한 XEPs를 지원하는 더 포괄적인 인터페이스가 필요하다는 것에 동의했다.

20.7. 커넥션 매니저 내부 요약

대부분의 커넥션 매니저는 C/GLib 언어 바인딩을 사용해서 개발했고, 다수의 고 수준 기본 클래스는 커넥션 매니저 개발을 더 쉽게 하도록 개발되었다. 이전에 살펴봤듯이, 디버스 객체는 디버스 인터페이스에 매핑한 많은 소프트웨어 인터페이스를 구현한 소프트웨어 객체로 등록된다. Telepathy-Glib는 커넥션 매니저, 커넥션 그리고 채널 객체를 구현하기 위한 기본 객체를 제공한다. 또한, 채널 매니저를 구현하기 위한 인터페이스를 제공한다. 채널 매니저는 버스에 등록하기 위한 채널 객체를 초기화하고 관리하기 위해, BaseConnection이 사용할 수 있는 팩토리이다.

또한, 바인딩은 믹스인(mixins) 이라는 것을 제공한다. 이것은 추가적인 기능 제공, API 추상화 그리고 이전 버전과의 하위 호환성을 제공하고 같은 방법으로 API 버전을 종료하기 위해 클래스에 추가할 수 있다. 가장 일반적으로 사용하는 믹스인 중의 하나가 객체에 디버스 속성 인터페이스를 추가하는 것이다. 또한, ofdT.Connection.Interface.Contacts와 ofdT.Channel.Interface.Group 인터페이스를 구현하는 믹스인이 있고, 믹스인은 과거와 현재 존재하는 인터페이스, 그리고 메서드의 한 집합(set)으로 과거와 현재 텍스트 메시지 인터페이스를 구현할 수 있게 한다.

그림 20.5: 커넥션 매니저 아키텍처 예

API 실수를 해결하기 위해 믹스인 사용하기

텔레파시 스펙에서 실수를 해결하기 위해 믹스인을 사용하는 한 곳이 바로 TpPresenceMixin이다. 텔레파시가 드러내고 있던 기존의 인터페이스(odfT.Connection.Interface.Presence)는 매우 복잡하고, 커넥션과 클라이언트 둘 다 구현하기 어려웠고, 대부분의 통신 프로토콜에 없거나 거의 사용하지 않는 기능을 노출했다. 기존의 인터페이스는 사용자가 관심 있고, 커넥션 매니저가 실제로 구현한 적이 있는 모든 기능을 노출하는 매우 간단해진 인터페이스(odfT.Connection.Interface.SimplePresence)로 교체되었다. 현재 믹스인은 커넥션에서 두 인터페이스를 구현하고 있어서 기존 클라이언트는 계속 동작하겠지만, 단순화한 인터페이스의 기능 수준만 제공한다.

20.8. 교훈

텔레파시는 디버스 위에서 모듈러와 유연한 API를 만드는 방법의 훌륭한 예제이다. 텔레파시가 디버스 위에서 확장성 있게 개발하고, 프레임워크에 분리하는 방법을 보여준다. 중앙 관리 서비스(Daemon)가 필요 없고, 다른 어떤 컴포넌트에서 데이터 손실 없이, 컴포넌트를 재 시작할 수 있게 한다. 또한, 텔레파시가 버스에서 전송하는 트래픽 양을 최소화하면서, 디버스를 효율적이고 효과적으로 사용하는 방법을 보여준다.

시간이 흐르면서, 텔레파시 개발은 반복적이 되고, 디버스 사용을 개선했다. 실수도 있었고, 교훈도 얻게 되었다. 다음은 텔레파시 아키텍처 설계에서 배운 중요한 교훈의 일부이다.

● 디버스 속성 사용: 정보를 찾기 위해 수십의 작은 디버스 메서드 호출이 필요없다. 모든 메서드 호출은 응답 시간을 가진다. 개별 호출(예로, GetHandle, GetChannelType, GetInterfaces)을 많이 하는 것 보다, 디버스 속성을 사용하고, GetAll을 한 번 호출해서 모든 정보를 반환한다.
● 새로운 객체를 알릴 때, 가능한 많은 정보를 제공해라. 새로운 객체에 대해 알게 됐을 때, 클라이언트가 하는 첫 번째는 객체에 관심 있는지를 알기 위해 객체 속성을 모두 요청한다. 객체를 알리는 시그널에 객체의 불변 속성도 포함해서, 대부분 클라이언트가 어떤 메서드를 호출하지 않아도 객체에 관심 있는지 결정할 수 있다. 게다가, 그 객체에 관심이 있다면, 객체의 불변 속성에 대한 요청은 신경 쓸 필요가 없다.
● Contacts 인터페이스는 한번에 다중 인터페이스로부터 정보를 요청하도록 한다. 연락처에 대한 모든 정보를 검색하기 위해 수차례 GetAll 메서드를 호출하는 것보다, 몇 번의 디버스 호출을 줄이기 위해, Contacts 인터페이스가 한번에 모든 정보를 요청하게 한다.
● 잘 맞지 않는 추상화를 사용하지 마라. 추가적인 인터페이스가 있어야 하는 것보다 기존의 추상화를 사용하기 때문에 Group 인터페이스를 구현한 채널로 연락처 명단과 연락처 그룹을 드러나게 하는 것이 좋은 생각처럼 보였다. 그러나 이것은 클라이언트 구현을 어렵게 만들고 결국 적합하지 않다.
● API가 미래의 요구를 충족할 수 있도록 보장해라. 기존 채널 요청 API는 단지 매우 기본적인 채널 요청만 허용하고 있어서 유연하지 않았다. 이것은 더 많은 정보가 필요한 채널의 요청이 필요할 때, 요구를 충족하지 못한다. 이 API는 상당히 더 유연성을 가지고 있는 것으로 교체해야 한다.

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.