코드 리팩토링 Serializer, DTO, CodeConvention
코드를 작성하는데 확인해보니
package entities
import (
"time"
)
type Spot struct {
Id uint `json:"id" gorm:"primaryKey"`
UserId uint `json:"user_id"`
User User `gorm:"foreignKey:UserId;constraint:OnDelete:CASCADE;"`
CategoryId *int `gorm:"default:null" json:"category_id"` // CategoryId가 null일 수가 있음
// sqlite에서 SET NULL, mysql, postgresql에서는 SetNull
// 배포시 아래 주석
// sqlite 설정
Category Category `gorm:"foreignKey:CategoryId;constraint:OnDelete:SET NULL;"`
// 배포시 아래 주석해제
// rds 설정
//Category Category `gorm:"foreignKey:CategoryId;constraint:OnDelete:SetNull;"`
Title string `json:"title"`
Location string `json:"location"`
Author string `json:"author"`
Review string `json:"review"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
CoverImg string `json:"cover_img"`
}
type SpotSerializer interface {
ListSerialize() SpotListOutputSchema
DetailSerialize() SpotDetailOutputSchema
}
type spotSerializer struct {
spot *Spot
user UserSerializer
}
func (s *spotSerializer) ListSerialize() SpotListOutputSchema {
return SpotListOutputSchema{
Id: int(s.spot.Id),
User: s.user.TinyUserSerialize(),
Title: s.spot.Title,
Location: s.spot.Location,
Author: s.spot.Author,
CreatedAt: s.spot.CreatedAt,
UpdatedAt: s.spot.UpdatedAt,
Review: s.spot.Review,
}
}
func (s *spotSerializer) DetailSerialize() SpotDetailOutputSchema {
return SpotDetailOutputSchema{
Id: int(s.spot.Id),
User: s.user.TinyUserSerialize(),
Title: s.spot.Title,
Location: s.spot.Location,
Author: s.spot.Author,
CreatedAt: s.spot.CreatedAt,
UpdatedAt: s.spot.UpdatedAt,
Review: s.spot.Review,
}
}
func NewSpotSerializer(s *Spot, u UserSerializer) SpotSerializer {
return &spotSerializer{spot: s, user: u}
}
// ============= input schema =============
type CreateSpotInputSchema struct {
Title string `json:"title"`
Location string `json:"location"`
Review string `json:"review"`
}
type UpdateSpotSchema struct {
Title string `json:"title"`
Location string `json:"location"`
Review string `json:"review"`
}
// ============= output schema =============
type SpotListOutputSchema struct {
Id int `json:"id"`
User TinyUserOutputSchema `json:"user"`
Title string `json:"title"`
Location string `json:"location"`
Author string `json:"author"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Review string `json:"review"`
}
type SpotDetailOutputSchema struct {
Id int `json:"id"`
User TinyUserOutputSchema `json:"user"`
Title string `json:"title"`
Location string `json:"location"`
Author string `json:"author"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Review string `json:"review"`
}
이런식으로 작성하다보니 점점 코드가 길어질 것 같은 느낌이 문득 든다. 실제로 API도 많이 붙이지도 않았는데 벌써부터 이런 거는 앞으로도 문제가 될 것 으로 보인다. 더 커지기 전에 손보자.
먼저 파일을 분리하자.
위의 코드를 살펴보면
1. 크게 gorm으로 Database에 입력하는 부분
2. Interface에 input으로 사용되는부분
3. serializer형태로 output을 뽑아내는 부분
이렇게 크게 3부분이 있기 때문에 gorm에 입력되는 부분은 그대로 두고
input 으로 사용되는 부분과 serializer부분의 파일을 분리해서 관리하도록 하겠다.
dto/spot.go
package dto
import "time"
// ============= input schema =============
type CreateSpotIn struct {
Title string `json:"title"`
Location string `json:"location"`
Review string `json:"review"`
}
type UpdateSpotIn struct {
Title string `json:"title"`
Location string `json:"location"`
Review string `json:"review"`
}
// ============= output schema =============
type SpotListOut struct {
Id int `json:"id"`
User TinyUserOut `json:"user"`
Title string `json:"title"`
Location string `json:"location"`
Author string `json:"author"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Review string `json:"review"`
}
type SpotDetailOut struct {
Id int `json:"id"`
User TinyUserOut `json:"user"`
Title string `json:"title"`
Location string `json:"location"`
Author string `json:"author"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Review string `json:"review"`
}
serializer/spot.go
package serializer
import (
"camping-backend-with-go/pkg/dto"
"camping-backend-with-go/pkg/entities"
)
type SpotSerializer interface {
ListSerialize() dto.SpotListOut
DetailSerialize() dto.SpotDetailOut
}
type spotSerializer struct {
spot *entities.Spot
user UserSerializer
}
func (s *spotSerializer) ListSerialize() dto.SpotListOut {
return dto.SpotListOut{
Id: int(s.spot.Id),
User: s.user.TinyUserSerialize(),
Title: s.spot.Title,
Location: s.spot.Location,
Author: s.spot.Author,
CreatedAt: s.spot.CreatedAt,
UpdatedAt: s.spot.UpdatedAt,
Review: s.spot.Review,
}
}
func (s *spotSerializer) DetailSerialize() dto.SpotDetailOut {
return dto.SpotDetailOut{
Id: int(s.spot.Id),
User: s.user.TinyUserSerialize(),
Title: s.spot.Title,
Location: s.spot.Location,
Author: s.spot.Author,
CreatedAt: s.spot.CreatedAt,
UpdatedAt: s.spot.UpdatedAt,
Review: s.spot.Review,
}
}
func NewSpotSerializer(s *entities.Spot, u UserSerializer) SpotSerializer {
return &spotSerializer{spot: s, user: u}
}
이런식으로 파일을 분리했다. 파일이름도 조금 간소화하여 변경하였고, 모쪼록 잘 변경한 것 같다.
생객해보니 LocalDB가 sqlite...
생각해보니 LocalDB가 sqlite이다. 물론 비용을 최소화하기 위해 spot인스턴스로 작성하긴 했지만 로컬로 개발 땡길 수 있을 때 최대한 로컬로 하려고 한다. 그런 과정에서 개발 DB는 Mysql인데 localDB가 sqlite다 보니 생각보다 버그가 많더라..
그래서 간단하고 빠르게 mysqlDB를 올리기로 했다.
정말 간단히...
local_database/docker-compose/docker-compose.yml
version: "3.8"
services:
db:
build:
context: ./mysql
env_file:
- ".env"
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
volumes:
- "./mysql/db_data:/var/lib/mysql"
ports:
- "${DB_PORT}:3306"
local_database/docker-compose/.env
MYSQL_ROOT_PASSWORD=test1234
MYSQL_DATABASE=ggocamping
DB_PORT=3306
local_database/docker-compose/mysql/Dockerfile
FROM mysql
이렇게 3개의 파일을 만들고
docker-compose.yml파일이 있는 경로에서
$ docker compose up -d
를 실행하자. 그리고 나면 docker를 이용해서 Database가 설치되게 된다.
설치가 되었으니 이제 Database에 접속을 하면 되겠다.
.env에 있는 접속 정보를 이용해서 접속을 하게 되면 된다. 혹시나 아래와 같은 에러가 나오게 되면 allowPublicKeyRetrival을 true로 설정해주자.
Database가 설치되었으니 이제 Mysql을 이용해서 개발을 진행하면 되겠다.