이번 게시글에서는 gorm을 사용하여 graphql서버의 전반적인 아키텍쳐를 구축하면 graphql 서버 구축을 마무리합니다.
소스코드는 github 링크에서 확인하실 수 있습니다(main 브랜치)
1. 패키지 구조
./
├── generated
│ └── generated.go
├── go.mod
├── go.sum
├── gqlgen.yml
├── graphql
│ └── user.graphql
├── main.go
├── model
│ └── user.go
├── repository
│ └── user_repository.go
├── resolver
│ ├── resolver.go
│ ├── types
│ │ └── types.go
│ └── user.resolvers.go
├── service
│ └── user_service.go
├── store
│ └── store.go
└── tools.go
패키지 구조는 3 tiered architecture를 사용하였습니다.
저수준의 모듈과 고수준의 모듈을 분리하여, 확장과 재사용에 유리하여 자주 사용되는 패키지 구조입니다.
resolver패키지가 표현 계층 servcie패키지가 비즈니스 계층 repository패키지가 영속성 계층 에 해당됩니다.
2. store/store.go
package store
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"graphql-sample/model"
)
var DB *gorm.DB
func InitDatabase() {
db, err := gorm.Open(sqlite.Open("local.db"), &gorm.Config{})
if err != nil {
panic(err)
}
err = db.AutoMigrate(&model.User{})
if err != nil {
panic(err)
}
DB = db
}
프로젝트의 시작점인 main문에서 gorm을 사용할수 있도록 InitDatabase()를 호출해줍니다.
사용자의 request가 아닌 프로젝트 init시에 일어나는 에러는 panic으로 처리해 db로드의 실패시 프로세스가 꺼지도록 합니다.
3. model/user.go
package model
import (
"graphql-sample/resolver/types"
"strconv"
"time"
"gorm.io/gorm"
)
type User struct {
ID uint `gorm:"primaryKey"`
Name string
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
func (user *User) GetGqlResponse() *types.User {
return &types.User{
ID: strconv.Itoa(int(user.ID)),
Name: user.Name,
}
}
func NewGormUser(input types.CreateUserInput) *User {
return &User{
Name: input.Name,
}
}
gorm을 사용한 db model을 정의해줍니다.
db모델을 graphql response로 변환하는 메서드와 graphql request를 de모델로 변환하는 메서드를 만들어줍니다.
4. resolver/user.resolver.go
// CreateUser is the resolver for the create_user field.
func (r *mutationResolver) CreateUser(ctx context.Context, input types.CreateUserInput) (*types.User, error) {
user, err := service.UserService{}.CreateUser(input)
if err != nil {
return nil, err
}
return user.GetGqlResponse(), nil
}
// UserList is the resolver for the user_list field.
func (r *queryResolver) UserList(ctx context.Context) (*types.UserList, error) {
userList, err := service.UserService{}.GetUserList()
if err != nil {
return nil, err
}
var list []*types.User
for _, user := range userList {
list = append(list, user.GetGqlResponse())
}
return &types.UserList{List: list}, nil
}
// User is the resolver for the user field.
func (r *queryResolver) User(ctx context.Context, input types.UserInput) (*types.User, error) {
userId, err := strconv.Atoi(input.ID)
if err != nil {
return nil, err
}
user, err := service.UserService{}.GetUser(userId)
if err != nil {
return nil, err
}
// gorm model과 types.user는 필드와 타입이 다르니 변환하는 메서드를 하나 구축합니다.
return user.GetGqlResponse(), nil
}
표현계층인 resolver에서는 interface영역의 데이터가 정상적으로 들어왔는지를 확인하고 service패키지의 비즈니스 코드를 호출합니다.
또한 비즈니스 코드를 호출한 후 내려줘야할 데이터를 변환하는 작업도 수행합니다.
5. service/user_service.go
package service
import (
"graphql-sample/model"
"graphql-sample/repository"
"graphql-sample/resolver/types"
)
type UserService struct{}
func (UserService) GetUserList() ([]model.User, error) {
return repository.UserRepository{}.FindAll()
}
func (UserService) GetUser(userId int) (*model.User, error) {
return repository.UserRepository{}.FindById(userId)
}
func (UserService) CreateUser(input types.CreateUserInput) (*model.User, error) {
return repository.UserRepository{}.CreateUser(input)
}
비즈니스 로직을 작성하기 위한 레이어입니다.
네임스페이스 충돌을 막기위해 더미 struct를 하나 만들었습니다.
golang은 package내 동일한 메서드, 변수명을 사용할수 없습니다.(패키지충돌, 네임스페이스충돌 이라고 부릅니다)
그렇다고 service/user/service.go 등으로 패키지를 나눠버리면 추후 순환참조로 인해 고생할 확률이 있으니 네임스페이스 충돌을 피하기 힘들어 보이는경우 더미 struct로 피해갈 수 있습니다.
6. repository/user_repository
package repository
import (
"graphql-sample/model"
"graphql-sample/resolver/types"
"graphql-sample/store"
)
type UserRepository struct{}
func (UserRepository) FindAll() ([]model.User, error) {
var userList []model.User
err := store.DB.Find(&userList).Error // Find의 두번째 파라미터로 id list를 넘겨주지 않으면 모든 record 조회
if err != nil {
return nil, err
}
return userList, nil
}
func (UserRepository) FindById(id int) (*model.User, error) {
user := model.User{}
err := store.DB.First(&user, id).Error
if err != nil {
return nil, err
}
return &user, nil
}
func (UserRepository) CreateUser(input types.CreateUserInput) (*model.User, error) {
user := model.NewGormUser(input)
err := store.DB.Save(user).Error
if err != nil {
return nil, err
}
return user, nil
}
repository레이어에서는 데이터 저장관련 로직을 작성합니다.
더미 struct로 인해 네임스페이스 충돌없이 FindAll(), FindById() 등의 이름을 사용할 수 있습니다.
create같은경우 해당 레이어에서 graphql input을 gorm model로 변환해 저장합니다.
'golang' 카테고리의 다른 글
[golang] golang reverse proxy 예제 (0) | 2023.04.06 |
---|---|
[golang] echo framework와 jwt (0) | 2023.01.21 |
[golang] gqlgen을 사용하여 golang graphql 서버 구축하기(3) (0) | 2022.12.08 |
[golang] gqlgen을 사용하여 golang graphql 서버 구축하기(2) (0) | 2022.12.07 |
[golang] gqlgen을 사용하여 golang graphql 서버 구축하기(1) (0) | 2022.12.07 |
댓글