본문 바로가기

이클립스 RCP

[번역] Neil Bartlett: What is OSGi for??

이클립스 OSGi p2가 요즘 욕을 먹다먹다 별 소리를 다 듣고, JAVA.NET의 Editor's Daily Blog로는 OSGi가 대체 뭐냐? 너무 장황하게들 설명하는데 뭔소린지도 모르겠고, 과연 모듈 시스템이 필요한거냐? 라는 질문까지 나와서는 한 무리를 형성하자 Neil Bartlett 이라는 사람이 아래와 같은 글을 올렸다. 뭐, 개인 의견이니까 심각하게 볼 건 없지만, 몇 가지 이야기는 들어볼 만 하다 생각되어서 번역해서 올려본다.
영어가 딸리다 보니, 의역을 넘어 창작(...)의 영역까지 가는것 같지만, 그냥 그런가 보다 하고 읽으시길..
Neil Bartlett: What is OSGi for?? 2008-06-07


java.net 일일 업데이트에서 Chris Adamson은 OSGi의 늘어난 탁월함에 대한 글을 썼다. 그러나 그와 동시에 OSGi를 사용하지는 않고 있다고 시인했는데, 이건 비단 그 만의 경우는 아닌것 같다. OSGi 커뮤니티에 속해 있고, OSGi의 끝내주는 기능들에 대해 설명하는데 열성적인 우리들은, OSGi가 무엇이고 왜 중요한지에 대해 설명하는데는 크게 노력해오지 않았던것 같다.

요약하자면, OSGi는 자바를 위한 모듈시스템이다. 그렇지만 그게 실제로 의미하는 것은 무엇인가?

하나의 배포 유닛으로써 모듈의 주요 기능 중 하나는... 무언가를 (직접) 만들거나 아니면 우리의 어플리케이션의 기능성을 확장하기 위해 내려받아 설치 할 수도 있다는 거다.

자바에서의 표준 배포 유닛은 JAR 파일이다. 우리가 JAR 파일들을 가져다 클래스패스 경로 위에 놓아두면, 경우에 때에 따라, 우리의 프로그램이 동작한다. 왜 단지 어떤 경우에만 인가? 왜냐하면 JAR 파일이란건 모듈이 아니라, 단지 임의의 코드 덩어리이기 때문이다. 또한, 실행시에 JAR 파일을 읽어들일때 그 즉시 (JAR파일이란 건) 존재하지 않게 되버린다. : "클래스경로" 라는 건 단지 긴 클래스들의 목록일 뿐이다. 클래스경로위에 놓은 JAR파일은 그 목록에 기여하지만, 실행할 때에는 JAR 파일들 사이의 경계들이 사라져버린다.

이런게 만들어내는 현상 중 하나는, JAR들이 사용자들로부터 구현패키지들을 숨길 수 있는 길이 없다는 것이다. 그리고 또 다른 한가지 현상으로는, JAR파일들의 클래스들은 클래스경로상에서 최초 사용가능한 위치에서 읽어들여지는데, 그 경로란게 항상 맞는건 아니라는 거다. 예를 들면, JAR 파일 하나가 org.foo.A 와 org.foo.B라는 두개의 클래스를 포함하고 있고, A 는 B에 의존하고 있다고 가정해보자. 우리는 이 두개의 클래스들을 JAR 파일 하나 안에 넣어 놓을 거다. 하지만 A는 같은 JAR 파일안의 B가 필요하다는 걸 강하게 나타낼 방법은 없다. 만일 다른 org.foo.B 를 제공하는 JAR 파일(혹은 동일 Library의 old 버전)이 있고, 그 JAR파일이 클래스경로에서 앞쪽에 위치하는 일이 발생한다면 항상 그 다른 B 클래스를 취한다. (결과적으로) 악질적인 클래스로딩 에러들이 잇따라 일어나게 된다.

진정한 모듈이란 단지 구현코드 만이 아닌 설명이 적힌 메타데이터 - 이를테면 (첫째) 이 모듈의 목적은 무엇이고 제공자가 누구인지 등등. (둘째) 이 모듈의 의존성은 어떻게 되는지, 그리고 (셋째) 이 모듈이 다른 모듈에게 제공하는 건 무엇인지 - 와 함께 구성된다. 또한, 진정한 모듈 시스템에서는, 각각의 모듈에 대해 독립된 네임스페이스를 만들고, 통제되어지고 명확하게 정의된 방식으로만 교차 모듈 로딩을 허용한다.

