728x90
읽기와 쓰기를 분리하여 유지보수성과 성능을 챙기자!
✅ CQRS란?
CQRS(Command Query Responsibility Segregation)는 **쓰기(Command)**와 읽기(Query) 책임을 서로 다른 모델 또는 레이어로 분리하는 아키텍처 패턴입니다.
- 기존 CRUD는 하나의 모델이 모든 작업을 처리
- CQRS는 복잡한 시스템에서 명확하고 유연한 구조 제공
📦 GORM에서 CQRS를 구현하는 이유
이점 설명
책임 분리 | 읽기/쓰기 로직 분리로 유지보수 용이 |
성능 최적화 | 읽기에서 복잡한 JOIN 최소화 가능 |
테스트 용이 | Command/Query 각각 테스트 가능 |
확장성 | Read-Replica, Kafka 등 확장 용이 |
📐 디렉토리 구조 예시
/internal
/user
- command_repository.go # 쓰기
- query_repository.go # 읽기
- user_model.go
1️⃣ 모델 정의
// user_model.go
package user
type User struct {
ID uint `gorm:"primaryKey"`
Name string
Email string
Password string
}
2️⃣ CommandRepository (쓰기 전용)
// command_repository.go
package user
import "gorm.io/gorm"
type UserCommandRepository struct {
db *gorm.DB
}
func NewUserCommandRepository(db *gorm.DB) *UserCommandRepository {
return &UserCommandRepository{db}
}
func (r *UserCommandRepository) Create(user *User) error {
return r.db.Create(user).Error
}
func (r *UserCommandRepository) UpdateName(userID uint, newName string) error {
return r.db.Model(&User{}).Where("id = ?", userID).Update("name", newName).Error
}
func (r *UserCommandRepository) Delete(userID uint) error {
return r.db.Delete(&User{}, userID).Error
}
3️⃣ QueryRepository (읽기 전용)
// query_repository.go
package user
import "gorm.io/gorm"
type UserQueryRepository struct {
db *gorm.DB
}
func NewUserQueryRepository(db *gorm.DB) *UserQueryRepository {
return &UserQueryRepository{db}
}
func (r *UserQueryRepository) FindByID(id uint) (*User, error) {
var user User
err := r.db.First(&user, id).Error
if err != nil {
return nil, err
}
return &user, nil
}
func (r *UserQueryRepository) SearchByEmail(email string) ([]User, error) {
var users []User
err := r.db.Where("email LIKE ?", "%"+email+"%").Find(&users).Error
return users, err
}
4️⃣ 사용 예시
cmdRepo := user.NewUserCommandRepository(db)
queryRepo := user.NewUserQueryRepository(db)
newUser := &user.User{Name: "홍길동", Email: "gil@example.com", Password: "hashed_pw"}
_ = cmdRepo.Create(newUser)
_ = cmdRepo.UpdateName(newUser.ID, "김길동")
foundUser, _ := queryRepo.FindByID(newUser.ID)
fmt.Println(foundUser.Name)
_ = cmdRepo.Delete(newUser.ID)
🧠 실전 팁
상황 전략
조회 최적화 | Select("id, name") 등 최소 필드만 조회 |
읽기 모델 분리 | ViewModel 또는 DTO 설계로 성능 최적화 |
트랜잭션 처리 | Command에서 db.Transaction(...) 적극 활용 |
추상화 | 인터페이스로 분리하여 테스트/Mock 편의성 확보 |
🧪 CQRS + 테스트 예시
type UserCommand interface {
Create(user *User) error
UpdateName(id uint, name string) error
}
type UserQuery interface {
FindByID(id uint) (*User, error)
SearchByEmail(email string) ([]User, error)
}
Mock 객체를 만들어 유닛 테스트 가능
✅ 마무리 정리
항목 설명
전략 | Command / Query 레이어를 GORM 레벨에서 분리 |
구현 | UserCommandRepository, UserQueryRepository 구성 |
장점 | 유지보수성, 성능, 테스트 용이성 확보 |
추천 시점 | API가 커지거나 조회가 복잡해질 때 적용 |
실제 프로젝트에 적용해보며 GORM에서 CQRS가 어떻게 구조적으로 강력한지 체감해보자!
728x90
'개발 > Go' 카테고리의 다른 글
[Gin] GORM Custom Type 활용법 - JSON 필드, Enum, Time 처리까지 한 번에 정리! (94) | 2025.06.25 |
---|---|
[Go] Go에서 싱글턴 패턴 구현하기 - 실전 예제로 쉽게 이해하기 (58) | 2025.06.20 |
[Gin] GORM + Redis 캐싱 전략 - DB 부하 줄이는 실전 캐싱 (75) | 2025.06.16 |
[Gin] GORM 디버깅 & 에러 핸들링 완전정복 (59) | 2025.06.13 |
[Gin] GORM으로 단위 테스트 잘하는 법 - 테스트 환경 구성부터 Mock까지 (70) | 2025.06.11 |