OSGi 기반 프레임워크과 애플리케이션 아키텍처 진화 과정

대체 어떻게 모듈화 해야 할까…
어떻게 나눌 것인가..
어떻게 구성해야 번들간의 상호참조(CD)를 없앨 수 있을까..
어떻게 나눠놔야 개발을 할 때 여러 번들을 뒤적거리지 않을까..
번들헬이 발생하지 않게 하려면…

위와 같은 고민들은 OSGi와 스프링 DM을 학습하다보면, 자연스레 맞닥드리게 되는 문제들입니다.

이 질문에 대한 답은 모르겠습니다. 사실 답은 있죠. “잘”. 그러려면, 많이 실험을 해봐야 합니다. 때마침 저한텐 아주 좋은 실험체가 있습니다. 임상 실험 프로젝트랄까요.ㅎㅎ 스프링 하이버기반으로 세 달에 걸쳐서 만든 시스템이 하나 있습니다. 규모가 크지도 작지도 않고 좋습니다. 도메인 모델이 한 40개 정도되는 프로젝트입니다. 비즈니스 로직도 포함하고 있어서 서로 얽히고 섥혀있지요.

이 프로젝트에서 OSAF15에 들어갈 코드를 분리해냈습니다. 그게 1단계였죠. 분리해낸지는 꽤 됐지만, 주석이랑 테스트 코드를 추가하느라 시간이 좀 지연됐습니다. 어제부로 그 작업도 끝났습니다. 1.5 단계랄까요. 정리하는 단계는 그렇게 끝났습니다.

이제는 본격적으로 2단계로 돌입해서 쪼갠 것을 적용해봐야 합니다. 그래서 검증이 되는거죠. 일단은 OSGi화 하지 않았던 기존 시스템과 동일하게 동작하는것을 목표로 적용합니다.

2단계가 잘 돌아가면, 그 뒤엔 쪼갠 것을 돌리는 상태에서 OSAF 번들만 수정해서 업데이트를 하는 겁니다. 이게 마지막인 3단계입니다.

오늘은 2단계에 막 들어선 날로, 코딩은 별로 못하고 낙서와 그림을 그리고 웹 서핑을 하면서 다른 자료를 찾는데 시간을 많이 보냈습니다. 다행히 어느 정도 성과가 있었습니다.

사용자 삽입 이미지처음에 그린 그림입니다. OSAF15 번들 자체가 너무 커서, 그 안에 들어있는 몇 개의 패키지를 별도의 모듈로 쪼개는 걸 구상하여 그린 겁니다. OSAF, OSAF-App 이렇게 둘로 쪼개고, 일반 App 번들과 OSAF-App 번들을 WAR 번들에서 참조하는 걸 그리다가.. 문제를 발견했습니다. 그게 바로 아래에 있는 S/F SessionFactory 입니다.

저 때는 아직 문제를 발견했다기 보다.. 뭐랄까.. 냄새가 나고 있었다고 할까요.. 저 땐 단순하게 SessionFactory를 사용한다고만 생각했지 SessionFactory에서 저 번들들 안에 들어있는 모델을 참조해야 한다고.. 즉 상호참조가 발생하리라곤 미쳐 생각을 못하고 있었습니다.

사용자 삽입 이미지 (여러 색의 형광펜을 발견하고, 잘 나오나 확인을 해보는 그림이 좀 멋있어 졌습니다.ㅋㅋㅋㅋ)

두 번째 그림입니다. 첫 번째와 비슷하게 OSAF에서 이번엔 Security 부분을 떼어 내야겠다는 생각이 들었습니다. OSAF-App에는 User, Role, Audit과 같은 인증, 권한 과 관련된 기본 도메인들과 그 도메인이 사용하는 Audit이라는 클래스가 있었습니다. 그리고 User, Role에 대한 Dao, Service, Controller 까지도 들어있었죠.

문제는 Security가 저 녀석들을 사용하고 있다는 겁니다. User, UserDao를 사용합니다. 그렇게 되면 OSAF와 OSAF-App 두 번들이 CD에 빠집니다. 그래서 Security를 빼내면 될 줄 알고 저렇게 OSAF-Security를 빼내기로 결정.

실제 코드 작업을 좀 하다가 보니… 크헉!!!! osaf.service에서 osaf.security를 참조하고 있었습니다. 이러면 이거 때어낸다고 해서 해결될 문제가 아닌게 되는거라.. 다시 고민에 빠짐…

사용자 삽입 이미지(이때부터 그림에 좀 신경을 쓰기 시작했죠.)

맨 왼쪽에 X 표를 친 부분이 바로 그 좌절하는 순간입니다.

여차저차해서 SessionFactory에 대한 실마리를 찾았고, 다시 OSAF는 좀 크지만, 한 덩어리로 가기로 했습니다.
사용자 삽입 이미지(네모와 동그라미를 그리는 연습을 자주 해야겠습니다.)