종속관계로 의미하고자 하는 것은 무엇인가? 쉽게말해서, 프로그램이나 코드 블럭이 동작하게 될 환경에 대한 일련의 가정들인 것이다. 예를들면, 어떤 자바 클래스는 JAVA 5에서 동작하고, 아파치 Log4J 라이브러리에 접근할 거라고 가정할 수 도 있다. 이런 가정들이란 암시적이고, (따라서) 잘못된 환경에서 처음으로 실행하려고 하기 전까지는 그런 가정이 있는지 조차 알 수 없다는 걸 의미한다. 다시말해, Java 1.4 환경에서 실행하려다 어긋나는 광경을 처음 목격하기 전까지, 이 코드는 Java5 에서 실행된다고 가정되어 있다는 사실을 (역어셈블링을 제외 하고는) 알 수가 없다는 거다. 마찬가지로 Log4J가 클래스경로에 없는 상태에서 처음 실행되어서 NoClassDefFoundError 를 보기전까지는 Log4J를 호출한다는 사실을 알 수가 없다.

그래서 우리는 어떤 클래스가 특정환경에서 동작할지 어떨지를 실행해 보기 전까지는 제대로 알 수가 없다. 그리고는 심지어 문제점들을 즉각 발견 못할 수 도 있다. 왜냐하면 특정 실행경로가 뒤따르지 않으면 문제가 발생하지 않기때문이다.

반대로, OSGi는 명시적이고 선언적인 의존정보를 우리에게 제공한다. JAR의 MANIFEST.MF 에 헤더를 추가함으로써, JAR 파일이 동작하게 될 환경이 어떻게 되는지 정확하게 명세화 할 수 있다.
Import-Package: org.apache.log4j
Bundle-RequiredExecutionEnvironment: J2SE-1.5

우리는 검증할 책임이 있고 독립된 모듈로 (해당부분을) 읽어들이는 프레임웍(=OSGi 구현체,equinox등)을 가지고 있다. 만일 우리가 Java 1.4에서 이 JAR 파일을 설치한다면, (클래스파일들로)"해체"되지 않을 것이고, 왜 그런지 확실한 메시지를 얻게 될것이다. 만일 Log4J 모듈이 아직 설치되지 않은 어플리케이션에 이 JAR 파일을 설치한다면, 다시 한번, 빠진것이 무엇인지 설명해주는 명시적인 에러 메시지를 보게 될 것이다. 이런 제약조건들때문에, 만일 적절하게 조립된 번들이 분해된다면, 결코 ClassNotFoundExceptionNoClassDefFoundError 가 발생하지 않으리란 걸 확신할 수 있다. (뭐, Class.forName() 등을 사용해서 동적으로 클래스를 읽어들이는 코드나 비슷한 경우에서의 이슈꺼리가 여전히 있긴 하지만, 그럴때는 또 거기에 따른 다른 처리방법이 있는거고... 대부분은 그렇다는 거다)

더욱이, 우리는 이런 모든 종속성을 실제로 배포하거나 어플리케이션이 동작하기 한참 전에 정적(방식)으로 검사할 수 있다. 모든 종속성 정보는 눈에 보이는, 선언적인 형태로, MANIFEST.MF 안에 바로 들어 있다. 이건 어플리케이션의 동작이 누락된 JAR 파일때문에 실패하지 않게 도와주는 완벽한 툴이다. 또한 서드파티 모듈을 가져와서 우리 어플리케이션에서 동작할지 어떨지, 그리고 만일 동작하지 않는다면 동작하기 해서 다른 어떤 모듈이 필요한지 즉각 확인할 수 있다

이건 단지 겉을 긁어대는 수준이지만, 적어도 Chris의 "왜 모듈 시스템을 필요로 하고 원하는가?"라는 질문에 답이 되길 바란다. 쉽게말해, 모듈 시스템이 없으면, 자바 어플리케이션들의 제작과 배포는 너무많은 오류를 손쉽게 일으킬 수 있다. 자바 프로그러머들은 10년 넘게 "JAR 지옥" 과 "클래스경로 지옥", 그리고 숨겨진 의존성들의 고통을 반복해왔다. (결국)우리는 그것에 익숙해져서 그 고통을 더 이상 의식적으로 자각하지 못한다. 그러나 그 고통이 사라진다면 (제길) 당신은 잘 알게 될거다!

덧붙여 말하면, 위 모든 것들은 모듈 시스템을 사용하는것에 대한 논증이지, 반드시 OSGi를 사용하라는것은 아니다. 예를들면 NetBeans 플랫폼과 Glassfish V1,V2에 들어있는 HK2(=V3에서는 4년전쯤에 OSGi로 대부분 교체되었다), 그리고 (만들어진 적이 있는지는 알수 없는)JSR 277, 예전 Eclipse 모듈 시스템(=4년쯤 전에 OSGi로 교체되었다)과 같은 다른 모듈 시스템들도 주변에 널려 있다. 만일 당신이 어떤 정치가들에게 당신의 투표권을 사용하기에 알맞은지 어떤지를 묻는다면, 그들 하나같이 그렇다고 할거고, 왜 그들의 정당에 투표해야만 하는지 설명할 것이다. (=즉 어떤 주장을 하는 사람들에게 그들의 주장이 옳은거냐고 묻는다면, 누구나 다 그렇다고 말 할 것이고 그 이유에 대해서도 설명하려 들 것이라는 이야기) 난 자바에 있어서 사용가능한 어떤 다른 모듈시스템보다 왜 OSGi를 사용해야만 하는지, 그 이유에 대한 설명은 다른 사람들(혹은 나중 글을 통해 스스로)에게 남겨둘까 한다.


