😺
GinでAPI作ってみる
背景
この前はechoでAPI作ったので、今度はGinを使ってみる。
とりあえずは何か一つ動くものを作成する。
クリーンアキテクチャーも意識したフォルダ構成にする。
環境
M1 Mac
docker desktop 4.17.0
全体感
依存関係
クリーンアーキテクチャーを意識して、依存関係は図のようにする。
usecaseとかもあった方がよいかもしれないが、現時点だとちょっとやりすぎなかんじもするので、やらない。
フォルダ構成
最終的なフォルダ・ファイル構成はこんなかんじ。
├── Dockerfile
├── README.md
├── config
│ └── config.go
├── db
│ └── mysql
│ ├── etc
│ │ └── my.cnf
│ ├── init
│ │ └── init.sql
│ └── log
├── docker-compose.yml
├── domain
│ ├── model
│ │ └── user.go
│ ├── repository
│ │ └── user_repository.go
│ └── service
│ └── user_service.go
├── dto
│ ├── input
│ │ └── user_input_dto.go
│ └── output
│ └── user_output_dto.go
├── go.mod
├── go.sum
├── handler
│ └── user_handler.go
├── infrastructure
│ └── mysql
│ ├── mysql.go
│ └── user_repository.go
├── main.go
実装
準備
・初期化
go mod init go-gin-api
Ginをインストール
go get -u github.com/gin-gonic/gin
GORMをインストール
go get -u gorm.io/gorm
go get github.com/go-sql-driver/mysql
メイン実装
各層はインタフェース経由で呼び出すようにして、疎結合にしておく。
(すべてのコードを記載すると量が多くなるので、主要なところだけ載せておきます。)
infrastructure
mysql.go
Mysqlとのconnection作るところ
package mysql
import (
"log"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var DB *gorm.DB
func Connect(host string, user string, password string, port string, databaseName string) {
var err error
dsn := user + ":" + password + "@tcp(" + host + ":" + port + ")/" + databaseName + "?parseTime=true" + "&charset=utf8mb4"
for i := 0; i < 5; i++ {
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err == nil {
break
}
time.Sleep(time.Millisecond * 100)
}
if err != nil {
log.Printf("Mysql接続に失敗。エラー内容:%s", err.Error())
} else {
log.Print("Mysql接続に成功")
}
}
user_repository.go
Mysqlに接続して実際にデータ取得するところ
package mysql
import (
"database/sql"
"errors"
"go-gin-api/domain/model"
"go-gin-api/domain/repository"
"gorm.io/gorm"
)
type UserRepository struct {
*sql.DB
}
func NewUserRepository(db *sql.DB) repository.IfUserRepository {
return &UserRepository{db}
}
func (ur *UserRepository) FindByID(id int) (*model.User, error) {
u := &model.User{}
err := DB.First(&u, id).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return u, nil
}
if err != nil {
return u, err
}
return u, nil
}
service
user_repository.go
infraとの仲介をするインタフェースのみ定義しておく
package repository
import "go-gin-api/domain/model"
type IfUserRepository interface {
FindByID(id int) (*model.User, error)
}
user_service.go
repositoryの処理の呼び出し
package service
import (
"go-gin-api/domain/model"
"go-gin-api/domain/repository"
"go-gin-api/dto/input"
"go-gin-api/dto/output"
)
type IfUserService interface {
FindByID(id int) (*output.User, error)
}
type UserService struct {
repository.IfUserRepository
}
func NewUserService(repo repository.IfUserRepository) IfUserService {
return &UserService{repo}
}
func (s *UserService) convertTo(user *model.User) *output.User {
return output.NewUserModel(user.ID, user.UserName, user.UpdatedAt, user.CreatedAt, user.DeletedAt)
}
func (s *UserService) convertFrom(user *input.User) *model.User {
return model.NewUser(user.ID, user.UserName, user.UpdatedAt, user.CreatedAt, user.DeletedAt)
}
func (s *UserService) FindByID(id int) (*output.User, error) {
user, err := s.IfUserRepository.FindByID(id)
if err != nil {
return nil, err
}
return s.convertTo(user), nil
}
handler
user_handler.go
ginからリクエストデータ取得してservice呼び出し
package handler
import (
"go-gin-api/domain/service"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type IfUserHandler interface {
FindByID(c *gin.Context)
}
type UserHandler struct {
service.IfUserService
}
func NewUserHandler(s service.IfUserService) IfUserHandler {
return &UserHandler{s}
}
func (uh UserHandler) FindByID(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
user, err := uh.IfUserService.FindByID(id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{
"msg": "エラーが発生しました。",
})
}
c.JSON(http.StatusOK, gin.H{
"id": user.ID,
"userName": user.UserName,
"updated_at": user.UpdatedAt,
"created_at": user.CreatedAt,
"deleted_at": user.DeletedAt,
})
}
エントリポイント
main.go
ここでDIする。
ミドルウェアの設定も入れておく。
package main
import (
"go-gin-api/config"
"go-gin-api/domain/service"
"go-gin-api/handler"
"go-gin-api/infrastructure/mysql"
"net/http"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"},
AllowMethods: []string{
"POST",
"GET",
"PUT",
"DELETE",
},
AllowHeaders: []string{
"Access-Control-Allow-Credentials",
"Access-Control-Allow-Headers",
"Content-Type",
"Content-Length",
"Accept-Encoding",
"Authorization",
},
}))
mysql.Connect(config.Config.MysqlHost, config.Config.MysqlUser, config.Config.MysqlPass, config.Config.MysqlPort, config.Config.MysqlDbName)
db, _ := mysql.DB.DB()
defer db.Close()
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Hello, World!"})
})
r.GET("/user/:id", func(c *gin.Context) {
r := mysql.NewUserRepository(db)
s := service.NewUserService(r)
h := handler.NewUserHandler(s)
h.FindByID(c)
})
r.Run(":" + config.Config.ServerPort)
}
動作確認
コンテナ起動
ここでMysqlに初期データを投入
docker-compose up -d
API実行。
レスポンスくることを確認。
Discussion