실제 OSAF 내부에선 저런 순환 구조는 아닙니다. base쪽에 패키지를 세세하게 나눠뒀기 때문에 패키지 순환 참조는 발생하지 않습니다. CD는 하나도 없습니다.

오늘은 여기까지 구상하고 마치고 내일 다시 재도전해야겠습니다. “잘” 나누는 방법을 찾기란 이렇게 힘들고 재밌는 일이더군요. 캬캬캬.

OSGi에서 Hibernate의 SessionFactory 문제

대체 어떻게 해야 할까? 뭘? @Entity 달려있는 클래스들이 여러 번들들에 분포되어 있고, 애플리케이션이 돌아가는 도중에 번들이 추가되고, 없어지고, 다시 설치되고, 업데이트 되는 와중에 SessionFactory는 그에 따라 계속 바껴야합니다.

Spring이 제공하는 AnnotationSessionFactoryBean 클래스로 만드는 SessionFactory는 정적입니다. 한 번 만들고 다른 빈들이 주입받아서 사용하는데, 도통 어떻게 변경해야 할지 모르겠습니다.

이게 문제는 OSGi, Hibernate, Spring DM, JPA를 사용하려다 보면 자연스럽게 다가오는 문제입니다. 저 말고도 이미 예전부터 이 문제를 당면한 여러 개발자들이 있었습니다. 지금 이 순간 “나는 iBatis를 쓰고 있어서 다행이야!!!” 라고 외치고 계신 분이 혹시 계신가요??? ㅎㅎㅎ 어림없습니다. 이 문제에서 못 벗어납니다. SessionFactory 대신에 SqlMapClient로 놓고 생각해 보시면 똑같습니다.

설정 파일을 번들 하나에 전부 넣어 놓고(애노테이션 붙인 도메인 객체들을 모두 한 번들에 놓고), 그걸로 SessionFactory 만들면 되지 않겠냐구요? 아니.. 미래에 추가될 번들에 들어있는 도메인을 어떻게 지금 추가할 수가 있나요? 백투더퓨쳐가 아닌이상 불가능할 뿐더러, 그럴 바엔 아예 OSGi 번들로 나누지 말고 그냥 기존의 애플리케이션처럼 사용하는게 좋을 거 같습니다. 뭐 잠깐 서버좀 껐다 키죠 뭐.ㅎㅎ

다행히도 이 문제에 대한 해결책이 나왔고, 그걸 구현한 예제까지도 제공하고 있습니다.
http://notehive.com/wp/2008/07/23/osgi-hibernate-spring-dm-sample/
전 이런 개발자가 정말 멋져보입니다. 이력서를 보니까, 88/89년에 인턴쉽 하고, 92년도부터 계속 개발을 해온 사람이네요. 그럼.. 지금.. 16년째.. 캬;;;; 장난 아니셤!!!

맥북에서도 Spring DM 웹 번들 설치 성공

인증샷 1. 번들 목록

사용자 삽입 이미지
인증샷 2. simple-web-app 첫 화면

사용자 삽입 이미지인증샷 3. 서블릿

사용자 삽입 이미지인증샷 4. JSP는.. 또 실패;;

사용자 삽입 이미지흠… 리소스가 제대로 등록이 안 되있는건지.. 원인을 좀 찾아봐야겠습니다.

맥북에서 Spring DM 웹 번들 돌린게 왜 기쁘냐면요;; 안 해보신 분들은 몰라요… 윈도에서 돌리는 Equinox(줄여서 윈E)랑 맥에서 돌리는 Equinox(맥E)가 좀 차이가 나는 것 같습니다. 버그 같은데, 그게 맥E 의 버그인지, Spring DM web extender의 버그 인지, catalina의 버그인지 도통..잘 모르겠습니다.

spring web extender가 catalina 번들이 제공하는 서비스를 필요로 하는데, 만약에 catalina 보다 먼저, start 시키면 당연히.. resolved 상태로 못가고 해당 서비스가 들어올 때까지 좀 기다립니다. 그러다가 특정 시간이 되면 타임되서 넘어갑니다. 이게 정상이죠. 윈E에선 이렇게 정상적으로 동작합니다. 그래서 설치할 번들 목록에서 spring web extender 가 catalina 보다 위에 있어도(즉 먼저 start를 시도하겠죠.) 상관없습니다. 기다리다 보면, 다른 번들들 모두 Active 상태가 되고, spring web extender만 Resolved 상태로 남아있습니다.(catalina도 Active 상태가 됐으니 web extender가 필요로 하는 서비스가 제공 되서 상태가 변한겁니다.) 그러면… 이제 web extender만 다시 start 명령어로 Active 상태로 만들어 주면 됩니다.

하지만…. 맥E에선, 한 번 해보세요~ 해보셨어요? 안 해보셨으면 말을 하지마세요.

사용자 삽입 이미지사용자 삽입 이미지

참.. 테스트 환경은. Eclipse 3.4. Spring DM 1.1.1 입니다.

