Skip to content

Latest commit

 

History

History
183 lines (145 loc) · 14.7 KB

File metadata and controls

183 lines (145 loc) · 14.7 KB

마이크로커널 아키텍처 스타일

마이크로커널 아키텍처(플러그인 아키텍처)는 수십 년 전에 만들어졌지만, 오늘날에도 널리 사용됩니다.
이 아키텍처는 단일 모놀리식 배포로 패키징되어 다운로드 및 설치가 가능한, 고객 사이트에서 서드 파티 라이브러리로 사용되는 어플리케이션에 적합합니다.

12.1 토폴로지

마이크로커널 아키텍처는 코어 시스템과 플러그인 컴포넌트로 구성된 단순한 모놀리식 아키텍처입니다.
애플리케이션 로직은 독립적인 플러그인 컴포넌트와 기본 코어 시스템에 분산되어 있기 때문에, 확장성 및 적응성을 확보할 수 있으며, 각 컴포넌트는 커스텀 처리를 수행할 수 있습니다.

12.1.1 코어 시스템

코어 시스템은 시스템 실행에 필요한 최소 기능으로 정의되며, 이클립스 IDE가 그 예입니다.
코어 시스템 자체는 단순한 텍스트 에디터지만, 플러그인을 추가하면 기능이 점차 확장됩니다.
코어 시스템은 커스텀 처리가 거의 필요 없는 정상 경로(happy path, 일반적인 처리 흐름)를 담당하게 되어, 순환복잡도를 배제할 수 있습니다.
추가 로직은 플러그인 컴포넌트를 장착하는 식으로 편리하게 확장이 가능하여 확장성과 유지보수성, 시험성이 향상됩니다.

예를 들어 전자 제품 재활용 애플리케이션에서 각 제품마다 감정 규칙을 적용할 때, 코어 시스템에 복잡한 로직을 두는 대신, 각 제품마다 플러그인 컴포넌트를 만들어 처리하면 효율적입니다.
이를 통해 복잡한 로직을 분리하고 확장 가능성을 높일 수 있습니다.
새로운 제품이 추가되면 플러그인과 레지스트리만 업데이트하면 됩니다.
마이크로커널 아키텍처에서는 코어 시스템이 해당 제품의 플러그인을 찾아 호출하는 방식으로 구현하기 때문에, 제품이 추가되거나 변경되어도 코어 시스템은 수정하지 않아도 됩니다.
각 전자 제품을 감정하는 복잡한 규칙과 로직은 각각의 독립적인 스탠드얼론 플러그인 컴포넌트에 담게 됩니다.

// 일반적인 구현
public void assessDevice(String devicelD) {  
    // 각 디바이스를 분기 처리    
    if (devicelD.equals("iPhone6s")) {
        assessiPhone6s();
    } else if (devicelD.equals("iPad1")) {
        assessiPad1();
    } else if (devicelD.equals("Galaxy5")) {
        assessGalaxy5();
    } else {
        // Other device assessments
    }
}

// 마이크로 커널 방식
public void assessDevice(String deviceID) {
    // 디바이스 ID를 기반으로 plugin을 가져와서 사용
    String plugin = pluginRegistry.get(deviceID);
    Class<?> theClass = Class.forName(plugin);
    Constructor<?> constructor = theClass.getConstructor();
    DevicePlugin devicePlugin = (DevicePlugin) constructor.newInstance();
    devicePlugin.assess();
}

코어 시스템은 규모와 복잡도에 따라 레이어드 아키텍처나 모듈러 모놀리스로 구현할 수 있으며, 도메인별로 플러그인 컴포넌트를 배치할 수도 있습니다.
예를 들어, 결제 처리 코어 시스템을 두고 신용카드, 페이팔 등 결제 수단을 플러그인 컴포넌트로 구성할 수 있습니다.
전체 모놀리스 애플리케이션은 보통 하나의 데이터베이스를 공유합니다.
코어 시스템의 프레젠테이션 레이어는 코어 시스템에 내장되거나, 별도의 UI를 통해 마이크로커널 아키텍처 스타일로도 구성할 수 있습니다.

