こんにちは!フリーランスエンジニアのこたろうです。
コントローラ層の実装とDIパターンについて、実践で得た知見を共有します。
コントローラの基本概念と実装
コントローラは以下の責務を持ちます:
- リクエストの解析
- ビジネスロジックの呼び出し
- レスポンスの整形
// 基本的なコントローラの構造
type ArticleController struct {
service *services.ArticleService
}
// コンストラクタ
func NewArticleController(s *services.ArticleService) *ArticleController {
return &ArticleController{service: s}
}
// メソッドの実装
func (c *ArticleController) GetArticle(w http.ResponseWriter, r *http.Request) {
article, err := c.service.GetArticle(r.Context())
if err != nil {
return
}
json.NewEncoder(w).Encode(article)
}
依存性の注入(DI)
依存性の注入とは、コンポーネントが必要とする依存関係を外部から提供する設計パターンです。これにより、コンポーネント間の結合度を低く保ち、柔軟な実装が可能になります。
実装例
func main() {
// 依存関係の構築
db := initDB()
repo := repositories.NewArticleRepository(db)
service := services.NewArticleService(repo)
controller := controllers.NewArticleController(service)
// ルータ設定
router := mux.NewRouter()
router.HandleFunc("/articles", controller.GetArticle).Methods("GET")
router.HandleFunc("/articles", controller.CreateArticle).Methods("POST")
}
インターフェースによる抽象化
インターフェースを使用することで、具体的な実装から依存関係を分離します。これにより、実装の詳細を隠蔽し、より柔軟なコード構造を実現できます。
// インターフェース定義
type ArticleServicer interface {
GetArticle(ctx context.Context) (*Article, error)
CreateArticle(ctx context.Context, article *Article) error
}
type ArticleController struct {
service ArticleServicer
}
DIのメリット
-
変更の影響範囲を限定
- コンポーネントの実装を変更しても、他への影響を最小限に抑えられる
-
コードの再利用性向上
- 依存関係を差し替えることで、様々なコンテキストで再利用可能
-
保守性の向上
- 機能追加や仕様変更への対応が容易
- 実装の詳細を隠蔽することで、コードの理解が容易に
-
開発効率の向上
- チームでの並行開発が容易
- コードの見通しが良くなる
まとめ
- コントローラはビジネスロジックとリクエスト処理を橋渡し
- DIで依存関係を適切に管理
- インターフェースで実装を抽象化
- これらにより保守性と開発効率が向上
参考文献
-『Goで作るはじめてのWebアプリケーション改訂版』技術書典