はじめまして!フリーランスエンジニアのこたろうです。
アプリケーションのフォルダ構成の改善とWebフレームワークの比較について、学びで得た知見を共有します。
フォルダ構成の改善
1. なぜCORS設定とルーティングを分離するのか?
プログラムを書く際、1つのファイルに多くの処理を書くと、以下の問題が発生します:
- コードが読みにくくなる
- 修正が難しくなる
- 他の人が理解しにくい
そこで、CORS設定とルーティングを別ファイルに分けることで:
- 各ファイルの役割が明確になる
- コードが整理され、読みやすくなる
- 修正が容易になる
CORS設定の分離例
// config/cors.go
// CORSとは?
// Cross-Origin Resource Sharing(CORS)は、
// 異なるドメインからのアクセスを制御する仕組みです。
// 例:localhost:3000のフロントエンドから
// localhost:8080のバックエンドにアクセスする場合に必要
func CorsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// どのドメインからのアクセスを許可するか
w.Header().Set("Access-Control-Allow-Origin", "*")
// どのHTTPメソッドを許可するか
w.Header().Set("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS")
// どのヘッダーを許可するか
w.Header().Set("Access-Control-Allow-Headers",
"Content-Type, Authorization")
// プリフライトリクエストの処理
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
ルーティングの分離例
// config/router.go
// ルーティングとは?
// URLと処理(関数)を紐付けることです
// 例:/api/todosにアクセスしたら、GetTodos関数を実行する
func NewRouter() *mux.Router {
router := mux.NewRouter()
// GETメソッド:データの取得
router.HandleFunc("/api/todos", controllers.GetTodos).
Methods("GET")
// POSTメソッド:データの作成
router.HandleFunc("/api/todos", controllers.CreateTodo).
Methods("POST")
// PUTメソッド:データの更新
router.HandleFunc("/api/todos/{id}", controllers.UpdateTodo).
Methods("PUT")
// DELETEメソッド:データの削除
router.HandleFunc("/api/todos/{id}", controllers.DeleteTodo).
Methods("DELETE")
return router
}
2. レイヤードアーキテクチャとは?
アプリケーションを以下の3つの層に分けて管理する方法です:
-
Controllers層(入口)
- ユーザーからのリクエストを受け取る
- データの形式をチェック
- Services層に処理を依頼
- 結果をユーザーに返す
-
Services層(ビジネスロジック)
- アプリケーションの主要な処理を行う
- データの加工や計算
- 複雑なチェック処理
- Repositories層にデータの操作を依頼
-
Repositories層(データ操作)
- データベースとのやり取り
- データの保存、取得、更新、削除
フォルダ構成例
├── controllers/
│ └── todo_controller.go (リクエストの受付)
├── services/
│ └── todo_service.go (ビジネスロジック)
└── repositories/
└── todo_repository.go (データベース操作)
実装例と説明
// repositories/todo_repository.go
// データベース操作を担当
type TodoRepository interface {
FindAll() ([]Todo, error) // 全データ取得
FindByID(id int) (*Todo, error) // 1件取得
Create(todo *Todo) error // データ作成
Update(todo *Todo) error // データ更新
Delete(id int) error // データ削除
}
// services/todo_service.go
// ビジネスロジックを担当
type TodoService struct {
repo TodoRepository // repositoryを使用
}
func (s *TodoService) GetTodos() ([]Todo, error) {
// 1. repositoryからデータを取得
todos, err := s.repo.FindAll()
if err != nil {
return nil, err
}
// 2. 必要な加工や計算を行う
// (例:期限切れのTODOに印をつけるなど)
for i := range todos {
if todos[i].DueDate.Before(time.Now()) {
todos[i].IsExpired = true
}
}
return todos, nil
}
// controllers/todo_controller.go
// リクエストの受付を担当
type TodoController struct {
service *TodoService // serviceを使用
}
func (c *TodoController) GetTodos(w http.ResponseWriter, r *http.Request) {
// 1. リクエストのチェック
// (必要に応じてバリデーションなど)
// 2. serviceに処理を依頼
todos, err := c.service.GetTodos()
if err != nil {
// エラー時の処理
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 3. 結果をJSONで返す
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(todos)
}
このように分けることで:
- コードが整理され、理解しやすくなる
- テストが書きやすくなる
- 修正が容易になる
- チーム開発がスムーズになる
gorilla/muxとginの比較
gorilla/mux
標準パッケージnet/httpを拡張したシンプルなフレームワーク
メリット
-
学習が簡単
- 標準パッケージの知識がそのまま使える
- シンプルな機能で分かりやすい
-
柔軟なルーティング
- URLに変数を含められる
- HTTPメソッドで分岐できる
// gorilla/muxの例
r := mux.NewRouter()
// URLに変数を含む例(/api/todos/1 など)
r.HandleFunc("/api/todos/{id}", getTodo).Methods("GET")
// 複数のHTTPメソッドを設定
r.HandleFunc("/api/todos", handler).
Methods("GET", "POST")
gin
高機能で高速なWebフレームワーク
メリット
-
便利な機能が豊富
- バリデーション(データチェック)
- ミドルウェア(共通処理)
- エラーハンドリング
-
高いパフォーマンス
- 処理が速い
- メモリ使用量が少ない
// ginの例
r := gin.Default()
// シンプルなルーティング
r.GET("/api/todos/:id", getTodo)
// バリデーション機能の例
type Todo struct {
Title string `binding:"required,min=1,max=100"`
Done bool
}
どちらを選ぶべき?
-
小規模なプロジェクト
→ gorilla/mux- シンプルで学習コストが低い
- 標準パッケージの勉強になる
-
大規模なプロジェクト
→ gin- 便利な機能が揃っている
- パフォーマンスが重要な場合
参考文献:
-『Goで作るはじめてのWebアプリケーション改訂版』技術評論社