![우리 프로젝트에서 Golang DB 처리 시에 GORM을 사용 해야 하는 이유](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEHYgq%2FbtsG3rJOOAa%2FGqPvTPN2Ls1BfOzeNeKHjk%2Fimg.png)
제가 현재 회사에서 개발중인 제품에서 사용하는 Language는 Golang입니다.
기존에는 제품에 DB를 도입할 필요가 없었다가 최근에 DB 도입을 하게 되었습니다.
DB를 개발자들이 잘 다루기 위한 방법이 필요했는데요.
그 중 찾아낸게 바로 GORM 이었습니다.
GORM을 쓰는 근거와 그것을 정리하는 Architecture Decision Record를 작성을 해보려고합니다.
GORM 사용시의 장점
1. 개발 효율성 관점
1) 테이블 Creation SQL관리가 필요없습니다. Go의 구조체를 테이블로 관리하면 됩니다.
우리 프로젝트에서는 각 서버에 DB를 직접 생성해서 작업을 하기때문에
아래와 같은 Table Creation SQL 코드도 관리해야하고 이를 Golang의 Type들과 datatype sync를 직접 맞춰주어야 합니다.
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
age INT NOT NULL
);
type User struct {
ID int
Name string
Age int
}
그리고 코드 작업시에 golang의 struct 만 보면 어느게 어떤 특징을 가지고 있는지 한눈에 보기가 힘듭니다.
ID 자체가 primary key를 가지고 있는지 name이 not null 특징을 가지고 있는지 매번 테이블 생성 SQL을 찾아가서 규칙을 확인해야하죠.
SQL문과 Go의 구조체 두 개를 관리해야합니다.
반면 GORM을 활용하게 되면 Go 구조체만 관리하면 DB Schema 관리를 할 수 있습니다.
Column의 특성 및 조건까지 한번에 확인할 수 있습니다.
// user_model.go
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100;not null"`
Age int
}
// AutoMigrate를 사용하여 데이터베이스 테이블을 자동으로 생성합니다.
err = db.AutoMigrate(&User{})
다만, table이 생성되는 네이밍 규칙은 숙지가 필요합니다.
2) SQL문을 몰라도 되고, SQL 실수로 인해서 생산성이 떨어지지 않습니다.
하나의 예시를 들어보겠습니다.
rows, err := db.Query("SELECT id, age, name FROM users")
var users []User
for rows.Next() {
var user User
err := rows.Scan(&user.ID, &user.Name, &user.Age)
if err != nil {
log.Fatal(err)
}
users = append(users, user)
}
GORM 쓴 케이스에서는 아래와 같이 Find() 하나로 해결해버릴 수 있습니다.
type User struct {
ID uint
Name string
Age int
}
// 사용자 정보 조회
var users []User
result := db.Find(&users)
User에 들어오는 데이터는 동일합니다.
그리고 눈치 채신 분들이 있겠지만, RAW SQL로 작업한 코드에는 심각한 결함이 있는데요.
바로 Query 하려는 Column의 순서와 struct은 순서가 다릅니다.
Select 문은 id, age, name 순
rows.Scan은 ID, Name , Age 순으로 가져옵니다.
rows, err := db.Query("SELECT id, age, name FROM users")
var users []User
for rows.Next() {
var user User
err := rows.Scan(&user.ID, &user.Name, &user.Age)
if err != nil {
log.Fatal(err)
}
users = append(users, user)
}
Query문을 사용하면 이런 자잘한 실수들이 발생하기가 쉬운데 이를 ORM들에서는 알아서 처리해서 가져와주니 이런 문제가 발생하지 않습니다.
2. 유지보수 관점
1) 에러처리의 일관성
GORM은 일관된 에러 처리 방식을 제공합니다.
모든 데이터베이스 작업 결과는 Error 필드를 통해 확인할 수 있으며, 이를 통해 에러를 쉽게 감지하고 적절히 대응할 수 있습니다.
이는 애플리케이션의 안정성을 높이고, 에러 관리를 통합적으로 할 수 있게 합니다.
2) 코드의 재사용성과 모듈화
GORM을 사용하면 데이터 액세스 레이어를 모듈화하기 쉽습니다.
구조체 메소드나 별도의 리포지토리 패턴을 통해 데이터 액세스 코드를 재사용하고, 이를 통해 비즈니스 로직과 데이터베이스 로직을 명확히 분리할 수 있습니다.
이러한 분리는 애플리케이션의 유지보수를 더욱 용이하게 만들어 줍니다.
3) 데이터 베이스 독립성
GORM officially supports the databases MySQL, PostgreSQL, SQLite, SQL Server, and TiDB
GORM은 MySQL, PostgreSQL, SQLITE, SQL Server 와 TiDB를 지원합니다.
따라서, 이 범위 내에서 DB 변경이 필요하다면 DB Driver만 변경해주면 코드의 큰 변경 없이 쉽게 옮겨 줄 수 있습니다.
반면 그냥 SQL을 사용하면 SQL 간 다른 문법, Datatype을 직접 변경해 주어야 하니 변경이 어려울 수 있습니다.
3. 커뮤니티 지원 및 자료
GORM은 널리 사용되는 라이브러리로 다양한 자료와 커뮤니티의 지원을 받을 수 있습니다.
GORM Community
AskHow do I ask a good question? Stackoverflow - https://stackoverflow.com/questions/tagged/go-gorm Github Issues - https://github.com/go-gorm/gorm/issues Chat Gitter - https://gitter.im/jinzhu/gorm
gorm.io
GORM 사용시의 단점
1. RAW SQL에 비해 성능 하락이 있습니다.
일반적으로 DB 처리를 해주는 라이브러리를 쓰게 되면 Raw SQL을 사용하는것에 비해서 성능은 줄어드는 점이 단점입니다.
아래의 github repo에서 go-orm들의 benchmark 결과를 살펴봐도 실제로 raw의 성능이 gorm의 성능보다 훨씬 뛰어난것을 확인할 수 있었습니다.
go-orm-benchmarks/results.md at master · efectn/go-orm-benchmarks
Advanced benchmarks for +15 Go ORMs. Contribute to efectn/go-orm-benchmarks development by creating an account on GitHub.
github.com
Insert
raw: 4398 275215 ns/op 703 B/op 13 allocs/op
gorm_prep: 3391 375917 ns/op 5160 B/op 65 allocs/op
gorm: 2421 485324 ns/op 7160 B/op 105 allocs/op
InsertMulti
raw: 1400 919564 ns/op 183799 B/op 930 allocs/op
gorm_prep: 1011 1195540 ns/op 251011 B/op 1890 allocs/op
gorm: 782 1552918 ns/op 291371 B/op 5231 allocs/op
Update
raw: 8665 149048 ns/op 750 B/op 13 allocs/op
gorm_prep: 2755 423510 ns/op 5008 B/op 56 allocs/op
gorm: 1984 592409 ns/op 6752 B/op 99 allocs/op
Read
raw: 8550 154318 ns/op 2061 B/op 50 allocs/op
gorm_prep: 6738 177739 ns/op 4405 B/op 87 allocs/op
gorm: 5691 217876 ns/op 4773 B/op 98 allocs/op
ReadSlice
raw: 3950 300570 ns/op 38340 B/op 1038 allocs/op
gorm_prep: 2337 472229 ns/op 43168 B/op 2081 allocs/op
gorm: 2265 526073 ns/op 44320 B/op 2191 allocs/op
다만, 현재 회사에서 사용하고 있는 데이터베이스 접근이 저렇게 많이 이루어 지지 않아서 이 부분은 단점이 되지 않을것 같습니다.
추후에는 일부 데이터베이스를 활용한 작업이 추가 될 수 있으나, 그것 또한 일반적인 웹 서비스들이 많이 사용하는 정도의 DB 접근 횟수는 안될것이라 예상되어 성능은 크게 걱정하지 않아도 될것같네요.
2. SQL을 직접 사용하지 않고 Library를 사용하니, 실제로 원하는 형태의 SQL이 정상 실행됐는지 바로 확인이 힘듭니다.
하나의 예시를 들어보겠습니다.
아래와 같이 직접 SQL을 쓴 경우에는 SQL 커맨드가 DB에 그대로 들어갈것이기에
SELECT id, name, age FROM users 가 반드시 불릴것이라고 생각되는 반면
rows, err := db.Query("SELECT id, name, age FROM users")
var users []User
for rows.Next() {
var user User
err := rows.Scan(&user.ID, &user.Name, &user.Age)
if err != nil {
log.Fatal(err)
}
users = append(users, user)
}
GORM 쓴 케이스에서는 아래와 같이 Find() 하나로 해결해버립니다.
type User struct {
ID uint
Name string
Age int
}
// 사용자 정보 조회
var users []User
result := db.Find(&users)
GORM 자체는 엄청나게 잘 관리되고 있는 오픈소스라서 그럴일은 없겠지만,
만에 하나의 경우에 버그가 있는 특정버전을 사용하다가 `Find()` 우리가 작동하기를 기대하는 위의 SQL select문과 동일한 SQL문을 부르지 않는다? 이라는 걱정이 있을수도 있습니다.
만약 그런 버그와 함께 제품이 고객사에 나간경우에 이런 버그가 작동하게 된다면 골치아파 질것입니다.
버그가 어디서 어떻게 났는지, 실제 쿼리문이 어떻게 동작했는지 등등을 확인 해야하는 경우가 생기는것이죠.
다만 위에서 말씀드렸듯
1. 엄청나게 많이 사용되고 큰 커뮤니티를 가지고있는 오픈소스라 그런 버그들이 일어날 확률이 적다는점.
2. GORM에서 지원해주는 logger 기능을 활용해서 DB Transaction이 일어날때마다 로깅을 해주기
의 방식으로 보완을 할 수 있습니다.
gorm sql show query에 대하여
golang의 orm 중 하나인 gorm을 사용하면서 내가 사용하는 코드에 대한 sql query문이 어떻게 나오는지에 대해 궁금해졌다.성능을 최적화하기 위해서도 알아야 하는 부분이기도 하다.sql query를 확인하
velog.io
3. 섬세한 SQL 처리에 대한 우려
하지만, GORM에서도 RAW SQL을 동일하게 사용할 수 있기에 섬세한 SQL 처리가 필요한 경우 이를 활용하면 될것같습니다.
ADR : GORM을 사용하여 데이터베이스 작업 처리
상태
Accepted
문맥
프로젝트의 초기 단계에서 데이터베이스 작업을 간편하게 처리하고, 개발 속도를 향상시키며, 일관된 데이터 접근 방식을 유지할 필요가 있습니다. 데이터 모델의 자주 변경되는 구조에 유연하게 대응하고, 개발 팀의 생산성을 최대화하는 것이 중요합니다.
결정
프로젝트에서는 GORM 라이브러리를 사용하여 데이터베이스 작업을 처리하기로 결정했습니다. GORM은 Go 언어에 최적화된 ORM 도구로, 강력한 모델링 기능을 제공하며 CRUD 작업을 간단하게 만듭니다.
고려사항
- 개발 효율성: GORM은 모델 기반으로 쿼리를 자동화하므로 개발자가 데이터베이스 쿼리 작성에 드는 시간과 노력을 줄일 수 있습니다.
- 유지보수: 모델을 중심으로 코드를 구성하면, 변경 사항이 발생할 때 관련된 코드만 수정하면 되어 유지보수가 용이합니다.
- 커뮤니티 지원 및 자료 : GORM은 널리 사용되는 라이브러리로 다양한 자료와 커뮤니티의 지원을 받을 수 있습니다.
- 성능: 복잡한 쿼리가 필요한 경우 GORM 사용으로 인해 성능 저하가 발생할 수 있지만, 우리 프로젝트의 경우 많은 데이터 베이스 성능이 필요 없기 때문에 성능으로 문제가 발생 할 확률이 낮습니다.
- 학습 곡선: GORM은 사용법이 직관적이지만, ORM을 처음 접하는 개발자에게는 학습 곡선이 존재할 수 있습니다.
- SQL 실제 커맨드 로깅: GORM이 지원하는 logger를 활용해서 DB transaction마다 로그를 추가하여 문제 발생시 확인이 가능하도록 지원 가능합니다.
대안
직접 SQL을 사용하여 데이터베이스 작업을 처리하는 방법도 고려했습니다. 이 방법은 높은 성능과 컨트롤을 제공하지만, 코드의 복잡성이 증가하고, 유지보수 비용이 상승할 수 있는 단점이 있습니다.
결론
GORM의 편의성, 유지보수 용이성 및 개발 속도 향상의 이점이 프로젝트의 요구사항과 일치합니다. 성능 관련 이슈가 발생하면, 최적화를 고려하거나 필요한 경우에 한해 특정 작업에서는 직접 SQL을 사용할 수도 있습니다.
참조
GORM 공식 문서 (https://gorm.io/docs/)
'백엔드 > Golang' 카테고리의 다른 글
Golang Convention 중 논의를 해야 할 사항 정리 (0) | 2024.11.10 |
---|---|
Golang 에러 처리 - (1) Google Guide/Best Practice 찾아보기 (2) | 2024.09.07 |
Golang zero-value 알아보기 (0) | 2024.07.09 |
[번역] Golang vs Spring boot native 성능 비교해보기 - Hello world 케이스 (0) | 2024.07.08 |
Golang init() 사용법 및 주의 사항 (0) | 2024.05.16 |
개발 및 IT 관련 포스팅을 작성 하는 블로그입니다.
IT 기술 및 개인 개발에 대한 내용을 작성하는 블로그입니다. 많은 분들과 소통하며 의견을 나누고 싶습니다.