2017년 12월 18일 월요일

JAVA API 디자인, 7-8장

http://charlie0301.blogspot.kr/2017/12/java-api.html
http://charlie0301.blogspot.kr/2017/12/java-api-5-6.html

책이 API 디자인 외에 한번쯤 library, framework을 고민한다면 읽어 볼 만함.
일부만 추려서 적었지만 책에는 더 좋은 내용이 자~알 설명 되어 있음.

------------------------------------------------------------------------------------
Chapter 7 :  모듈화 아키텍처를 사용하라

1. 모듈화 설계의 유형

라이브러리
모듈화 라이브러리들을 사용 시 구현이 중복일 수 있고 이 때 최신 API JAR만 로드 되어 링크 될 수 있도록 해야 함.
모듈화 라이브러리의 경우 각 모듈의 구현체를 접근할 수 있는 생성자, 정적 팩터리 메서드가 주로 사용되고 다른 방법으로는 프로퍼티 기반 방법도 가능하다.
모듈화 라이브러리의 "명세"는 인터페이스나 한두 개의 팩터리가 포함된 일부 패키지를 노출하는 모듈이며 해당 명세에서 허용되는 행위를 설명하는 문서도 동반한다.
"구현"은 단순히 명세 모듈에 의존하고 팩터리 구현체를 서비스 등록 시스템이 등록하는 노출된 패키지가 없는 모듈에 해당 됨.
"클라이언트"는 명세 모듈에 의존하고 팩터리 메서드를 호출하는 모듈에 해당되는

2. 상호컴포넌트 룩업과 통신

모듈화 설계의 유일한 목적은 애플리케이션의 개별 부분들을 다른 부분과 분리하는 데 있음.
두개의 모듈을 서로 독립적으로 만들고 싶다면 서로에 관해 알아서는 안되고 인터페이스를 이용해 상호작용해야 한다.

모듈 사용 시 환경 설정을 해야할 필요가 있는데 이 때 대부분의 프레임워크에서 하는 일은 의존성 주입(dependency injection)을 하며 주로 설정파일을 사용할 때가 많다.

- 의존성 주입

*** launcher 예제

모듈화 라이브러리에서는 적절한 초기화를 위해 registerXYZ를 수행하는 팩터리 클래스가 필요하다.
단 코딩 스타일의 미적 측면을 약화시킬 수 있고 절차적 등록으로 인해 (모듈갯수, 로드, 링크, 실행시간) 구동속도를 느리게 할 수 있다.

- 호출 이 필요없는(call-less) 연동 (프로퍼티기반)

*** system.getproperty 예제

연동코드를 실행할 필요가 없고 등록을 좀 더 선언적인 방식으로 수행
주로 System.getProperty()를 호출하는 방법임.
JDK에서 흔희 사용하는 괜찮은 방법이고 호출이 필요없는 등록을 허용하고 지연연동(late binding)도 가능하다.

app 개발자가 registerXYZ 호출 코드는 필요 없지만 일종의 설정을 제공해야 한다.
이는 앱 개발자에게 코드 작성 & 컴파일 & 실행 보다는 낫지만 그래도 클래스의 이름이나 시스템 동작에 대한 지식은 필요하다.
프로퍼티 기반 설정 중 널리 알려진 형식 중 하나는 스프링 프레임워크에서 제공하는 것으로 XML 설정 파일이다.

XML 설정 파일 예제

- 어노테이션 지정

스프링 2.5에서는 구현 클래스에 어노테이션을 지정하는 옵션을 제공함.

*** 어노테이션 예제

어노테이션으로 노출한 빈과 해당 빈의 이름, 다른 API에 대한 잠재적인 의존성까지 지정함.
app 개발자는 더 이상 모든 구현체 클래스의 이름을 참조하는 설정 파일을 작성할 필요가 없음.

*** 빈 설정 예제

이전 방식보다 알아야 할 지식의 양은 적어 졌지만 그럼에도 모든 구현체가 담긴 패키지의 이름은 알아야 함.

- Look Up

*** look up 예제

Lookup.getDefault()의 기본 구현은 JDK의 확장 메커니즘을 기반으로 하기 때문에 아무런 설정파일이 필요 없고 클래스패스만 올바르게 구성하면 된다.
org.apidesign.anagram.api.WordLibrary 와 같은 서비스를 찾을 경우

Lookup.getDefault().lookupAll(org.apidesign.anagram.api.WordLibrary.class) 를 실행하게 되고 이후
구현체에서는 현재 클래스패스를 발견하고 META-INF/services/org.apidesign.anagram.api.WordLibrary에 위치한 모든 리소스를 읽어 클래스명을 알아내기 위해 분석하고
클래스를 인스턴스화 한 후 해당 인스턴스들을 반환한다.

JDK6부터는 등록된 서비스 구현체의 전체목록을 조회하는 공개 API가 존재한다.

*** 예제

3. 확장점 작성하기

왓???

4. 순환 의존성의 필요성

