Biff.core: Clojure 웹 앱을 위한 시스템 구성
Source: Hacker News
제가 이전에 썼던 것처럼, Biff를 여러 개의 별도 라이브러리로 분리하고 그 과정에서 여러 가지를 바꾸고 있습니다. 12개의 라이브러리 초안을 모두 완성했으며, 이제 하나씩 다듬고 배포하려고 합니다. 첫 번째 라이브러리가 이제 준비되었습니다.
biff.core: Biff 프로젝트를 위한 시스템 구성 및 기타 인터페이스. 이것이 다른 모든 라이브러리를 연결해 주는 접착제이며, 그래서 가장 먼저 공개하는 것입니다.
오랫동안 Biff는 “모듈과 컴포넌트” 구조를 사용해 왔습니다. 프로젝트의 각 애플리케이션 네임스페이스는 “module” 맵을 노출하고, 그 모듈들에서 가져온 여러 요소들을 하나의 “system” 맵으로 합치는 보일러플레이트가 필요했으며, 시작 시에 그 시스템 맵을 “component” 함수에 넘겨 주었습니다. Biff 2도 그 구조를 유지하면서, 그 보일러플레이트를 다루기 위한 추가 기능을 제공하고 있습니다.
예시를 보려면, 아래 코드를 확인하세요. 이 코드는 모듈들의 :routes (및 :api-routes) 키를 가져와 시스템 맵에 :biff/handler 값으로 변환합니다. 저는 이런 로직을 깔끔하게 라이브러리로 추출하고 싶었고, 라이브러리 사용법이 “프로젝트에 이 모듈을 추가하세요” 정도만 되도록 만들고 싶었습니다. 즉, “그리고 메인 네임스페이스에 이 코드를 붙여넣으라”는 별도의 절차가 없도록 말이죠.
그래서 새 biff.core 라이브러리에는 “init functions”라는 개념이 들어갑니다. 이 함수들은 모듈 컬렉션을 받아 하나의 맵을 반환하고, 그 맵은 시스템 맵에 병합될 수 있습니다. 짠! 아래가 예시입니다.
Init functions는 모듈 맵 안의 :biff.core/init 키에 저장됩니다. 그래서 “모듈만 있으면 된다(컴포넌트도 마찬가지)”는 효과를 얻을 수 있죠. 여기서 주요 복잡성은 애플리케이션 코드에서 (def handler …) 변수를 정의하는 보일러플레이트가 **늦은 바인딩(late binding)**이라는 좋은 부수 효과를 제공한다는 점입니다. 모듈을 바꾸면 handler 변수가 자동으로 업데이트되고, 시스템 맵에 :biff/handler를 값이 아니라 변수(#’handler)로 지정하면, 웹 서버를 재시작하지 않아도 들어오는 Ring 요청이 최신 핸들러를 사용하게 됩니다. 이 보일러플레이트를 라이브러리 코드로 추출하면 변수 바인딩을 얻을 수 없게 됩니다.
제가 찾은 해결책은 다음과 같습니다.
- Init functions는 모듈 벡터 자체가 아니라 그 벡터의 var를 인자로 받습니다.
- 시스템 맵에서 재시작 없이 업데이트되길 원하는 모든 항목은 함수여야 합니다. 경우에 따라서는 시스템 맵에
:com.example/my-thing키를 두는 대신,:com.example/get-my-thing함수를 두어my-thing을 반환하도록 해야 합니다. - 그 함수는 모듈 var를 deref 해서 메모이제이션된 함수에 전달하고, 그 함수가 필요한 객체(예: Ring 핸들러)를 만들어 반환합니다.
다시 한 번, 아래 예시를 보세요.
결과적으로 미관상으로도 깔끔해집니다. 메인 네임스페이스는 거의 변하지 않아도 되고, 여러분이 해야 할 일은
modules와
components를 추가하는 것뿐입니다.
항상 더 통합하려는 유혹이 있습니다. 왜 별도의 components 벡터를 두나요? 모듈이 :biff.core/on-start와 :biff.core/on-stop 키를 지원하고, 이 라이프사이클 함수들 사이의 의존성을 표현할 방법이 있다면, 올바른 순서대로 호출할 수 있지 않을까요?
그 질문에 대한 답은 “그런 의존성을 표현할 필요가 없게 하기 위해서”입니다. 실제로 컴포넌트를 올바른 순서로 배치하는 것은 그리 어렵지 않으며(특히 Biff 스타터 프로젝트가 자동으로 해 주기 때문에), 그렇게 하면 컴포넌트가 어떻게 동작하는지 이해하기 쉬워집니다. 컴포넌트는 단순히 맵을 통과시키는 함수들의 연속일 뿐입니다. 상태ful 리소스가 너무 많아 관리가 힘들다면, biff.core에 넘기기 전에 컴포넌트 벡터를 자동으로 구성해 주는 레이어를 언제든 추가할 수 있습니다.
Plug: 저희 팀에서는 현재 시니어 소프트웨어 엔지니어(주로 ClojureScript와 Python을 사용) 채용 중입니다. 재생 가능 에너지 프로젝트를 위한 모델링 소프트웨어를 개발하고 있습니다.