Everlogue: 자체 자동화에 대한 신뢰를 거부하는 도서 목록
요약
Everlogue는 단순한 Goodreads 복제품을 넘어, 독서 이력을 구조화하고 공유하는 새로운 플랫폼입니다. 사용자의 기존 라이브러리 가져오기부터 커뮤니티 주도 도서 목록 성장, 그리고 유명 북 클럽의 실시간 추적까지 다양한 데이터 수집 경로를 통합했습니다. Sanity를 편집 제어 시스템으로 활용하여 신뢰할 수 있는 콘텐츠 생태계를 구축하는 것이 핵심입니다.
핵심 포인트
- 사용자 기존 라이브러리(Goodreads 등)를 CSV로 가져와 통합 관리 가능
- 커뮤니티 요청을 통해 구조화된 공유 도서 목록으로 성장시키는 메커니즘 구현
- Reese's, Oprah's Book Club 등 유명 북 클럽의 복잡한 스케줄에 맞춰 실시간 추적 기능 제공
_이 글은 Sanity Challenge, Path Two: Vibe-Code Something Strange 제출물입니다.
제가 만든 것
저는 Everlogue를 만들었습니다. 이곳은 독자가 읽는 모든 것을 담는 공간입니다. 공유 도서 목록, 개인 독서 서가, 유명인사 도서 클럽, 그리고 구조화된 Sanity 콘텐츠에 기반한 독서 동반자 'Ask Everlogue' 등이 있습니다.
하지만 저는 단순히 더 멋진 프런트엔드를 가진 또 다른 Goodreads 복제품을 만들고 싶지 않았습니다.
저는 더 어려운 질문에 답하고 싶었습니다:
만약 도서 목록이 스스로 성장할 수 있고, Sanity가 공유 콘텐츠가 될 만큼 신뢰할 수 있는 것을 결정한다면 어떨까?
이것이 저를 여러 개의 데이터 수집 경로(ingestion paths)를 중심으로 Everlogue를 구축하게 했고, Sanity는 편집 제어 시스템(editorial control system) 역할을 하게 했습니다.
대량/역사적 수집 (Bulk / historical ingestion)
독자들은 자신의 Goodreads 라이브러리를 가져올 수 있습니다. Everlogue는 CSV 파일을 구문 분석하고, 독자의 라이브러리에 있는 책들을 처리하며, 메타데이터를 일치시키거나 풍부하게 만들고(enrich), 책들을 적절한 '읽고 싶은 목록 (Want to Read)', '현재 읽는 중 (Currently Reading)', 그리고 '읽은 목록 (Read)' 서가에 배치합니다.
이를 통해 독자는 책 한 권씩 다시 만드는 대신, 기존의 독서 이력을 Everlogue로 가져올 수 있는 방법을 얻게 됩니다.
독자 주도 도서 목록 성장 (Reader-grown catalog)
독자들은 공유 도서 목록을 성장시킬 수도 있습니다.
만약 누군가 Everlogue에 없는 책을 검색하고 그것을 서가에 추가하려고 하면, 그 빠진 제목은 검토를 위한 도서 목록 요청(catalog request)이 됩니다.
모든 독자가 같은 책의 분리된 버전을 만드는 대신, Everlogue는 이 요청을 검토하여 구조화된 공유 도서 목록 콘텐츠로 바꿀 수 있습니다.
지속적/실시간 수집: 북 클럽 관찰 (Continuous / live ingestion: Book Club Watch)
가장 독특한 데이터 수집 경로는 '북 클럽 관찰(Book Club Watch)'이 되었습니다.
Everlogue는 Reese's Book Club, GMA Book Club, Read With Jenna, 그리고 Oprah's Book Club을 추적합니다. 이 컬렉션들을 최신 상태로 유지하는 것은 간단해 보이지만, 그들이 선정작을 같은 일정으로 발표하지 않는다는 것을 깨닫기 전까지는 그렇지 않습니다.
저는 각 클럽을 개별적으로 모델링했습니다:
- Reese: 동부 표준시(ET) 오전 9시, 1일~8일 동안 진행하며, 해당 월의 선정작이 기록되면 중단합니다.
- GMA: 매주 화요일 동부 표준시(ET) 오전 10시
- Read With Jenna: 월요일 + 화요일 동부 표준시(ET) 오전 9시. 오직 초월 기간에만 진행됩니다.
Oprah
월요일/수요일/금요일 오전 9시 (ET)
정기적인 확인이 필요한데, 고정된 출시 패턴이 없기 때문입니다.
Sanity Blueprint는 이러한 확인을 수행하기 위해 네 개의 Scheduled Function을 배포합니다.
하지만 제목을 찾는 것이 그것을 출판할 권한을 의미하지는 않습니다.
Book Club Watch가 잠재적인 선택지를 찾으면, 라이브(live) 책 문서를 생성하는 대신 구조화된 bookClubDiscovery 문서를 만듭니다.
자동화가 제안합니다.
편집자가 결정합니다.
또 다른 Sanity Function이 이러한 편집적 전환에 반응합니다.
이러한 구분이 Everlogue에서 가장 중요한 아키텍처 결정 중 하나가 되었습니다.
카탈로그는 자동 스크래퍼가 공유된 진실(shared truth)을 정의하도록 허용하지 않으면서 스스로 성장할 수 있습니다.
결과로 생성된 카탈로그는 또한 디스커버리 서피스(discovery surfaces)와 Ask Everlogue를 포함하여 Everlogue의 나머지 부분에 동력을 공급합니다. 저는 독서 친구가 책방에서 이야기하는 것처럼 느껴지게 하고 싶었지만, 그 추천은 Everlogue가 실제로 알고 있는 책들—즉, 만들어낸 선반이 아닌 곳—에서 나와야 했습니다.
데모
비디오 워크스루 대신, 저는 공개 앱과 그 뒤에 있는 Sanity 시스템을 보여주는 스크린샷으로 워크플로우를 문서화했습니다.
코드 (Code)
https://github.com/sphcastillo/everlogue
The Book Club Watch 인프라는 다음 파일에서 시작됩니다:
sanity.blueprint.ts
이 블루프린트는 다음을 정의합니다:
1개 Book Club Watch 봇 토큰 (robot token)
4개의 예약 함수 (Scheduled Functions)
1개 승인 트리거 문서 함수 (approval-triggered Document Function)
핵심 Watch 구현은 다음 경로에 있습니다:
src/lib/book-club-watch/
이 코드는 클럽 일정, 발견 상태(discovery state), 출처 검사(source inspection), 중복 방지(duplicate protection), 승인 상태(approval state), 그리고 게시(publishing)를 모델링합니다.
Sanity Studio는 편집 계층을 추가합니다: 검토 대기열(Review Queue), 승인/게시 실패(approved/publication failures), 거부된 발견 사항(rejected discoveries), 게시 기록(published history), 실행 기록(run history), 그리고 검토 프로세스를 위한 사용자 지정 작업(custom actions).
나의 빌드 과정 (My Build Process)
저는 Codex와 Cursor의 도움을 받아 Everlogue를 구축했으며, 가장 생산적이었던 프롬프트는
이 Goodreads CSV를 파싱하고, 독자의 선반 상태(shelf state)를 보존하며, 이미 소유한 책의 중복 생성을 방지합니다.
공식 북클럽 출처를 확인하여 무언가 변경될 때 발견(discovery)을 생성합니다.
스케줄링된 함수(scheduled function)로부터 라이브 북(live book)을 직접 절대 생성하지 않습니다.
아무것도 변경되지 않았더라도 Watch 실행 기록을 남깁니다.
이러한 제약 조건들이 중요했던 이유는 저의 초기 접근 방식 중 일부가 잘못되었기 때문입니다.
첫 번째 잘못된 가정: 월별 크론(cron)
저의 첫 직감은 한 달에 한 번 실행되는 단일 스케줄링 함수였습니다.
하지만 이는 현실과 접촉하며 살아남지 못했습니다.
네 개의 북클럽은 서로 다른 발표 패턴을 가지고 있으며, 오프라(Oprah)는 신뢰할 수 있는 월별 날짜가 아예 없습니다.
이러한 출처들을 하나의 스케줄에 억지로 넣기보다는, 출시 동작 자체를 구조화된 설정으로 모델링하고 각 워처(watcher)가 현재 확인이 의미 있는지 여부를 결정하도록 했습니다.
두 번째 잘못된 가정: 발견(discovery) = 출판(publication)
또 다른 초기 방향이 무너졌습니다:
"책을 찾았다"
→
"책을 생성하라"
저는 그것을 원하지 않았습니다.
웹페이지에서 추출된 제목은 편집상의 진실(editorial truth)이 아니라 증거일 뿐입니다.
저는 이 프로세스를 두 개의 시스템으로 분리했습니다.
발견(Discovery)은 구조화된 콘텐츠를 제안합니다.
편집 승인(Editorial approval)이 구조화된 콘텐츠를 출판합니다.
발견은 다음과 같은 상태들을 거칩니다:
discovered
↓
needs_review
↓
approved ─────→ published
│
└──────────→ rejected
Studio는 사용자가 제목, 저자, 출처 URL, 증거(evidence), 제안된 메타데이터, 기존 책 매치, 그리고 출판 모드를 검사할 수 있는 장소가 되었습니다.
오직 승인만이 출판 함수가 반응하도록 허용합니다.
출판 트리거는 또한 발견이 이미 카탈로그에 존재하는 책을 가지고 있는지 확인하여 워크플로우에 또 다른 수준의 중복 방지 기능을 제공합니다.
Studio를 넘어:
블루프린트(Blueprint)는 다음과 같이 구성되었습니다:
Book Club Watch
├── Robot Token
├── Reese Scheduled Function
├── GMA Scheduled Function
├── Read With Jenna Scheduled Function
├── Oprah Scheduled Function
└── Publish Approved Discovery
여기서 저는 가장 큰 구현 문제 중 하나에 부딪혔습니다.
처음에 제가 사용했던 Blueprint 스택은 프로젝트 범위(project-scoped)였습니다.
함수들은 로컬 테스트를 통과했고, Studio도 빌드되었으며, 번들링도 완료되었습니다. 하지만 Sanity는 예약된 함수(Scheduled Functions)가 조직 범위(organization-scoped)의 Blueprint 스택을 요구했기 때문에 배포를 거부했습니다.
크론 리소스(cron resources)들이 배포되려면, 제가 이 스택을 프로젝트 범위에서 조직 범위로 승격시켜야 했습니다.
이것은 AI가 생성한 구현 코드가 기술적으로는 그럴듯했지만 불완전했던 지점 중 하나였습니다. 배포 환경이 누락된 제약 조건을 노출시킨 것입니다.
로컬 테스트도 저를 속였습니다.
또 다른 혼란스러운 순간은 로컬 테스트에서 오프라(Oprah)의 선택을 성공적으로 찾았을 때 발생했습니다.
터미널에는 실제 결과가 표시되었지만, 제 Studio 검토 대기열(Studio Review Queue)은 비어 있었습니다.
그 이유는 저 자신의 런타임에 있었습니다:
if (context.local) { console.log(await discoverPicks(...)) return }
로컬 함수 테스트는 의도적으로 데이터셋을 변경하지 않고 발견 기능(discovery)만을 실행했습니다.
이것은 제가 처음에 동일하게 취급했던 두 가지를 분리하도록 강요했습니다:
- 소스 발견 기능이 작동하는가?
- 배포된 클라우드 워크플로우가 올바른 편집 상태를 기록하는가?
저는 그 후 실제 배포된 크론을 개발 데이터셋에 대해 테스트했습니다.
그 실행은 공식 오프라 출처에서 소피 첸 켈러(Sophie Chen Keller)의 Little Wonder를 발견했고, 다음 필드를 가진 실제 bookClubDiscovery 레코드를 생성했습니다:
bookClub
discoveredTitle
discoveredAuthors
selectionMonth
selectionDate
sourceUrl
sourceEvidence
identity
matchExplanation
status
그 상태(status)는 의도했던 대로 needs_review였습니다.
개발 환경에서 프로덕션으로 가기까지
저는 결국 Everlogue를 두 가지 데이터셋을 중심으로 표준화했습니다:
development
production
Book Club Watch 기능은 먼저 개발 데이터셋을 대상으로 활성화되었습니다.
테스트를 위해, 저는 정상적인 스케줄에 따라 며칠을 기다리는 대신 실제 클라우드 실행을 관찰할 수 있도록 오프라의 일정을 임시로 가속했습니다.
전체 흐름을 확인한 후에야, 오프라의 실제 월/수/금 스케줄을 복원했습니다.
프로덕션 환경에서 Watch를 활성화하기 전에, 전체 프로덕션 데이터셋—문서 1,644개와 에셋 327개—을 내보냈고, 또 다른 Blueprint 안전장치를 추가했습니다:
프로덕션은 WATCH_PRODUCTION_VALIDATED=true가 아니면 실행될 수 없습니다.
개발 워크플로우를 통과한 후에야 프로덕션 버전을 배포할 수 있었습니다.
실패 상태가 제품의 일부가 되었습니다.
최종 시스템은 외부 시스템이 항상 작동한다고 가장하지 않습니다.
Book Club Watch는 실행 기록을 기록하고 다음과 같은 결과들을 구분합니다:
변경 없음 (no change)
이미 기록됨 (already recorded)
건너뜀 (skipped)
실패 (failed)
발견됨 (discovered)
Studio는 발견된(discovered) 것, 출판 실패, 그리고 출판 이력을 숨기는 대신 보관합니다.
라이브 소스 중 하나가 테스트 중에 HTTP 500을 반환했습니다. Google Books의 풍부화 작업도 한 번의 발견 과정 동안 사용할 수 없었습니다.
이러한 어떤 조건도 조용히 잘못된 카탈로그 데이터를 생성하도록 허용되지 않았습니다.
그 결과는 Everlogue가 구조적으로 가장 좋아하는 부분입니다:
자동화는 웹을 검사하고 구조화된 상태를 제안할 수 있지만, 공유 카탈로그에 대한 일방적인 권한은 없습니다.
기계가 제안합니다.
편집자가 결정합니다.
Sanity가 이 둘을 연결합니다.
Sanity 프로젝트 상세 정보
프로젝트 ID: 3h0o1unw
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기