순환 의존성은 레거시 코드를 다룰 때 꽤나 유용하다. 반면 이러한 기능을 새로 작성한 코드에 사용하는 것은 클래스와 모듈 간의 참조로 구성된 하나의 커다란 스파게티 접시를 만드는 것과 같다.

상호 참조는 Lookup을 사용하여 의존성을 제거할 수 있다.
또한 각각 독립적으로 실행되지 않으므로 필요로하는 의존성은 manifest에 명시를 하여 적절히 처리되도록 하는게 필요하다.

5. Lookup은 어디에나 있다.

JDK의 ServiceLoader와 달리 넷빈즈의 Lookup은 다수의 인스턴스를 가질 수 있고 각각은 개별 풀의 역할을 수행한다.
예를 들어 아이콘이 java.awt.Image로 변환되어야 할 필요성이 있는 경우 아이콘이 해당 class를 이미 가지고 있다면 loader에서 찾아 제공하고 아니면 이미지를 만들어 처리하면 된다.

*** 아이콘 예제

Lookup을 이벤트 버스 (event bus)로 생각할 수 있다. 객체가 들어오고 나오고 하는 곳 같이..

*** Modifiable 예제

다른 유용한 것으로는 질의 패턴(query pattern)이다. Lookup의 두가지 측면을 혼합한 것인데 적응형 패턴(adaptable pattern)에 대한 API가 될 수 있다는 것과 Lookup이 컴포넌트 주입에 대한 파사드가 될 수 있다는 것을 의미한다.

6. Lookup의 남용

Lookup의 동적인 특성은 대단히 흥미롭고 감동적이어서 불필요하게 사용할 때가 있다.
Lookup이 흥미로운 객체로 가득 찬 "마법 가방"의 역할을 하게 되는 것이고 API 사용자는 가방안에 손을 집어 넣어 아무것도 안 보이는 채로 특정 인터페이스를 찾는다.
인터페이스가 있다면 제대로 동작할 것이고 없다면 완전히 망가질 것이다.


------------------------------------------------------------------------------------
Chapter 8 : 클라이언트와 제공자를 위한 API를 분리하라.

클라이언트를 위한 API와 플러그인을 위한 서비스 제공자 인터페이스(SPI)는 다르다.

API 진화는 SPI 진화와 다르다.

클라이언트에게 메서드를 제공하는 API의 경우 추가와 관련된 문제는 없다.
새로운 메서드를 추가한다고 해서 바이너리 호환성이 깨지는 것은 아니고 클라이언트 입장에서는 더 많은 가능성과 선택권을 갖게 된다.

. SPI의 경우 정확히 그 반대다.
다른사람들이 반드시 제공해야만 하는 인터페이스에 새로운 메서드를 추가하면 사실상 기존의 모든 구현체가 망가진다.

. API를 적절히 나눠라.
API와 SPI를 분리하는 이유는 진화 문제 때문이었다.
다른 접근 법은 가독성이다.

모든 API(특히 자바로 작성된 API)는 지역성(locality)을 보이며 관련성 있는 것들은 서로 가까운 곳에 정의된다.
사용자에 관해 생각해 보고 사용자가 API에 기대할 만한 지역성을 사용자에게 제공하라.
- 관련 메서드는 같은 클래스에 배치한다.
- 관련이 없는 메서드를 어디에 둬야 할지 생각해낼 수 없다는 이유로 그것들이 이어써야 할 자리가 아닌 곳에 놓아서는 안 된다.
- 관련 클래스는 한 패키지 안에 넣어라.
- 특별한 상황에 유용한 추가적인 클래스는 다른 곳으로 옮긴다.

위 규칙들을 따르면 API의 지역성이 증가하고 결과적으로 API 내에서의 사용자 지향성이 대폭 증가할 것이다.

여러분이 만든 라이브러리의 인터페이스를 훨씬 더 많은 부분으로 나누는 것이 타당하다.
다음은 API를 네가지 서로 다른 범주로 정의한 넷빈즈 모듈의 예다.

- 핵심 API : 이 핵심 API를 사용하지 않고는 불가능한 일부 핵심적인 연산을 수행하고자 하는 평범한 관심사를 가진 사용자 그룹
- 지원 API : API 사용을 용이하게 하는 유틸리티 메서드 집합. 이러한 유틸리티 메서드는 반드시 사용해야 하는 것은 아니지만 API 사용자에게 편의성을 제공한다.
- 핵심 SPI : 라이브러리에 연결(plug in)하고자 하는 다양한 그룹의 사용자를 위한 인터페이스 집합.
- 지원 SPI : 이따금 연결할 인터페이스를 구현하는 것이 다소 복잡할 때가 있다. 다시금 도우미 인터페이스를 제공할 수 있다.

위는 넷빈즈의 특정 모듈에 어떤 것이 효과적인지만 보여주는 것이고
다만 넷빈즈 프로젝트를 진행하면서 저지른 실수로부터 얻은 교훈 중 하나는 바로 API가 사용의 지역성을 따른 다면 API 사용자의 삶이 향상된다는 것이다.

댓글 없음:

댓글 쓰기