ps: Bad case에 걸렸을 때 Good case를 찾아 빠져 나가는 방법이 있긴 있는데.. 그건 비밀입니다. 캬캬캬

Late Binding in Java

참조 : http://neilbartlett.name/blog/osgibook/

OOP의 목적 중 하나는 의존성을 최대한 낮춰서 코드의 재사용성과 유연함을 늘리는 것이다.

자바에서는 인터페이스를 사용해서 이런 목적에 접근 했다. 인터페이스는 불편하고 그것을 구현한 구현체만 바꾸면 인터페이스를 사용하고 있는 클라이언트 코드는 변경할 필요가 없었기 때문이다. 하지만, 한계가 있는데, 객체를 생성하려면 어차피 구현한 클래스를 클라이언트 쪽에서 알고 있어야 한다는 것이다.

그래서, 스캐너의 생성자를 사용해서 문자열을 받고 그 문자열로 분기문을 돌려서 구현체를 기반으로 객체를 만드는 코드를 사용하기도 하는데, 역시나 새로운 구현체가 생기거나 하면, 또 코드를 바꿔야 된다.

이에 대한 대안으로 스캐너를 사용하지 않고, “다른 뭔가”가 그 객체를 제공해주게 하는 것이다. 그래서 등장한게 “Assembler” 다. “Assembler”를 여러 곳에서 만들어 쓰다가 패턴이 발견되었고, 그 패턴을 구현한 프레임워크가 스프링과 구글쥬스다.

이 들을 Dependency Injection 프레임워크라고 한다. (물론 스프링은 그게 전부는 아니지만) 불행히도, 이런 Assembler Pattern도 정적이라는 특성 때문에 몇 가지 문제가 있다. 객체들의 연관 관계를 정적으로 한 번 생성하고 말기 떄문에, 객체 생성 순서를 신경 써야 한다. B가 A 객체를 참조하려면 A 객체를 먼저 만들어야 된다. 그리고 객체간에 상호 참조(Circular Dependency)도 조심해야 된다.

또 다른 문젠 동적으로 업데이트 하는 것이 불가능하다. 이렇게 정적으로 묶여있는 의존성 그래프에는, 아주 작은 객체 연관 관계를 변경하려고 해도 전체 시스템을 껐다가 켜야 한다.

OSGi는 바로 이 문제를 동적인 “서비스”를 이용해서 해결한다.

서비스는 DI 프레임워크에서의 빈 처럼 평번한 자바 객체(POJO)다. 서비스는 하나 이상의 인터페이스 이름으로 OSGi 서비스 레지스트리에 의해 제공된다. 서비스는 다른 서비스를 사용할 수 있고 고정적인 그래프로 묶이는게 아니라, 서비스는 언제든지 동적으로 등록되고 해지될수 있다. 따라서 서비스들 사이의 관계는 임시적인 관계이다.

객체 생성 순서 문제는 다음과 같이 해결한다. B가 A를 필요로 하는 상황에서 B 객체를 만들 때 A 객체가 있는지 화인하고 안 만들어져있으면 이 객체를 이용할 수 있을 때 까지 잠시 대기한다. 그리고 A 객체를 B가 이용하고 있는 도중에 A가 사라지고 새로운 A’ 객체를 등록하면 서비스 B한테 이벤트를 날려서 교체하게 해준다.

인터페이스 -> Scanner -> Assembler -> DI Framework -> OSGi

bnd에 번들 실행환경 설정하기

Bundle-RequiredExecutionEnvironment: J2SE-1.5, J2SE-1.4

이런식으로 설정하면 이 설정 그대로 MANIFEST.MF에 복사해서 붙여넣어줍니다. 모든 번들에 이런 실행환경을 설정해주는것이 좋겠죠. 가용한 설정 값드은 다음과 같습니다.

CDC-1.0/Foundation-1.0
CDC-1.1/Foundation-1.1
JRE-1.1
J2SE-1.2
J2SE-1.3
J2SE-1.4
J2SE-1.5
J2SE-1.6
OSGi/Minimum-1.0
OSGi/Minimum-1.1

1.5 환경에서 1.4 용 번들을 만들고 싶을 때, -source 와 -target을 사용해서 컴파일 하면 되지만, -source는 애노테이션이나 제네렉, for-each문과 같은 기능을 꺼버리고, -target은 클래스 파일 버전을 1.4 환경이 읽을 수 있도록 설정하는 것일 뿐, 실제 API 상의 차이는 잡아내지 못합니다. 예를 들어, String의 contains() 메소드는 1.5에 추가되어서 JDK 1.5 이상이 환경에서 얼마든지 저 메소드를 사용할 수 있습니다. 저 코드를 담고 있는 번들을 -target과 -source를 사용해서 1.4 번들로 만들고 JDK 1.4 위에서 돌리면, 예외가 발생합니다. 당연한거죠. 따라서, 1.4 용 번들을 만들고자 한다면, JDK 1.4 위에서 해당 번들을 만들고 패키징하는 것을 권장합니다.