채택할 것인가 구축할 것인가: SignalK를 위해 우리의 작동 중인 로그북을 삭제한 이유
요약
보트 에이전트를 위한 로그북 시스템을 구축하던 중, 기존 SignalK 생태계의 도구들을 검토한 결과 자체 저장 계층을 삭제하고 기존 표준을 채택하기로 결정한 과정을 다룹니다.
핵심 포인트
- 직접 구축하기보다 기존 생태계의 도구를 활용하는 것이 효율적임
- 데이터의 영속성과 상호운용성을 위해 표준화된 저장 방식(YAML) 채택
- 에이전트의 역할은 데이터 저장이 아닌 도구 인터페이스 제공에 집중
- 로컬 우선(local-first) 접근 방식의 중요성 강조
우리의 보트 에이전트(boat agents)는 음성으로 순간을 기록합니다: "이 순간을 기록해줘" → 위치, 시간, 그리고 상태 정보가 포함된 항목이 생성됩니다. logbook-mcp의 첫 번째 버전은 에이전트 머신의 SQLite를 기반으로 이를 지원했습니다. 잘 작동했습니다. 테스트도 있었고, 배포도 완료되었습니다.
그 후, 우리는 SignalK 로그북 생태계와 대조하여 이를 감사(audit)했고, 저장 계층(storage layer) 전체를 삭제했습니다.
감사 (The audit)
우리의 최우선 지침은 _직접 만들기 전에 기존 도구를 사용하거나 개선하는 것_입니다. 이를 정직하게 적용하면, 설계 시점에 한 번뿐만 아니라 생태계를 더 잘 이해하게 되었을 때 다시 한번 자신의 작동 중인 코드를 생태계와 대조하여 감사해야 함을 의미합니다. 세 가지 후보가 있습니다:
meri-imperiumi/signalk-logbook
— SignalK 서버 위에서 실행되는 반자동 전자 로그북(electronic logbook)입니다. 일별 YAML 파일, OpenAPI 사양을 갖춘 REST API, SignalK 관리 UI 내의 웹앱, 그리고 반자동 항목 생성(항해 중 매시간, 항해 시작/종료) 기능을 제공합니다. 핵심 기능은 POST /logs가 입력 텍스트만 받으면, 플러그인이 서버 측의 라이브 버스(live bus)로부터 위치, 침로(heading), 속도, 풍향, 기압을 스냅샷으로 찍어 저장한다는 점입니다.
Saillogger — 세련된 자동 항해 캡처 기능을 제공하지만, 로그가 그들의 클라우드에 저장됩니다. 프로그래밍 방식으로 읽고 써야 하는 로컬 우선(local-first) 에이전트에게는 적합하지 않은 형태입니다.
postgsail — 셀프 호스팅되는 Postgres + PostgREST + Grafana 스택입니다. 강력한 항해 분석 기능을 제공하지만, 운영해야 할 인프라 계층 전체가 필요하며, 에이전트가 작성하는 서사적 항목보다는 대시보드에 초점이 맞춰져 있습니다.
우리의 것 유지하기 — 완전한 제어권, 네트워크 의존성 없음, 제3자 리스크 없음. 하지만 우리는 이미 존재하는 것보다 더 못한 것을 다시 만들게 될 것입니다. UI도 없고, 자동 항목 생성도 없으며, 데이터 보강(enrichment)도 없습니다. 또한 로그가 보트에 남는 대신 노트북과 함께 사라지게 될 것입니다.
결정적인 논거 (The deciding argument)
선박의 로그(log)는 선박의 소유입니다. 에이전트 머신(agent machine)의 SQLite에 이를 저장하는 것은 항상 약간 잘못된 방식이었으며, 단지 우리가 그것을 입 밖으로 내어 말하지 않았을 뿐입니다. signalk-logbook은 로그 항목들을 선박 자체 서버에 사람이 읽을 수 있는 YAML 형식으로 저장합니다. 이는 이 포스트에 언급된 모든 도구가 버려지더라도 수십 년 후에 아주 쉽게 파싱(parse)할 수 있는 파일들입니다.
그러한 관점의 재정립은 실제로 '우리의 것'이 무엇인지도 명확히 해주었습니다. 그것은 로그 항목의 저장이 아니라, 에이전트 대상의 도구 인터페이스(tool surface)와 (로드맵 상의) USCG/Transport Canada 해상 근무 시간(sea-time) 계산이었습니다. 이는 생태계 내의 그 어떤 것도 수행하지 않는 기능입니다. 따라서 logbook-mcp는 약 200줄의 상태가 없는(stateless) 접착 코드(glue code)가 되었습니다. 즉, 플러그인의 REST API를 통해 mark_moment와 read_entries를 제공하며, 해상 근무(sea-service) 양식 내보내기는 별도의 저장소에 유지하는 대신 나중에 플러그인의 로그 항목으로부터 '파생'되도록 구현되었습니다. 단일 진실 공급원(Single source of truth)을 구축한 것이며, 우리가 유지 관리하는 유일한 코드는 진정으로 새로운 부분뿐입니다.
채택에 실제로 들어간 비용: 문서화되지 않은 네 가지 특이사항
타인의 플러그인을 채택하는 것은 공짜가 아닙니다. OpenAPI 명세(spec)가 전체 과정의 80%를 해결해 주었지만, 실제 서버를 대상으로 한 라이브 통합(live integration)을 통해 나머지 부분을 발견했습니다:
POST /logs가 본문 없이 빈201상태 코드만 반환합니다. 생성된 항목을 다시 돌려받지 못합니다. 따라서 "방금 무엇을 작성했는가?"를 확인하려면 해당 날짜의 데이터를 다시 가져온 뒤 가장 최신 항목을 확인해야 합니다 (자정 경기에 대비해 이전 UTC 날짜를 사용하는 폴백(fallback) 로직이 필요합니다).- "선택 사항"인
ago필드가 실제로는 필수입니다. 명세(spec)에는 선택 사항으로 표시되어 있지만, 코드상에서 15분간의 상태 버퍼(state buffer)가 비어 있지 않을 때마다buffer.get(req.body.ago)를 호출하며,buffer.get(undefined)는 오류를 발생시킵니다. 항상ago: 0을 전송하십시오. - 작성자 식별(Author attribution)이 Authorization 헤더가 아닌 쿠키를 읽습니다. 플러그인은
parseJwt(req.cookies.JAUTHENTICATION)를 통해 항목 작성자를 도출합니다. 저희 클라이언트는 토큰을 두 가지 방식으로 모두 전송합니다: 서버의 인증 게이트(auth gate)를 위한 헤더 방식과 플러그인을 위한 쿠키 방식입니다. - 모든
/plugins/*REST 경로는 관리자 권한(admin-gated)이 필요합니다. signalk-server의adminAuthenticationMiddleware가 모든 플러그인 경로를 보호합니다. 장치 액세스 토큰(device access tokens)이나 읽기/쓰기 사용자 토큰(read/write user tokens)은 부여된 권한에 상관없이 401 오류를 받게 됩니다. 에이전트의 토큰은 반드시 관리자 사용자(admin user)의 것이어야 합니다. 저희는 매우 혼란스러운 두 차례의 토큰 프로비저닝(provisioning) 과정을 거친 후, 서버 소스 코드에서 이 사실을 확인했습니다.
이 중 어느 것도 불만 사항은 아닙니다. 이는 실제 소프트웨어와 통합할 때 발생하는 일반적인 비용이며, 이제 모두 문서화되었습니다 (SPEC 및 우리가 미리 찾았더라면 좋았을 이 포스트에 기록되었습니다). 그럼에도 전체 비용은 자체 저장소, 스키마 마이그레이션(schema migrations), UI, 그리고 자동 입력 파이프라인(auto-entry pipeline)을 유지 관리하는 비용의 극히 일부에 불과했습니다.
실제 경계선은 어디인가
감사(audit)를 통해 얻은 유용한 결과물은 "채택"이 아니라, 더 명확해진 경계선이었습니다:
- 그들의 영역 (Theirs): 엔트리 저장(entry storage), 데이터 보강(enrichment), 반자동 엔트리(semi-automatic entries), 큐레이션 UI(curation UI). 우리에게는 범용적인 기능(Commodity)이지만, 그들에게는 핵심 역량(core competence)입니다.
- 우리의 영역 (Ours): 에이전트 도구 인터페이스(agent tool surface) (검증된 스키마(validated schemas), TTS에 안전한
display문자열, 기록되지 않은 순간이 기록되었다고 거짓말하지 않는 정직한 오류 상태(honest error states)), 그리고 다른 어떤 곳에서도 제공하지 않는 해상 시간/라이선싱 레이어(sea-time/licensing layer)입니다.
만약 여러분이 생태계를 알기 전에 직접 만든 작동하는 코드를 보유하고 있다면, 여전히 감사를 수행할 가치가 있습니다. 매몰 비용(sunk cost)은 실제로 존재하지만 미미합니다. 우리의 경우 단 하룻밤이었으며, 우리는 데이터베이스를 포기하는 대신 200줄의 접착 코드(glue code)와 마땅히 있어야 할 곳, 즉 보트 위에 존재하는 로그(log)를 얻었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기