TL;DR
- Fougere는 하나의 도메인 선언에서 TypeScript 타입, 검증기, SQL 테이블, 폼 계약, API 표면을 도출하고, 앱 내부 실행과 별도 프로세스 실행을 지원하는 백엔드 프레임워크임.
- 엔티티의
readOnly필드는 클라이언트 입력에서 제외되며, 도메인 간ref는 공유 데이터베이스에서는 외래 키로, 분리된 데이터베이스에서는 쓰기 시 행 조회와 검증으로 처리됨. - 표준 스키마(Standard Schema) 엔티티는
@fougere/schema하나만으로tRPC,Hono,TanStack Form등에서 검증기로 사용할 수 있으며, 이때 테이블·GraphQL 타입·폼 계약은 제공되지 않음. - 핸들러에서 도메인 전이 로직과 검증을 직접 작성하고, 세션 수집기(collector)는 매개변수 타입에 따라 주입되며, 도메인은 설정 한 줄로 프로세스 간 이동이 가능함.
- 현재 알파 버전으로 공개됐으며, 스키마 변경은
fougere freeze와fougere migrate로 적용하고 타입 변경에는 직접 작성한 마이그레이션이 필요함.
Fougere
- 도메인을 작성하면 전송 계층(wire)을 포함한 나머지 요소가 도메인 선언에서 도출되는 구조임.
- 하나의 클래스에서 비즈니스 객체를 선언하면 TypeScript 타입, 검증기, SQL 테이블, 폼 계약, API 표면이 생성됨. 선언은 전송 시 JSON이므로 도메인을 별도 프로세스나 다른 언어로 옮겨도 호출 코드는 바뀌지 않음.
- 블로그의
Post엔티티는 ID, 제목, 선택적 본문, 다른 Frond의User를 가리키는 작성자 ID, 생성 시각, 초안·게시 상태, 선택적 게시 시각을 선언함. 제목 길이는 1~160자이며, 상태 기본값은draft임. readOnly는 단순한 의도 표기가 아니라 클라이언트가 해당 필드를 전송하지 못하게 함. 따라서 게시 처리는 필드 쓰기가 아니라 별도의 작업이어야 함.ref는 다른 Frond의 엔티티를 참조함. 두 Frond가 같은 데이터베이스를 쓰면 외래 키를 사용하며 추가 비용이 없음. 블로그가 자체 데이터베이스를 쓰면 두 데이터베이스 사이에 제약 조건을 공유할 수 없으므로, 쓰기 시 참조 행을 읽고 동일한 삽입을 거부함.- 프레임워크가 처리할 수 없는 세 번째 경우는 부팅 시 명시적으로 이름을 들어 알림.
- 필드가 바뀌면 테이블, 검증기, GraphQL 타입, 폼 계약이 같은 선언에서 파생되므로 선언과 불일치할 수 없음. 프레임워크가 잘못됐음을 증명할 수 있는 선언은 부팅 시 거부하고 해당 선언을 표시함.
- 변경 사항(diff)은 본인, 동료, 에이전트 중 누가 작성했는지와 관계없이 검토 가능한 형태임.
빠른 시작
pnpm에서 너무 최근에 게시된 버전이minimum-release-age설정으로 제한되는 경우, 다음 명령으로 최신 버전을 바로 실행할 수 있음:pnpm --config.minimum-release-age=0 create fougere.- 실행하면 테이블, 폼 계약, REST·GraphQL 표면,
useQuery·useCommand로 작업을 호출하는 페이지가 준비된 앱이 동작함. 위 요소는 보존해야 하는 파일로 코드 생성되지 않음.
아무것도 도입하지 않아도 됨
- 엔티티는 표준 스키마(Standard Schema)이므로 해당 규격을 받아들이는 곳에서 사용할 수 있음. 여기에는
tRPC,Hono,TanStack Form, 라우트 수준 검증에 이 규격을 채택하는 서버 프레임워크가 포함됨. npm i @fougere/schema하나만 설치하면 되며, 어댑터 패키지나 앱 내 다른 Fougere 구성 요소는 필요하지 않음.Post.pick('title', 'summary', 'body')로PostDraft를 정의하고 표준 검증 인터페이스를 호출하면, 빈 제목에 대해title경로를 가리키는 검증 이슈가 반환됨.- 다른 앱으로 가져갈 수 있는 것은 검증기뿐임. 테이블, GraphQL 타입, 폼 계약은 앱에 남으며, 나머지 축을 위해 다시 도입할 수 있는 요소는
getFields()임.
엔티티에서 도출되는 요소
- 검증: 브라우저와 파사드(façade)에서 동일한 검증기를 사용하며, 알 수 없는 키는 거부됨.
- 저장소: SQL 테이블과 추가 방식의 스키마 동기화임.
- 폼:
useFormFor(Post)가 필드, 규칙, 필드별 오류 매핑을 제공함. - API 표면:
post.list,post.create,post.publish등의 작업임. - GraphQL·REST: 타입, 입력값, 경로가 동일한 작업에서 도출됨.
- 타입: 클래스 자체가 타입임.
- 코드 생성 단계,
dist/generated, 감시자(watcher)는 없음. 선언 자체가 산출물임.
직접 작성하는 부분
- 핵심은
update()가 아니라 상태 전이이며, 전이에는 검증기가 필요함. 이 검증기는 소개된 코드 중 Fougere가 도출하지 않는 유일한 코드임. PostHandler는 목록 응답용PostCard를Post.pick(...)으로 정의하고,Crud(Post, { list: PostCard })를 확장함. 저장소를 생성자에서 받아 기본 CRUD 동작에 전달함.publish작업은 사용자, 초안 상태, 게시할 가치가 있는 본문을 검증하고 게시 시각과 상태를 함께 기록함. 이미 게시된 항목이면CONFLICT오류와Already published메시지를 반환함.user?: User는 주입 지점임. 매개변수 시그니처를 타입으로 세션을 해석하는 수집기와 대조하며, 데코레이터나 컨테이너 조회 코드는 작성하지 않음.
도메인의 이동
- Frond는 엔티티, 핸들러, 수집기, 시드를 포함하는 도메인임. 실행 위치는 한 줄로 지정되며, 프로세스 분리 여부를 바꾸는 설정도 그 한 줄임.
fougere.config.ts에서remotes: { blog: 'http://blog-node:4100' }를 설정하면 블로그 Frond가 원격으로 실행됨. 이 줄을 삭제하면 같은 앱에서 프로세스 내부 실행으로 돌아감.- 프로세스 내부 호출은 루프백 요청이 아니라 직접 메모리 실행임. 프로세스를 분리하면 호스트가 중단될 때 페이지에서 타입이 지정된 503 오류를 받고, 호스트가 돌아오면 복구됨.
- 원격 측은 TypeScript일 필요가 없음.
demos/rust-frond는 Rust로 작성된 도메인이며, 타입뿐 아니라 도메인 규칙도 TypeScript 검증기로 강제됨.
현재 알파 버전
- npm에
alpha태그로 공개된 상태임. 표면이 계속 바뀔 수 있다는 점이 버전이 담은 약속임. - 계획이 아니라 실제 실행으로 확인된 항목은 브라우저에서 검증되는 초안→게시 흐름, 일상적으로 사용하는 분리 실행, 프로덕션 빌드 양쪽에서 동일한 사용자 코드, 그리고 Fougere 앱으로 운영되는 이 사이트임.
- 알려진 제한 사항은 다음과 같음.
- 부팅 과정에서는 스키마를 쓰지 않음.
fougere freeze가 기록한 테이블, 열, 이름 변경, 삭제를fougere migrate가 적용함. - 타입을 바꾸려면 직접 작성하는 마이그레이션이 필요함.
- 계산 필드는 뷰를 지정하지 않으면 행마다 한 번씩 읽음.
- 분리된 수신기는 기본적으로 루프백에 바인딩됨. 범위를 넓히려면 서명된 봉투(signed envelope)가 필요하며, 업스트림 메시가 호출자를 이미 인증한 경우에는
allowUnsigned를 명시할 수 있음. - 전체 제한 사항은 간결함보다 정확성을 우선해 관리하는
CLAUDE.md에 정리됨.
더 알아보기
- 시작하기
- 철학
- 엔티티와 네 가지 축
- 점진적 도입
- 기존 앱에 도입하기
demos/에는 프로젝트마다 하나의 아이디어가 분리되어 있으며,nuxt-blog가 대표 데모임.- MIT 라이선스이며 chok이 제작함.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요