--- 이하 원문 --

Neil Bartlett: What is OSGi for?? 2008-06-07
2008-06-07 00:02 작성

In his daily update at java.net, Chris Adamson writes about the increasing prominence of OSGi. But also admits that he just doesn't "get" OSGi, and it seems he's not the only one. Perhaps we in the OSGi community, in our enthusiasm for explaining the cooler features of OSGi, have not done a great job of explaining what OSGi is and why it matters.

In a nutshell, OSGi is a module system for Java, but what does that really mean?

One of the main functions of a module is as a unit of deployment… something that we can either build or download and install to extend the functionality of our application.

The standard unit of deployment in Java is the JAR file. We take these JAR files and put them on our classpath and, some of the time, our application runs. Why only some of the time? Because a JAR file is not a module, it is merely an arbitrary chunk of code. Also, when we load a JAR file at runtime it immediately disappears: the "classpath" is merely a long list of classes. JARs that we place on the classpath contribute to that list, but at runtime the boundaries between the JARs dissolve.

One symptom of this is that JARs have no way to hide implementation packages from users. Another is that classes are loaded from the first possible location in the classpath, which is not always correct. For example, suppose a JAR file contains two classes, org.foo.A and org.foo.B, and A depends on B. We ship these two classes together in a JAR, but there is no way for A to insist that it gets the B from the same JAR. If another JAR happens to provide org.foo.B (e.g. an older version of the same library) and that other JAR is listed earlier on the classpath, then we will always get that other B. Evil classloading errors ensue.

A real module consists not just of implementation code, but also metadata that describes: (a) What this module is for and who provides it etc (b) What dependencies it has, and (c) What it provides to other modules. Also in a real module system, we create an isolated namespace for each module, and only allow cross-module loading to occur in a controlled and well-defined way.

What do we mean by a dependency? Simply, it is a set of assumptions made by a program or block of code about the environment in which it will run. For example, a Java class may assume that it is running on Java 5 and that it has access to the Apache Log4J library. These assumptions are implicit, meaning we don't know they exist until the first time we try to run the code in an environment where they are false. In other words, we don't know (except by disassembling) that the code assumes it is running on Java 5 until we first try running it on Java 1.4 and watch it crash. Likewise we don't know that it makes calls to Log4J, until the first time we run it without Log4J on the classpath and see a NoClassDefFoundError.

Therefore we have no real idea whether a class will work in a particular environment except by trying it out. Even then we may not immediately discover problems, because they don't occur until a particular execution path is followed.

By contrast, OSGi makes us provide explicit, declarative dependency information. By adding headers to the JAR's MANIFEST.MF, we can specify exactly what environments that JAR will run in:Import-Package: org.apache.log4j
Bundle-RequiredExecutionEnvironment: J2SE-1.5

We then have a framework that is responsible for validating and loading this as an isolated module. If we install this JAR on Java 1.4, it will not "resolve", and we will get a clear message telling us why not. If we install this JAR in an application that does not have a Log4J module already installed, then again we will get an explicit error message explaining what is missing. Because of these constraints, we can be confident that if a properly constructed bundle resolves, it can never throw a ClassNotFoundException or a NoClassDefFoundError (well, almost… there is still the issue of code that does dynamic class loading using Class.forName() or similar… there are different mechanism to handle this).

Even better, we can do all of this dependency checking statically, long before actually deploying or running an application. All of the dependency information is visible, in declarative form, right in the MANIFEST.MF. This is perfect for tools, whicht can help us to construct applications that never fail due to a missing JAR. We can also take a third-party module and immediately identify whether it will work in our application, and if not what other module we need to obtain in order to make it work.

This just scratches the surface, but I hope it at least answers Chris' question "Why would you want or need a module system?". Simply, without a module system, the construction and deployment of Java applications is just too error-prone. Java programmers have been coping with pain - with "JAR hell" and "classpath hell" and hidden dependencies - for more than ten years. We've got so used to it that we don't consciously notice the pain any more. But you damn well notice when the pain goes away!

Incidentally, all of the above is an argument for using a module system, not necessarily for using OSGi. There are other module systems around - such as the one in NetBeans platform, and HK2 in Glassfish V1 and V2 (which is largely replaced by OSGi in V3), and JSR 277 (if it is ever built), and the old Eclipse module system (which was replaced with OSGi around four years ago). If you ask any politician whether it's good to use your vote, they will unanimously agree that it is, and then they explain why you should use it to vote for their party. I will leave it for others (or myself in a later post) to explain why you should use OSGi rather than any of the other module systems available for Java.