이 글은 Claude Opus 5 를 이용해 번역되었으며, 이후 퇴고를 거쳤습니다.

원문: Dave Thomas, Pragmatic Programmers Newsletter — “Components and Microkernels”


저는 최근에 소프트웨어를 구조화하는 오래된 방식에 대해 새롭게 생각하는 법을 발견했습니다.

1980년대 후반, Brad Cox 는 Object Oriented Programming, an Evolutionary Approach 를 출간하면서 Objective-C 와 소프트웨어 IC(Software IC) 라는 개념을 함께 소개했습니다. 그는 제대로 작성된 소프트웨어 모듈이라면 집적회로와 비슷해야 한다고 주장했습니다. 사양서를 읽고, 모듈들을 서로 연결해서 애플리케이션을 만든다는 것입니다.

40년이 지났지만 우리는 아직 거기에 이르지 못했습니다. 우리는 외부 라이브러리를 우리 코드에 통합하는 데 많은 시간을 쓰고 있지만, 그 과정은 결코 “모듈 A 의 4번 핀을 모듈 B 의 19번 핀에 연결한다"만큼 간단하지 않습니다.

최근 저는 규모가 큰 모놀리스 하나를 작업하고 있습니다. 문제 중 하나는 배포입니다. 수백 개의 모듈 중 어느 하나만 바뀌어도 전체를 다시 배포해야 합니다. 또 하나는 업그레이드입니다. 어떤 컴포넌트 하나가 새 단장을 하면, 모든 고객이 다음 배포에서 그 새 버전을 받게 됩니다. 그저 버그 수정판만 받고 싶은 고객이라도 말입니다.

문득 이 문제를 Cox 의 소프트웨어 IC 와 마이크로커널이라는 개념을 결합해서 풀 수 있겠다는 생각이 들었습니다.


실용적인 컴포넌트#

우리 애플리케이션을 덩어리로 쪼개 봅시다. 각 덩어리는 오직 한 가지만 책임지고, 덩어리들은 서로를 호출해서 일을 처리합니다. 이 덩어리를 컴포넌트 라고 부르겠습니다.

모든 컴포넌트에는 공개된 API 가 있습니다. 그 API 에는 저수준의 것들, 즉 매개변수와 타입이 담깁니다. 그리고 간결한 문서도 함께 담깁니다. 이 컴포넌트가 무엇을 하는지, 각 API 가 무엇을 할 수 있고 무엇을 할 수 없는지, 특수한 경우와 발생 가능한 오류에 대한 설명이 들어갑니다.

컴포넌트에는 버전 번호가 있고, 자신이 의존하는 다른 컴포넌트들의 버전 목록을 가지고 있습니다.

이 컴포넌트들을 레지스트리 에 저장해 둡시다.


형태와 기능의 분리#

여기에 규칙을 하나 두겠습니다. UI 가 필요하면 UI 컴포넌트를 만들되, 그 컴포넌트에는 다른 기능을 일절 넣지 않습니다. 대신 실제 일을 하는 하나 이상의 서비스 컴포넌트에 의존하게 합니다.

웹 앱이라면, 각 UI 컴포넌트를 그 자체로 하나의 웹 앱 으로 만드는 것을 규칙으로 삼습니다. 로드될 때 그 컴포넌트는 사용할 URL 을 하나 부여받습니다. 보통은 localhost:포트 형태가 될 것입니다.

그리고 디스플레이 매니저 라는 컴포넌트를 하나 더 제공합니다. 이것은 리버스 프록시 역할을 하면서, 실행 중인 각 UI 컴포넌트를 개별 iframe 으로 다중화합니다. 최종 사용자는 각 UI 를 우리 앱의 하위 창 하나하나로 보게 됩니다.

그런데 이 모든 것을 어떻게 하나로 엮을까요? 여기서 마이크로커널이 등장합니다.


마이크로커널#

일반적으로 프로세서는 두 가지 모드로 동작합니다. 커널 모드(특권 모드) 와 사용자 모드 입니다. 커널 모드에서는 코드가 모든 것에 접근할 수 있습니다. 하부 하드웨어와 대화하는 방법이 여기에 있습니다. 사용자 모드는 그 모든 것을 감추어, 여러분의 코드를 하드웨어로부터도 다른 실행 중인 코드로부터도 격리합니다. 커널 모드는 반드시 필요하지만 위험합니다. 커널 모드의 버그 하나가 시스템을 통째로 내려앉히거나, 데이터를 대량으로 망가뜨리거나, 보안 구멍을 열어 버릴 수 있습니다. 그래서 운영체제는 자기 코드 중 커널 모드에서 도는 부분을 최소한으로 유지하도록 설계됩니다.

이것을 극단으로 밀고 간 것이 마이크로커널 입니다. OS 를 공격적으로 분할합니다. 커널에는 반드시 특권이 필요한 코드만 남깁니다. 나머지 OS 는 (파일 시스템, 고수준 디바이스 드라이버, 스케줄러 같은 것들까지 포함해서) 평범한 사용자 모드에서 돕니다.

우리 앱을 이런 식으로 설계해 봅시다.