그림 12-3

12.1.2 플러그인 컴포넌트

플러그인 컴포넌트는 특수한 처리 로직, 부가 기능, 그리고 코어 시스템을 확장하는 커스텀 코드로 구성된 독립적인 컴포넌트입니다.
변동성이 큰 코드를 분리하여, 애플리케이션의 유지보수성과 시험성을 높이는 역할을 합니다.
이상적인 플러그인 컴포넌트는 상호 독립적이고 의존성이 없어야 합니다.

플러그인 컴포넌트와 코어 시스템은 주로 점대점(point-to-point) 통신 방식으로 연결됩니다.
코어 시스템에서 플러그인의 진입점 클래스를 호출하는 메서드 부분이 둘을 연결하는 파이프 역할을 합니다.
플러그인 컴포넌트는 컴파일 기반 또는 런타임 기반으로 구현할 수 있습니다.
런타임 기반 플러그인은 시스템이나 다른 플러그인을 재배포하지 않고 추가/삭제가 가능하며, 자바 OSGi, 펜로즈 같은 프레임워크로 관리됩니다.
컴파일 기반 플러그인은 관리가 쉽지만, 변경 시 전체 모놀리식 애플리케이션을 재배포해야 합니다.

점대점 플러그인 컴포넌트는 JAR, DLL, 루비 젬 같은 공유 라이브러리나 패키지명(자바), 네임스페이스(C#)로 구현됩니다. 예를 들어, 전자 제품 재활용 애플리케이션에서는 각 제품의 플러그인을 독립적인 공유 라이브러리로 작성하고, 제품명과 동일한 이름을 사용하는 방식으로 구현할 수 있습니다.

플러그인 컴포넌트를 코드베이스나 IDE 프로젝트에서 개별 네임스페이스나 패키지명으로 구현하면 관리하기 좋습니다.
네임스페이스는 app.plugin.<도메인>.<콘텍스트> 형식으로 명명하는 것이 좋습니다.
예를 들어, app.plugin.assessment.iphone6s처럼 명명하면, 두번째 노드인 plugin을 통해 이 컴포넌트가 플러그인임을 바로 알 수 있고, 세번째 노드인 도메인(assessment)을 통해 컴포넌트의 업무 용도를 구분할 수 있습니다.
마지막 노드에는 콘텍스트(iphone6s)를 담아서 각 제품 플러그인을 구분하기 쉽게 합니다.

플러그인 컴포넌트는 반드시 코어 시스템과 점대점 통신을 할 필요는 없으며, 스탠드얼론 서비스나 마이크로서비스로 구현해 REST나 메시징으로 호출할 수 있습니다.
언뜻 보기에는 이 방식이 확장성을 높이는 것처럼 보이지만, 코어 시스템이 모놀리식인 이상 모든 요청이 코어 시스템을 거쳐 플러그인으로 가기 때문에 여전히 단일 아키텍처 퀀텀의 제약을 받습니다.

플러그인 컴포넌트를 개별 서비스로 구현해 원격 액세스하면 커플링이 낮아져 확장성과 처리량이 개선되며, OSGI 등의 특수한 프레임워크 없이도 런타임 변경이 가능하다는 장점이 있습니다.
또한 비동기 통신이 가능해 유저 반응성을 크게 향상시킬 수 있습니다.
예를 들어, 전자 제품 재활용 애플리케이션에서는 코어 시스템이 플러그인에 대한 비동기 요청으로 제품에 대한 감정 작업을 시작할 수 있습니다.
플러그인의 감정이 끝나면 비동기 메시징을 통해 코어 시스템에 완료를 통보해 유저에게 전달할 수 있습니다.

다만 원격 플러그인에 접속하려면 마이크로커널 아키텍처를 분산 아키텍처로 변경해야 하기 때문에, 구현과 배포에 있어서 복잡도와 비용이 증가합니다.
또한, 플러그인이 무응답이거나 작동하지 않을 경우 요청이 완료되지 않기 때문에, 이를 해결하기 위해 메시지큐 등의 기술을 추가로 도입해야 합니다.
이와 같이 여러 비용들이 있기 때문에, 플러그인과 코어 시스템 간의 통신을 점대점으로 할지 원격 액세스로 할지는 신중히 결정해야 합니다.

플러그인 컴포넌트는 중앙 공유 데이터베이스에 직접 접근하지 않고, 데이터를 관리하는 코어 시스템을 통해 필요한 정보를 전달 받습니다.
이를 통해 디커플링을 유지함으로써 데이터베이스 변경이 코어 시스템에만 영향을 미치고, 플러그인 시스템에는 미치지 않도록 할 수 있습니다.
그 대신 각 플러그인은 독립적인 데이터 저장소를 가질 수 있습니다.
예를 들어 전자 제품 재활용 애플리케이션에서는 각 감정 플러그인 컴포넌트가 제품별 감정 규칙을 담은 데이터베이스나 규칙 엔진을 따로 둘 수 있습니다.
이러한 데이터 저장소는 외부화하거나 플러그인 컴포넌트 또는 모놀리식 배포의 일부로 내장(인메모리/임베디드 DB)할 수 있습니다.

12.2 레지스트리

코어 시스템은 플러그인 레지스트리를 통해 사용할 수 있는 플러그인과 이를 가져오는 방법을 알 수 있습니다.
레지스트리에는 플러그인의 이름, 데이터 계약(입/출력 데이터), (플러그인 -> 코어 시스템으로의) 원격 액세스 프로토콜(XML 등) 등의 정보가 포함됩니다.
레지스트리는 코어 시스템의 내부 맵 구조처럼 단순할 수도 있고, 아파치 주키퍼나 콘술 같은 복잡한 레지스트리 및 디스커버리 도구로 구현될 수도 있습니다.
다음은 java를 통해 플러그인 이름을 저장하는 레지스트리를 간단하게 구현한 예시입니다.

Map<String, String> registry = new HashMap<String, String>();

static {
    // 점대점 액세스 예제 코드
    registry.put("iPhone6s", "Iphone6sPlugin");

    // 메시징 예제 코드
    registry.put("iPhone6s", "iphone6s.queue");

    // REST형 예제 코드
    registry.put("iPhone6s", "https://atlas:443/assess/iphone6s");
}

12.3 계약

플러그인 컴포넌트와 코어 시스템 간의 계약은 도메인 단위로 표준화되어 있으며, 플러그인이 수행할 기능과 입출력 데이터 형태가 계약에 명시됩니다.
서드파티 플러그인의 계약을 변경할 수 없는 경우에는 직접 정의한 커스텀 계약을 사용하고, 표준 계약과 플러그인 계약 간의 어댑터를 만들어 코어 시스템이 플러그인 계약과 독립적으로 동작하도록 합니다.
플러그인 계약은 XML, JSON, 또는 객체로 구현됩니다.
예를 들어, 다음의 계약은 전자 제품 재활용 애플리케이션에서 전체 로직(AssessmentPlugin)과 출력 데이터(AssessmentOutput)를 정의합니다.

public interface AssessmentPlugin {
    public AssessmentOutput assess();
    public String register();
    public String deregister();
}

public class AssessmentOutput {
    public String assessmentReport;
    public Boolean resell;
    public Double value;
    public Double resellPrice;
}

이 예제에서 제품 감정 플러그인은 감정 결과를 포맷팅된 문자열(assessmentReport)로 반환하며, resell은 제품의 재판매 가능 여부를 나타냅니다.
재판매 가능하면 제품의 가치(value)와 권장 가격(resellPrice)이 계산됩니다.
이 구조에서 코어 시스템과 플러그인 컴포넌트 간의 역할은 명확히 구분되어 있으며, assessmentReport 필드에 대한 포맷팅과 세부 처리는 플러그인이 담당합니다.
코어 시스템은 감정 리포트를 출력하거나 유저에게 보여주는 역할만 맡고, 리포트의 세부 내용을 이해하거나 처리하지 않습니다.

12.4 실제 용례

이클립스 IDE, PMD, Jira, Jenkins 등 많은 소프트웨어 개발 및 릴리즈 도구들이 마이크로커널 아키텍처로 개발되었습니다.
크롬과 파이어폭스 같은 웹 브라우저도 마이크로커널 아키텍처를 사용하여 뷰어와 플러그인으로 기본 브라우저 기능을 확장할 수 있습니다.
또한 이 아키텍처는 제품 기반 소프트웨어뿐만 아니라 대규모 비즈니스 애플리케이션에도 적용할 수 있습니다.

예를 들어, 보험금 청구를 처리하는 시스템이 있고, 해당 시스템이 지역별로 다른 규칙과 규정 때문에 복잡성이 높다고 합시다.
어떤 지역에서는 차 앞 유리 파손 시 무료로 교체해주는 규정이 있지만, 다른 곳은 그렇지 않은 식입니다.
각 경우를 처리하기 위해 로직을 추가하다 보면 규칙 엔진이 복잡해지는데, 규칙 하나를 변경할 때마다 다른 규칙에 연쇄적으로 영향을 미치게 될 수 있습니다.
이로 인해 규칙 하나의 단순한 변경을 위해서도 개발자, 테스터 등 전문가들이 모여 함께 검토해야 하는 상황이 발생할 수 있습니다.
이러한 상황에서 마이크로커널 아키텍처가 유용합니다.
지역별 보험금 청구 규칙을 독립적인 플러그인 컴포넌트로 관리하여, 특정 지역의 규칙을 추가, 삭제, 변경해도 다른 시스템에 영향을 주지 않게 설계할 수 있습니다.
새로운 지역을 추가하거나 기존 지역을 삭제하더라도 다른 부분에 영향이 없으며, 코어 시스템은 변동이 적은 표준 청구 처리 프로세스를 유지합니다.

12.5 아키텍처 특성 등급

아키텍처 특성 별점
퀀텀 수 1
배포성 X X X
탄력성 X
진화성 X X X
내고장성 X
모듈성 X X X
전체 비용 X X X X X
성능 X X X
신뢰성 X X X
확장성 X
단순성 X X X X
시험성 X X X

마이크로커널 아키텍처는 레이어드 아키텍처와 유사하게 단순성과 전체 비용에서 강점이 있지만, 모놀리식 배포로 인해 확장성, 내고장성은 약점입니다.
모든 요청이 코어 시스템을 통해 처리되므로 퀀텀은 항상 1입니다.
그러나, 이 아키텍처는 도메인 분할과 기술 분할이 모두 가능한 유일한 아키텍처 스타일로, 강력한 도메인 대 아키텍처 동형성(domain-to-architecture isomorphism)을 제공합니다.
특히, 위치나 클라이언트별 설정이 필요한 경우, 또는 IDE처럼 커스터마이징 및 기능 신장성이 중요한 제품에 적합합니다.

마이크로커널 아키텍처는 기능을 독립적인 플러그인으로 분리할 수 있어 시험성, 배포성, 신뢰성이 평균보다 약간 높습니다.
잘 분할하면 전체 테스트 범위와 배포 리스크를 줄일 수 있습니다.
모듈성과 확장성도 평균보다 약간 높으며, 독립적인 플러그인을 통해 기능을 쉽게 추가, 삭제, 변경할 수 있어 팀이 신속하게 대응할 수 있습니다.
성능도 평균보다 약간 높은데, 보통 애플리케이션 규모를 작게 유지할 수 있고, 불필요한 기능은 해제 하는 식으로 처리 흐름을 간소화할 수 있기 때문입니다.