Overview
One definition, HTTP server and client SDKs, always in sync. Client developers happy. Backend developers too.
클라이언트와 API를 수동으로 맞추는 게 지쳐 아예 프레임워크를 만들었습니다.
⚠️ v0.2.0 프로덕션 미적용 단계입니다. 브레이킹 체인지가 있을 수 있습니다.
The Solution
onedef에서는 struct 자체가 API 계약이자 선언입니다.
type GetUserAPI struct {
onedef.GET `path:"/users/{id}"`
Deps struct{ Service UserService }
Request struct{ ID string }
Response User
}
func (h *GetUserAPI) Handle(ctx context.Context) error {
service := h.Deps.Service
h.Response = service.FindUser(h.Request.ID)
return nil
}
이 struct 하나로 다음이 전부 해결됩니다:
GET /users/{id}: 라우팅 등록, 경로 파라미터 파싱, 응답 직렬화- 클라이언트 SDK: 같은 정의에서 생성되는 타입 안전 SDK. 드리프트 원천 차단
- 가독성: 엔드포인트의 모든 정보가 한 struct에 모여있어, 사람과 LLM 모두 한눈에 파악 가능
struct를 수정하면 모든 게 함께 바뀝니다. 구조적으로 동기화가 깨질 수 없습니다.
The Problem
아마 훌륭한 프로젝트를 성공시킨 엔지니어 분들은 OpenAPI가 있는데 "굳이?" 싶으실 수 있습니다.
분명 OpenAPI는 문서화 문제를 풀었습니다. OpenAPI로 API 스펙을 기술할 수는 있습니다.
하지만 이는 클라이언트에서 그대로 사용할 수 있다는 의미는 아닙니다.
보통 이런 식으로 흘러갑니다. 백엔드 팀이 스펙을 만들고 그 앞에 Swagger UI나 Redoc을 붙입니다. API는 기술적으로 문서화되었습니다.
하지만 클라이언트 개발자는 여전히 엔드포인트, 스키마, nullable 필드, 에러 구조, 제너레이터가 뱉은 이상한 네이밍을 하나하나 읽어가며 API가 실제로 어떻게 동작하는지 직접 파악해야 합니다.
그것도 부족하면 결국 백엔드 코드를 직접 까봅니다.
그다음엔 스키마와 싸우기 시작합니다. 생성된 클라이언트를 튜닝하고, 그 위에 글루 코드를 씌웁니다.
이게 어느새 업계 표준 워크플로우가 되어버렸습니다.
클라이언트 개발자, 백엔드 개발자, Founding Engineer, Lead를 거치면서 매번 같은 문제를 겪었습니다.
저는 이 반복되는 노가다에 지쳐 onedef를 만들었습니다.
Client SDK
현재 Dart를 타겟으로 지원하며, TypeScript/Swift/Kotlin 등을 염두에 두고 있습니다.
SDK 생성은 직접 설계한 언어 독립적 IR(중간 표현) 기반으로 구동되기에 각 언어별 코드 생성기만 구현하면 됩니다.
Dart 레퍼런스 구현이 완성된 지금, LLM의 도움으로 새로운 언어 타겟 추가는 그 어느 때보다 현실적입니다.