ORM으로 생산성 높이기
9 min read
ORM
ORM이란?
ORM(Object Relational Mapping)은 객체 지향 프로그래밍의 객체와 관계형 데이터베이스의 테이블을 연결해주는 기술입니다. 쉽게 말해 프로그래밍 언어에서 사용하는 객체를 데이터베이스 테이블로 매핑해주는 도구라고 생각하시면 됩니다. 테이블로 데이터를 관리하고 저장하는 RDBMS 방식에서 ORM을 사용하는 이유는 무엇일까요?
가장 간단한 예시로 '유저'라는 개념을 다룬다고 가졍해보겠습니다. 데이터베이스에서 유저를 관리하기 위해서는 유저 테이블을 생성해야합니다.
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
email VARCHAR(50),
age INT
);
위와 같이 정의된 유저 테이블은 도메인에 따라 다양한 연결 관계를 가지게 됩니다. 예를 들어 유저는 여러 개의 게시글을 작성할 수 있고 여러 개의 댓글을 작성할 수 있습니다. 관계형 데이터베이스에선 이를 테이블 간의 관계로 정의하며 이를 키로 연결합니다.
우리에게 더 친숙한 객체 지향 프로그래밍 언어에서는 이러한 관계를 다음과 같은 클래스로 표현할 수 있습니다.
class User {
constructor(id, name, email, age) {
this.id = id;
this.name = name;
this.email = email;
this.age = age;
}
}
이때 객체로 정의된 User
클래스는 속성과 동작이 결합된 형태로 정의됩니다.
인간이 세상을 바라보는 방식과 유사하게 객체를 중심으로 관계를 정의합니다.
이 두 세계는 서로 다른 패러다임을 가지고 있습니다. 객체 지향 세계는 상속, 다형성, 캡슐화같은 개념을 가지고 있는 반면 관계형 데이터베이스는 테이블, 행, 열, 키 등의 개념을 가지고 있습니다. 이 두 세계 사이의 '임피던스 불일치'라고 불리는 간극을 매워주는 것이 바로 ORM입니다.
임피던스 불일치(Impedance Mismatch)란 서로 다른 두 시스템 간의 불일치를 의미합니다. 전기회로에서 임피던스가 맞지 않으면 에너지가 비효율적으로 전될되는 것처럼 객체 모델과 관계형 모델 사이의 패러다임 차이로 인해 데이터 전환 과정에서 효율성이 떨어지는 현상을 의미합니다.
ORM을 사용하는 이유
모든 방법론이 그러하듯 ORM 또한 장단점이 존재합니다. 먼저 ORM이 어떤 상황에서 유용한지 살펴보겠습니다.
1) 생산성 향상
ORM을 사용하면 SQL 쿼리를 직접 작성하는 것보다 훨씬 빠르게 개발할 수 있습니다. 쿼리문을 사용하지 않고 내장된 메스드를 이용함으로써 훨씬 적은 코드로 동일한 기능을 구현할 수 있게 해줍니다. 예를 들어 유저에 관한 쿼리문을 작성한다고 가정해보겠습니다.
INSERT INTO users (id, name, email, age) VALUES (1, '홍길동', 'hong@example.com', 30);
SELECT id, name, email, age FROM users WHERE age > 30;
위와 같은 쿼리문을 ORM을 사용하면 다음과 같이 작성할 수 있습니다.
const olderUsers = await db.select().from(users).where(gt(users.age, 30));
const newUser = await db.insert(users).values({
name: '홍길동',
email: 'hong@example.com',
age: 25
}).returning();
2) 코드 재사용성과 유지보수성
ORM 사용 시 데이터 엑세스 로직을 재사용 가능한 컴포넌트로 분리할 수 있습니다. 이를 통해 코드 중복을 줄이고 유지보수성을 크게 향상시킬 수 있습니다.
class UserService {
async findUserById(id) {
return await db.select().from(users).where(eq(users.id, id));
}
async createUser(name, email, age) {
return await db.insert(users).values({
name,
email,
age
}).returning();
}
}
위와 같은 패턴을 사용하면 데이터베이스 엑세스 로직을 캡슐화하고 서비스 레이어로 분리할 수 있습니다. 이렇게 작성된 코드는 나중에 데이터베이스 혹은 비즈니스 로직이 변경되더라도 내부만 수정해도 되기에 유지보수성이 향상됩니다.
3) 데이터베이스 독립성
ORM을 사용하면 데이터베이스에 종속되지 않고 다양한 데이터베이스 엔진(PostgreSQL, MySQL 등)을 사용할 수 있습니다. 이는 애플리케이션이 특정 데이터베이스에 종속되지 않고 필요에 따라 교체할 수 있는 유연성을 제공합니다.
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
4) 객체 지향적인 코드 작성
ORM을 사용하면 객체 지향 프로그래밍의 장점을 활용할 수 있습니다. 테이블 간의 관계를 명시적으로 정의하고 연관된 데이터를 함께 조회하는 것이 용이합니다.
export const users = sqliteTable('users', { ... });
export const posts = sqliteTable('posts', { ... });
export const comments = sqliteTable('comments', { ... });
// 관계 정의
export const usersRelations = relations(users, ({ many) }) => ({
posts: many(posts),
comments: many(comments),
}));
5) Typescript 호환성
Typescript를 지원하는 ORM을 사용하면 타입 안정성을 높일 수 있습니다. ORM을 사용하면 데이터베이스 스키마와 애플리케이션 코드 간의 일관성을 유지할 수 있으며 개발 시점에서 타입 오류를 사전에 방지할 수 있습니다.
// 타입 추론으로 자동 타입 생성
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
// 타입 안전한 쿼리문
async function createUser(user: NewUser) {
return await db.insert(users).values(user).returning();
}
그 외에 모델 유효성 검증, SQL Injection 방지, 트랜잭션 관리 등 다양한 기능을 제공하여 개발자의 생산성을 높여줍니다.
ORM 사용 시 주의할 점
ORM을 사용함으로써 더 높은 생산성을 취하는 만큼 우리가 잃는 것 또한 존재합니다. 쿼리문 작성 시 N + 1 쿼리 문제나 대량의 레코드를 조회할 시 메모리 사용량이 급증하여 애플리케이션 성능이 저하되거나 충돌이 발생할 수 있습니다.
따라서 본인이 개발해야하는 애플리케이션의 요구 조건에 따라 적절한 방식을 택하는 것이 중요합니다. 예를 들어 소규모 프로젝트나 스키마의 잦은 변동, 혹은 프론트엔드와 백엔드 간의 통합된 스펙을 구현할 때엔 ORM을 사용하는 것이 유효할 수 있습니다. 반면 대규모 데이터 처리 애플리케이션 혹은 극도의 성능 최적화가 필요한 경우엔 ORM을 사용하지 않는 것이 더 나은 선택일 수 있습니다.
댓글 4개
Bae seong-kyu
2025년 04월 30일
답글 0개
Hwang Tae Young
2025년 04월 30일
답글 0개
Sohyun
2025년 04월 30일
답글 0개
Sohyun
2025년 04월 30일
답글 0개