Hello! I'm Hong Minhee (洪 民憙), an open source software engineer in my late 30s, living in Seoul, Korea. I'm bisexual and non-binary (they/them), and an enthusiastic advocate of free/open source software and the fediverse.
I work full-time on @fedify, an ActivityPub server framework in TypeScript, funded by @sovtechfund. I'm also the creator of @hollo, a single-user ActivityPub microblog; @botkit, an ActivityPub bot framework; Hackers' Pub, a fediverse platform for software developers; and LogTape, a logging library for JavaScript and TypeScript.
I have a long interest in East Asian languages (CJK) and Unicode. I post mostly in English here, though occasionally in Japanese or in mixed-script Korean (國漢文混用體), a traditional writing style that interleaves Chinese characters with the native Korean alphabet. Wanting to write in that style was actually one of the reasons I joined the fediverse. Feel free to talk to me in English, Korean, Japanese, or even Literary Chinese!
安寧하세요! 저는 서울에 살고 있는 30代 後半의 오픈 소스 소프트웨어 엔지니어 洪民憙입니다. 兩性愛者(bisexual)이자 논바이너리(non-binary)이며, 自由·오픈 소스 소프트웨어(F/OSS)와 聯合宇宙(fediverse)의 熱烈한 支持者이기도 합니다.
STF(@sovtechfund)의 支援을 받아 TypeScript用 ActivityPub 서버 프레임워크 @fedify 開發에 專業으로 任하고 있습니다. 그 外에도 싱글 유저用 ActivityPub 마이크로블로그 @hollo, ActivityPub 봇 프레임워크 @botkit, 소프트웨어 開發者를 위한 聯合宇宙 플랫폼 Hackers' Pub, JavaScript·TypeScript用 로깅 라이브러리 LogTape 等의 製作者이기도 합니다.
東아시아 言語(이른바 CJK)와 Unicode에도 關心이 많습니다. 이 計定에서는 主로 英語로 포스팅하지만, 때때로 日本語나 國漢文混用體 韓國語로도 씁니다. 聯合宇宙에 오게 된 動機 中 하나가 바로 國漢文混用體로 글을 쓰고 싶었기 때문이기도 하고요. 韓國語, 英語, 日本語, 아니면 漢文으로도 말을 걸어주세요!
사실 완벽하게 동작하지는 않고, 개인적으로 쓰기에 적당히 만족하는 수준에서만 동작하는 코드라, 공유하기에 부끄럽기도 하고 코드도 초 간단합니다만,
누군가 맥에 익숙하지만 윈도도 쓰는 사용자에게 도움이 되는 마음과,
Hackers’ Pub 의 부흥(?) 을 기원하면서.
해당 AutoHotKey 스크립트 코드를 공유합니다.
; Alt + 백틱 키를 눌렀을 때 실행되는 코드; MacOS 의 CMD + ` (Backtick) 키로 동일 프로그램내 창전환을 윈도에서 구현. !`::{ focusedWindow := WinExist("A") if !focusedWindow return focusedPID := WinGetPID(focusedWindow) focusedPName := WinGetProcessName(focusedWindow) focusedClass := WinGetClass(focusedWindow) focusedTitle := WinGetTitle(focusedWindow) ; Get all windows of the same class and process windows := [] for window in WinGetList() { if (WinGetPID(window) = focusedPID && WinGetTitle(window) != focusedTitle) { windows.Push(window) } } if (windows.Length > 1) { WinActivate(windows[2]) } else if (windows.Length = 1) { WinActivate(windows[1]) }}
실행 방법은 위의 코드를 임의의 ahk 확장자를 가진 파일로 작성하여 AutoHotKey 로 실행시키면 끝입니다. AutoHotkey 를 통해 해당 스크립트를 실행파일 (exe) 로 컴파일 할 수도 있습니다.
사실 최근 윈도를 메인으로 쓰는 1년 동안, 중간에 간간히 맥을 메인으로 쓰기도 했습니다. ↩︎
오토핫키(AutoHotkey)는 윈도우에서 키보드 단축키 설정, 간단한 매크로 제작, 자동화 등을 목적으로 하는 무료 오픈 소스 기반의 스크립트 언어이다. 간단한 프로그램 제작에 특화된 직관적인 문법을 갖추고 있으며 마우스와 키보드 제어, 화면에서 이미지 검색, 윈도우 창 조작, 간단한 GUI 제작 등 언어의 목적에 맞는 기능이 내장되어 있다. 출처 : 나무위키 (https://namu.wiki/w/AutoHotkey) ↩︎
A huge THANK YOU to everyone who contributed in any way - from testing and submitting bug reports through to designing, coding, fixing, packaging, testing some more, translating, documenting, hosting, administration, so many people, so much work, so much to be thankful for!
Welcome to GIMP 3.0!
ALT text detailsThe GIMP welcome dialogue shows the splash screen (sunset, water, hills), says, You installed GIMP 3.0.0, and offers some choices such as visiting the Web site, tutorials, personalizing GIMP, contributing, creating new images, seeing the release notes and more.
- Built-in OpenTelemetry support for span context propagators (tracecontext, baggage) - Built-in OTel tracing for node:http.request - LSP now starts the TypeScript server lazily
꾸준히 느끼는 거지만 "법은 지켜가며 해야지" "남에게 피해를 주니까 다들 싫어하는 거다" 이런 말 들을 때마다 '법이 부당할 수 있다는 의심'을 살면서 깊게 하지 않으셨군요! '그럴 수 있는 환경'에서 살아오셨군요! 법을 지키기만 하면 자신은 '안전하고 공정한 사회'에 있을 수 있다는 착각을 할 수 있다는 게 얼마나 큰 '권리'인지 자각하지 못 한다는 점까지가 당신이 특권층이라는 증거입니다. 법을 지키며 시위할 때 말을 들어주기나 한답니까? 목소리가 닿긴 해요? 법을 어기는 건 언제나 마지막 최후의 보루로 둬요. 누가 법을 어기는 걸 쉽게 합니까. 누가 유치장에 가고 싶어 합니까. 누가 그 힘든 길을 걷고 싶겠냐고요. 법이 존재를 인식하지 못 하고, 인식은 하지만 부정하고, 부정하여 내쫓고자 하고, 그리하여 존재 자체가 불법이 되는 경우가 얼마나 많은데… 참 안온하게 자라셨습니다.
이주민의 정치발언에 대한 제재는 그래도 지난 3개월간 많은 논의가 이루어졌고 단순히 잘못됨을 인지하는 걸 넘어서서 어떻게 바꾸어나가야 할 지에 대한 논의까지 이어졌던 걸로 기억해서 그래도 긍정적으로 보고 있어요 아쉬움과 함께 한 걱정과 우려가 존재하지만, 그만큼 관심 갖는 사람들도 많이 늘었으니까… 희망을 거름삼아 걱정과 우려가 더 커지지 않도록 해야겠죠…
역시 난 2025년 현재에 중국인이라는 이유로 린치하고 있는 이 나라를 규탄해야한다고 생각해 (본론)
반중, 중국 혐오세력이 중국의 지시를 받았다, 저들은 중국인이다 따위의 혐오발언, 날조와 선동을 하는 것에 대해 우리는 "중국인이 아니다"라는 '해명'을 할 게 아니라 "중국인이면 뭐 어떠냐"라는 '대응'이 필요한데 아직은 전자의 발언이 적잖이 보여서 아쉬움이 있음 중국인이 정치에 참여하면 안 돼? '자국민'인 '한국인'만 한국 정치에 참여 가능해? 중국인이 내는 건강보험 적자라고 해서 중국인이 우리 사회의 좀먹은 부분이고 사람들이 거부감 갖게 만들고, 나중에 가서야 통계 산출에 오류가 있었다 사실 흑자였다라고 하며 사과 한 마디 없던 정부가 존재하는 나라답기도…
어제 인종차별 철폐 기념대회에서 제일 마음 아팠던 말이 이주노동자들에게서 건보 흑자 나는 이유가 뭔 줄 아느냐 병원 갈 시간이 없어서라고 하셨단 말임 내가 아는 이주노동자들은 식당에서 많이 일하는데 식당 노동은 주 6일 12시간 근무가 일상화되어있고, 연차조차 존재하지 않아서 병원에 갈 시간이 존재하지 않음 그래서 좀 속상…
Nix를 보며 알수있는건, 사람들이 메타프로그래밍을 하기 좋은 언어로 메타프로그래밍을 하는게 아니라, 런타임이 좋은 언어로 메타프로그래밍을 한다는 것이다.
Nix의 런타임이 좋다는건 일반적인 의미에서(성능이 빠르다거나) 좋다기보다는 '재현가능한 캐싱되는 빌드'라는 런타임이 아주 많은 동작을 커버하는데 Nix가 그걸 구현했다는 얘기다. 그러니까 사람들은 큰 프로그램을 쌓아올릴 대들보가 될만한 런타임이 있으면 거기서 부터 메타프로그래밍을 시작해버린다. Nix가 언어는 구리고(애초에 엄청 잘만들려고 한거같지도 않음) 메타프로그래밍을 잘하기위한 어떠한 장치도 없음에도 가장 아래에 위치할수있어서 그 역할이 맡겨져버린다.
그래서 유용한 런타임과 오브젝트 언어(또는 DSL)을 표현할 문법에 대한 좋은 아이디어가 있으면, 좀더 나은 메타프로그래밍을 하기위한 언어를 만들수 있을거라고 생각한다.
Features: * Look at your timelines, any attributes of posts and whatnot! creation dates are set correctly, even, probably, sometimes! * Post by putting text into /posts/new! whatever you put there is posted on file close! * Reblog a post by copying it to /posts/reblogged (probably breaks a lot) * Look at media attachments! they're automatically downloaded if you try to open the file!
사실 알고보니 이것도, 저것도 모나드였다... 하는 예시는 많은데 Category의 예시는 뭐가 있을까? 그럼 설명이 훨씬 편해질텐데 말이다.
좀 인위적이지만 쉬운 예시를 하나 만들어보자면, 어떤 함수의 실행에 비용을 부여하는 것이다.
data Costful a b = Costful (a -> IO b) Intf :: Costful Int Stringg :: Costful String Bool
요런 정의를 생각해볼때
f . g는 f와 g의 동작은 합성하고, 비용은 +한 것이 될것이다.
instance Category where Costful f c1 . Costful g c2 = Costful (f . g) (c1 + c2)
요렇게 말이다.
이때 f . g의 비용은 함수를 실행하기 전에도 알수있다.
반면 그냥 f, g를 모나딕한 함수로 정의하고 f >=> g 이런식으로 합성했을땐, 함수를 실제로 실행하기 전에는 비용을 알수 없다. >=> 또는 >>=의 정의를 생각해보면 쉽게 알수 있다.
Category 인스턴스는 정적인 정보를 추가로 가지고 있는 함수, 또는 함수보다 표현력이 약한데 비스무리한거(그래서 정적인 정보가 더많은) 것을 다룰때 유용하다.
TypeScript로 백엔드 서버를 개발하면서 적절한 ORM 선택은 항상 중요한 결정 중 하나입니다. 최근 제 프로젝트에서 Drizzle ORM과 Kysely를 모두 사용해 볼 기회가 있었는데, 개인적으로는 Drizzle ORM이 더 편리하고 생산성이 높았던 경험을 공유하고자 합니다.
두 ORM에 대한 간략한 소개
Drizzle ORM은 TypeScript용 ORM으로, 타입 안전성과 직관적인 API를 강점으로 내세우고 있습니다. 스키마 정의부터 마이그레이션, 쿼리 빌더까지 풀스택 개발 경험을 제공합니다.
Kysely는 “타입 안전한 SQL 쿼리 빌더”로 자신을 소개하며, 타입스크립트의 타입 시스템을 활용해 쿼리 작성 시 타입 안전성을 보장합니다.
두 도구 모두 훌륭하지만, 제 개발 경험에 비추어 볼 때 Drizzle ORM이 몇 가지 측면에서 더 편리했습니다.
이 타입 정의는 TypeScript 코드에서 타입 안전성을 제공하지만, 이 타입 정의만으로는 CREATE TABLE SQL을 생성할 수 없다는 것이 결정적인 단점입니다. 실제로 테이블을 생성하려면 별도의 SQL 스크립트나 마이그레이션 코드를 작성해야 합니다. 이는 타입과 실제 데이터베이스 스키마 간의 불일치 가능성을 높입니다.
Drizzle의 접근 방식이 데이터베이스 스키마와 TypeScript 타입을 더 긴밀하게 연결해주어 개발 과정에서 혼란을 줄여주었습니다.
마이그레이션 경험
Drizzle ORM의 마이그레이션 도구(drizzle-kit)는 정말 인상적이었습니다. 스키마 변경사항을 자동으로 감지하고 SQL 마이그레이션 파일을 생성해주는 기능이 개발 워크플로우를 크게 개선했습니다:
npx drizzle-kit generate:pg
이 명령어 하나로 스키마 변경사항에 대한 마이그레이션 파일이 생성되며, 이를 검토하고 적용하는 과정이 매우 간단했습니다.
반면 Kysely의 마이그레이션은 본질적으로 수동적입니다. 개발자가 직접 마이그레이션 파일을 작성해야 하며, 스키마 변경사항을 자동으로 감지하거나 SQL을 생성해주는 기능이 없습니다:
이러한 수동 방식은 복잡한 스키마 변경에서 실수할 가능성이 높아지고, 특히 큰 프로젝트에서는 작업량이 상당히 증가할 수 있었습니다.
하지만 Kysely의 마이그레이션에도 두 가지 중요한 장점이 있습니다:
TypeScript 기반 마이그레이션: Kysely의 마이그레이션 스크립트는 TypeScript로 작성되기 때문에, 마이그레이션 로직에 애플리케이션 로직을 통합할 수 있습니다. 예를 들어, S3와 같은 오브젝트 스토리지의 데이터도 함께 마이그레이트하는 복잡한 시나리오를 구현할 수 있습니다. 반면 Drizzle ORM은 SQL 기반 마이그레이션이므로 이러한 통합이 불가능합니다.
양방향 마이그레이션: Kysely는 up과 down 함수를 모두 정의하여 업그레이드와 다운그레이드를 모두 지원합니다. 이는 특히 팀 협업 환경에서 중요한데, 다른 개발자의 변경사항과 충돌이 발생할 경우 롤백이 필요할 수 있기 때문입니다. Drizzle ORM은 현재 업그레이드만 지원하며, 다운그레이드 기능이 없어 협업 시 불편할 수 있습니다.
참고로, Python 생태계의 SQLAlchemy 마이그레이션 도구인 Alembic은 훨씬 더 발전된 형태의 마이그레이션을 제공합니다. Alembic은 비선형적인 마이그레이션 경로(브랜치포인트 생성 가능)를 지원하여 복잡한 팀 개발 환경에서도 유연하게 대응할 수 있습니다. 이상적으로는 JavaScript/TypeScript 생태계의 ORM도 이러한 수준의 마이그레이션 도구를 제공하는 것이 바람직합니다.
두 ORM 모두 쿼리 작성을 위한 API를 제공하지만, Drizzle의 접근 방식이 더 직관적이고 관계형 모델을 활용하기 쉬웠습니다:
// Drizzle ORM - db.query 방식으로 관계 활용const result = await db.query.posts.findMany({ where: eq(posts.published, true), with: { user: true // 게시물 작성자 정보를 함께 조회 }});// 결과 접근이 직관적이고 타입 안전함console.log(result[0].title); // 게시물 제목console.log(result[0].user.name); // 작성자 이름 - 객체 구조로 명확하게 구분됨console.log(result[0].user.id); // 작성자 ID - 게시물 ID와 이름이 같아도 문제 없음// Kyselyconst result = await db .selectFrom('posts') .where('posts.published', '=', true) .leftJoin('users', 'posts.userId', 'users.id') .selectAll();// 결과 접근 시 칼럼 이름 충돌 문제console.log(result[0].id) // 오류: posts.id와 users.id 중 어떤 것인지 모호함console.log(result[0].name) // 오류: 둘 다 name 칼럼이 있다면 모호함
Drizzle의 접근 방식이 테이블과 컬럼을 참조할 때 타입 안전성을 더 강력하게 보장하고, 관계를 활용한 쿼리 작성이 더 직관적이었습니다.
특히 여러 테이블 조인 시 동일한 이름의 칼럼 처리 부분에서 Drizzle ORM이 훨씬 더 편리했습니다. 이는 제 개발 경험에서 가장 중요한 차이점 중 하나였습니다.
// Drizzle ORM - 동일 이름 칼럼 처리const result = await db.query.posts.findMany({ with: { user: true // posts.id와 users.id가 모두 있지만 자동으로 구분됨 }});// 결과에 자연스럽게 접근 가능console.log(result[0].id); // 게시물 IDconsole.log(result[0].user.id); // 사용자 ID - 명확하게 구분됨console.log(result[0].user.name); // 사용자 이름// Kysely - 동일 이름 칼럼 처리를 위해 별칭 필요const result = await db .selectFrom('posts') .leftJoin('users', 'posts.userId', 'users.id') .select([ 'posts.id as postId', // 별칭 필수 'posts.title', 'posts.content', 'users.id as userId', // 별칭 필수 'users.name as userName', // 칼럼 이름이 같을 수 있으므로 별칭 필수 'users.email as userEmail' // 일관성을 위해 모든 사용자 관련 칼럼에 접두어 필요 ]);// 별칭을 통한 접근console.log(result[0].postId); // 게시물 IDconsole.log(result[0].userId); // 사용자 IDconsole.log(result[0].userName); // 사용자 이름
Drizzle ORM은 테이블과 칼럼을 객체로 참조하기 때문에, 동일한 이름의 칼럼이 있어도 자연스럽게 계층 구조로 처리되며 타입 추론도 정확하게 작동합니다. 반면 Kysely에서는 문자열 기반 접근 방식 때문에 별칭을 수동으로 지정해야 하는 경우가 많았고, 복잡한 조인에서 이런 작업이 번거로워졌습니다. 특히 여러 테이블에 같은 이름의 칼럼이 많을수록 모든 칼럼에 명시적인 별칭을 지정해야 하는 불편함이 있었습니다.
또한 Drizzle ORM은 결과 타입을 자동으로 정확하게 추론해주어 별도의 타입 지정 없이도 안전하게 결과를 사용할 수 있었습니다.
Kysely의 장점
물론 Kysely도 여러 강점이 있습니다:
더 가벼운 구조: 필요한 기능만 포함할 수 있는 모듈화된 구조
SQL에 더 가까운 접근: SQL 구문에 매우 충실한 API 설계
유연성: 복잡한 쿼리에서 때로 더 유연한 작성이 가능
또한 앞서 언급했듯이, Kysely의 TypeScript 기반 마이그레이션과 양방향(up/down) 마이그레이션 지원은 특정 상황에서 Drizzle ORM보다 우위에 있는 기능입니다.
SQLAlchemy와의 비교 및 앞으로의 기대
JavaScript/TypeScript 생태계의 ORM을 이야기하기 전에, 여러 언어 중에서도 Python의 SQLAlchemy는 특별한 위치를 차지합니다. 개인적으로 여태 사용해본 다양한 언어의 ORM 중에서 SQLAlchemy가 가장 기능이 풍부하고 강력하다고 느꼈습니다. 복잡한 쿼리 구성, 고급 관계 매핑, 트랜잭션 관리, 이벤트 시스템 등 SQLAlchemy의 기능은 정말 방대합니다.
Drizzle ORM은 JavaScript 생태계에서 매우 인상적인 발전을 이루었지만, 아직 SQLAlchemy의 경지에는 이르지 못했다고 생각합니다. 특히 다음과 같은 부분에서 SQLAlchemy의 성숙도와 기능 풍부함이 돋보입니다:
두 ORM 모두 훌륭한 도구이지만, 제 개발 스타일과 프로젝트 요구사항에는 Drizzle ORM이 더 잘 맞았습니다. 특히 스키마 정의의 직관성, 강력한 마이그레이션 도구, 그리고 전반적인 개발자 경험 측면에서 Drizzle ORM이 더 생산적인 개발을 가능하게 해주었습니다.
동일 이름 칼럼 처리와 같은 실질적인 문제에서 Drizzle ORM의 객체 기반 접근 방식이 가져다주는 편리함은 실제 프로젝트에서 큰 차이를 만들었습니다.
ORM 선택은 결국 프로젝트 특성과 개인 선호도에 크게 좌우됩니다. 새로운 프로젝트를 시작한다면 두 도구 모두 간단히 테스트해보고 자신의 워크플로우에 더 적합한 것을 선택하는 것이 좋겠지만, 제 경우에는 Drizzle ORM이 명확한 승자였습니다.
앞으로 Drizzle ORM이 더욱 발전하여 SQLAlchemy 수준의 풍부한 기능과 유연성을 제공하게 되길 바랍니다. JavaScript/TypeScript 생태계에도 그런 수준의 강력한 ORM이 있으면 좋겠습니다. 다행히도 Drizzle ORM은 계속해서 발전하고 있으며, 그 발전 속도를 보면 기대가 큽니다.
TypeScript로 백엔드 서버를 개발하면서 적절한 ORM 선택은 항상 중요한 결정 중 하나입니다. 최근 제 프로젝트에서 Drizzle ORM과 Kysely를 모두 사용해 볼 기회가 있었는데, 개인적으로는 Drizzle ORM이 더 편리하고 생산성이 높았던 경험을 공유하고자 합니다.
두 ORM에 대한 간략한 소개
Drizzle ORM은 TypeScript용 ORM으로, 타입 안전성과 직관적인 API를 강점으로 내세우고 있습니다. 스키마 정의부터 마이그레이션, 쿼리 빌더까지 풀스택 개발 경험을 제공합니다.
Kysely는 “타입 안전한 SQL 쿼리 빌더”로 자신을 소개하며, 타입스크립트의 타입 시스템을 활용해 쿼리 작성 시 타입 안전성을 보장합니다.
두 도구 모두 훌륭하지만, 제 개발 경험에 비추어 볼 때 Drizzle ORM이 몇 가지 측면에서 더 편리했습니다.
이 타입 정의는 TypeScript 코드에서 타입 안전성을 제공하지만, 이 타입 정의만으로는 CREATE TABLE SQL을 생성할 수 없다는 것이 결정적인 단점입니다. 실제로 테이블을 생성하려면 별도의 SQL 스크립트나 마이그레이션 코드를 작성해야 합니다. 이는 타입과 실제 데이터베이스 스키마 간의 불일치 가능성을 높입니다.
Drizzle의 접근 방식이 데이터베이스 스키마와 TypeScript 타입을 더 긴밀하게 연결해주어 개발 과정에서 혼란을 줄여주었습니다.
마이그레이션 경험
Drizzle ORM의 마이그레이션 도구(drizzle-kit)는 정말 인상적이었습니다. 스키마 변경사항을 자동으로 감지하고 SQL 마이그레이션 파일을 생성해주는 기능이 개발 워크플로우를 크게 개선했습니다:
npx drizzle-kit generate:pg
이 명령어 하나로 스키마 변경사항에 대한 마이그레이션 파일이 생성되며, 이를 검토하고 적용하는 과정이 매우 간단했습니다.
반면 Kysely의 마이그레이션은 본질적으로 수동적입니다. 개발자가 직접 마이그레이션 파일을 작성해야 하며, 스키마 변경사항을 자동으로 감지하거나 SQL을 생성해주는 기능이 없습니다:
이러한 수동 방식은 복잡한 스키마 변경에서 실수할 가능성이 높아지고, 특히 큰 프로젝트에서는 작업량이 상당히 증가할 수 있었습니다.
하지만 Kysely의 마이그레이션에도 두 가지 중요한 장점이 있습니다:
TypeScript 기반 마이그레이션: Kysely의 마이그레이션 스크립트는 TypeScript로 작성되기 때문에, 마이그레이션 로직에 애플리케이션 로직을 통합할 수 있습니다. 예를 들어, S3와 같은 오브젝트 스토리지의 데이터도 함께 마이그레이트하는 복잡한 시나리오를 구현할 수 있습니다. 반면 Drizzle ORM은 SQL 기반 마이그레이션이므로 이러한 통합이 불가능합니다.
양방향 마이그레이션: Kysely는 up과 down 함수를 모두 정의하여 업그레이드와 다운그레이드를 모두 지원합니다. 이는 특히 팀 협업 환경에서 중요한데, 다른 개발자의 변경사항과 충돌이 발생할 경우 롤백이 필요할 수 있기 때문입니다. Drizzle ORM은 현재 업그레이드만 지원하며, 다운그레이드 기능이 없어 협업 시 불편할 수 있습니다.
참고로, Python 생태계의 SQLAlchemy 마이그레이션 도구인 Alembic은 훨씬 더 발전된 형태의 마이그레이션을 제공합니다. Alembic은 비선형적인 마이그레이션 경로(브랜치포인트 생성 가능)를 지원하여 복잡한 팀 개발 환경에서도 유연하게 대응할 수 있습니다. 이상적으로는 JavaScript/TypeScript 생태계의 ORM도 이러한 수준의 마이그레이션 도구를 제공하는 것이 바람직합니다.
두 ORM 모두 쿼리 작성을 위한 API를 제공하지만, Drizzle의 접근 방식이 더 직관적이고 관계형 모델을 활용하기 쉬웠습니다:
// Drizzle ORM - db.query 방식으로 관계 활용const result = await db.query.posts.findMany({ where: eq(posts.published, true), with: { user: true // 게시물 작성자 정보를 함께 조회 }});// 결과 접근이 직관적이고 타입 안전함console.log(result[0].title); // 게시물 제목console.log(result[0].user.name); // 작성자 이름 - 객체 구조로 명확하게 구분됨console.log(result[0].user.id); // 작성자 ID - 게시물 ID와 이름이 같아도 문제 없음// Kyselyconst result = await db .selectFrom('posts') .where('posts.published', '=', true) .leftJoin('users', 'posts.userId', 'users.id') .selectAll();// 결과 접근 시 칼럼 이름 충돌 문제console.log(result[0].id) // 오류: posts.id와 users.id 중 어떤 것인지 모호함console.log(result[0].name) // 오류: 둘 다 name 칼럼이 있다면 모호함
Drizzle의 접근 방식이 테이블과 컬럼을 참조할 때 타입 안전성을 더 강력하게 보장하고, 관계를 활용한 쿼리 작성이 더 직관적이었습니다.
특히 여러 테이블 조인 시 동일한 이름의 칼럼 처리 부분에서 Drizzle ORM이 훨씬 더 편리했습니다. 이는 제 개발 경험에서 가장 중요한 차이점 중 하나였습니다.
// Drizzle ORM - 동일 이름 칼럼 처리const result = await db.query.posts.findMany({ with: { user: true // posts.id와 users.id가 모두 있지만 자동으로 구분됨 }});// 결과에 자연스럽게 접근 가능console.log(result[0].id); // 게시물 IDconsole.log(result[0].user.id); // 사용자 ID - 명확하게 구분됨console.log(result[0].user.name); // 사용자 이름// Kysely - 동일 이름 칼럼 처리를 위해 별칭 필요const result = await db .selectFrom('posts') .leftJoin('users', 'posts.userId', 'users.id') .select([ 'posts.id as postId', // 별칭 필수 'posts.title', 'posts.content', 'users.id as userId', // 별칭 필수 'users.name as userName', // 칼럼 이름이 같을 수 있으므로 별칭 필수 'users.email as userEmail' // 일관성을 위해 모든 사용자 관련 칼럼에 접두어 필요 ]);// 별칭을 통한 접근console.log(result[0].postId); // 게시물 IDconsole.log(result[0].userId); // 사용자 IDconsole.log(result[0].userName); // 사용자 이름
Drizzle ORM은 테이블과 칼럼을 객체로 참조하기 때문에, 동일한 이름의 칼럼이 있어도 자연스럽게 계층 구조로 처리되며 타입 추론도 정확하게 작동합니다. 반면 Kysely에서는 문자열 기반 접근 방식 때문에 별칭을 수동으로 지정해야 하는 경우가 많았고, 복잡한 조인에서 이런 작업이 번거로워졌습니다. 특히 여러 테이블에 같은 이름의 칼럼이 많을수록 모든 칼럼에 명시적인 별칭을 지정해야 하는 불편함이 있었습니다.
또한 Drizzle ORM은 결과 타입을 자동으로 정확하게 추론해주어 별도의 타입 지정 없이도 안전하게 결과를 사용할 수 있었습니다.
Kysely의 장점
물론 Kysely도 여러 강점이 있습니다:
더 가벼운 구조: 필요한 기능만 포함할 수 있는 모듈화된 구조
SQL에 더 가까운 접근: SQL 구문에 매우 충실한 API 설계
유연성: 복잡한 쿼리에서 때로 더 유연한 작성이 가능
또한 앞서 언급했듯이, Kysely의 TypeScript 기반 마이그레이션과 양방향(up/down) 마이그레이션 지원은 특정 상황에서 Drizzle ORM보다 우위에 있는 기능입니다.
SQLAlchemy와의 비교 및 앞으로의 기대
JavaScript/TypeScript 생태계의 ORM을 이야기하기 전에, 여러 언어 중에서도 Python의 SQLAlchemy는 특별한 위치를 차지합니다. 개인적으로 여태 사용해본 다양한 언어의 ORM 중에서 SQLAlchemy가 가장 기능이 풍부하고 강력하다고 느꼈습니다. 복잡한 쿼리 구성, 고급 관계 매핑, 트랜잭션 관리, 이벤트 시스템 등 SQLAlchemy의 기능은 정말 방대합니다.
Drizzle ORM은 JavaScript 생태계에서 매우 인상적인 발전을 이루었지만, 아직 SQLAlchemy의 경지에는 이르지 못했다고 생각합니다. 특히 다음과 같은 부분에서 SQLAlchemy의 성숙도와 기능 풍부함이 돋보입니다:
두 ORM 모두 훌륭한 도구이지만, 제 개발 스타일과 프로젝트 요구사항에는 Drizzle ORM이 더 잘 맞았습니다. 특히 스키마 정의의 직관성, 강력한 마이그레이션 도구, 그리고 전반적인 개발자 경험 측면에서 Drizzle ORM이 더 생산적인 개발을 가능하게 해주었습니다.
동일 이름 칼럼 처리와 같은 실질적인 문제에서 Drizzle ORM의 객체 기반 접근 방식이 가져다주는 편리함은 실제 프로젝트에서 큰 차이를 만들었습니다.
ORM 선택은 결국 프로젝트 특성과 개인 선호도에 크게 좌우됩니다. 새로운 프로젝트를 시작한다면 두 도구 모두 간단히 테스트해보고 자신의 워크플로우에 더 적합한 것을 선택하는 것이 좋겠지만, 제 경우에는 Drizzle ORM이 명확한 승자였습니다.
앞으로 Drizzle ORM이 더욱 발전하여 SQLAlchemy 수준의 풍부한 기능과 유연성을 제공하게 되길 바랍니다. JavaScript/TypeScript 생태계에도 그런 수준의 강력한 ORM이 있으면 좋겠습니다. 다행히도 Drizzle ORM은 계속해서 발전하고 있으며, 그 발전 속도를 보면 기대가 큽니다.