우리의 마이크로커널은 최종 시스템을 부트스트랩하는 데 필요한 최소한의 코드 입니다. 이 커널이 할 수 있는 일은 레지스트리에서 컴포넌트를 로드하는 것, 그리고 컴포넌트들이 서로를 호출할 수 있도록 교환기 역할을 하는 것뿐입니다.

컴포넌트들은 정적으로 링크되지 않습니다. 대신 컴포넌트 사이의 호출은 이름으로 이루어집니다. 이 호출은 커널을 통과하는데, 커널은 그 이름을 어떤 컴포넌트로 풀어낼지뿐 아니라 그 컴포넌트의 어느 버전을 쓸지 도 알고 있습니다.

모든 호출이 커널을 지나가기 때문에, 여러 텔레메트리를 한곳에서 다룰 수 있게 됩니다. 로깅, 성능 측정 같은 것들 말입니다. 각 호출을 OTel 트레이스로 감쌀 수도 있습니다. 그리고 이 경계는 고수준 접근 제어를 적용하기에 훌륭한 자리이기도 합니다.


유연한 배포#

이런 세상에서 배포는 어떤 모습일까요?

여러분은 더 이상 모든 코드를 하나의 산출물로 링크하지 않습니다. 대신 버전이 매겨지고 서명된 컴포넌트들로 레지스트리를 채웁니다. 이 컴포넌트들은 불변입니다. 무언가를 바꾸려면 반드시 버전 번호를 올려야 합니다.

그런 다음 애플리케이션을 기본 마이크로커널과 최초 매니페스트 의 형태로 배포합니다. 매니페스트에는 로드해야 할 최상위 컴포넌트들과 그 설정 데이터가 담깁니다. 커널이 시작되면 컴포넌트들을 로드하고, 각자에게 설정을 건네주고, 서로 엮어 줍니다.

이제 컴포넌트 하나를 업데이트해 봅시다. 새 버전을 레지스트리에 배포하고, 갱신된 매니페스트를 밀어 넣습니다. 그러면 커널이 이것을 현재 실행 중인 구성과 대조해서, 필요하면 새 컴포넌트를 로드합니다. 고객마다 서로 다른 컴포넌트를 업데이트하고 싶다고요? 매니페스트만 따로 관리하면 됩니다.

물론 제가 그린 그림만큼 장밋빛이지는 않습니다. 데이터 마이그레이션 같은 것은 계획이 필요합니다. 하지만 주의를 기울이고 데이터 접근 컴포넌트 자체를 적절히 계층화한다면, 버전 관리만으로도 상당 부분을 처리할 수 있습니다.


게다가 에이전트 친화적입니다#

각 컴포넌트가 API 를 공개하되 메서드 이름과 매개변수뿐 아니라 각각이 무엇을 하는지에 대한 짧은 설명 까지 함께 공개한다고 했던 것을 기억하실 겁니다. 그런데 그것이 바로 AI 가 컴포넌트를 도구로 호출하기 위해 필요로 하는 것과 정확히 일치합니다. MCP 하나로 처리할 수 있습니다. 제가 선호하는 방식은 이 환경을 알고 있는 기본적인 에이전트 루프를 작성해서, LLM 에게 적절한 컨텍스트를 제공하는 것입니다.

그 결과, 에이전트는 여러분이 접근을 허용한 모든 애플리케이션 기능을 호출할 수 있게 됩니다. 여러분이 추가로 작성해야 하는 코드는 하나도 없습니다.

도시의 현재 날씨를 가져오는 컴포넌트를 하나 작성해 보십시오. 그런 다음 에이전트 UI 를 띄우고 “런던의 현재 날씨는 어때?“라고 물어보십시오. 섭씨 기온이 돌아옵니다. “그걸 야드파운드 단위로 보여줘.” 에이전트는 그 API 를 알고 있고, 따라서 무엇을 변환해야 하는지도 알고 있습니다.


별로 새로울 게 없다고요?#

이런 모델이 동적으로 로드되는 공유 라이브러리(.dll, .so, .dylib 같은 것들)와 크게 다르지 않다고 반박하실 수도 있습니다. 부분적으로는 맞는 말입니다. 하지만 공유 라이브러리는 다른 문제를 겨냥한 것입니다. 20개의 앱이 모두 여러분의 DateFinder 라이브러리를 쓴다면, 한 번만 로드해서 공유하게 하는 편이 낫지 않겠느냐는 문제 말입니다.

제가 여기서 이야기하는 것은 그 능력을 포함하되, 거기서 애플리케이션 수준까지 더 밀고 나갑니다. 모든 것이 동적이고, (거의) 모든 것이 무중단 교체 가능하며, 모든 것이 발견 가능합니다.


의외로 쉽습니다#

바탕이 되는 마이크로커널은 Claude 와 함께라면 이틀 정도의 작업입니다. 컴포넌트가 자리 잡을 프레임워크가 반나절 더, 레지스트리도 비슷한 정도입니다. 그다음에는 모두가 사용하지만 커널에 들어갈 자격은 없는 핵심 컴포넌트들을 만들어 냅니다. 디스플레이 매니저, 로깅, 컴포넌트 관리, 오류 처리 같은 것들 말입니다. 그러면 이제 여러분에게는 앱을 작성할 수 있는 근사하고 유연하며 동적인 환경이 생깁니다.