til復活

ちょうど各所でAdvent Calendarが始まったタイミングでそういえば今年はアウトプット少なかったので、Advent Calendarという形でなくても今月は毎日何かしらアウトプットできたらと思っていた。 このtilを見返すと断片的な情報でも結構役立つこともあり、まずはここからアウトプット習慣を復活させていく。

goのディレクトリ構成再考

goのディレクトリ構成のベスプラ的なものを考えていた。 ちょうど去年のこの時期くらいに考えていたことだが、改めて考え直す機会があったので。

まずはGoチームから出ているプロジェクトの目的に応じたディレクトリ構成についてのドキュメントが参考になる。 シンプルな構成なのでこれをベースに議論するのが良さそう。 ちなみにgolang-standardsというリポジトリでもディレクトリ構成が定義されていて、いかにもデファクトっぽい名前だがそういうわけではないらしい。 https://github.com/golang-standards/project-layout/issues/117

このドキュメントを元にChatGPTにUserをCreateするAPIを作成するときのディレクトリ構成とサンプルコードを書いてもらった。 わりとシンプルだが、具体的な課題が出ていないうちはこんなかんじから進めてもいいのではないかと思った。

ディレクトリ構成

server.goは必要に応じて追加

project-root/
  cmd/
    api-server/
      main.go
  internal/
    server/
      server.go
    handler/
      user.go
    model/
      user.go
    db/
      db.go

サンプルコード

model/user.go: ユーザーモデルの定義

package model

type User struct {
    ID    uint   `gorm:"primary_key"`
    Name  string `gorm:"type:varchar(100);"`
    Email string `gorm:"type:varchar(100);unique_index"`
}

db/db.go: gormによるデータベース接続の設定

package db

import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "yourproject/internal/model"
)

type Database struct {
    *gorm.DB
}

func NewDatabase(dsn string) (*Database, error) {
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        return nil, err
    }

    db.AutoMigrate(&model.User{})

    return &Database{db}, nil
}

handler/user.go: ユーザーを作成するAPIハンドラ

package handler

import (
    "net/http"
    "github.com/labstack/echo/v4"
    "yourproject/internal/db"
    "yourproject/internal/model"
)

type UserHandler struct {
    DB *db.Database
}

func (h *UserHandler) CreateUser(c echo.Context) error {
    u := new(model.User)
    if err := c.Bind(u); err != nil {
        return err
    }

    if result := h.DB.Create(u); result.Error != nil {
        return c.JSON(http.StatusInternalServerError, result.Error)
    }

    return c.JSON(http.StatusCreated, u)
}

cmd/api-server/main.go: サーバーのエントリーポイントとルーティング設定

package main

import (
    "github.com/labstack/echo/v4"
    "log"
    "yourproject/internal/db"
    "yourproject/internal/handler"
)

func main() {
    e := echo.New()
    database, err := db.NewDatabase("your-database-connection-string")
    if err != nil {
        log.Fatal(err)
    }

    userHandler := &handler.UserHandler{DB: database}

    e.POST("/users", userHandler.CreateUser)

    e.Logger.Fatal(e.Start(":8080"))
}