1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[golang] モジュール間の結合は関数インターフェースで

Last updated at Posted at 2021-04-19

概要

Clean Architectureを学んだりオブジェクト指向に詳しかったりすると、サービス同士の結合はinterfaceを介して行うもんだと思ってしまう。Golangの教科書にもそう書いてある。だが、関数interfaceによる結合(DI)のほうがベターじゃない?という話。

例えばhttpサーバーを作る場合、こんな風になる。

r := mux.NewRouter()
r.HandleFunc("/hoge", /*hoge*/).Methods("GET")

/*hoge*/をどう書こうか?

1. structをinterfaceで結合

type HogeHandler struct {}
func (h *HogeHandler) ServeHTTP(w http.ResponseWriter, req *http.Request){
   // get hoge
}
r := mux.NewRouter()
h := &HogeHandler{}
r.HandleFunc("/hoge", h ).Methods("GET")

引数にHogeHandler structを渡している。なぜこれができるかというと、net/httpにHandlerというinterfaceが定義されているから。

type Handler interface {
   ServeHTTP(http.ResponseWriter, *http.Request)
}

HogeHandler structはこのinterfaceを実装している。

2. 関数を関数interfaceで結合

カンタンな関数ならstructにする必要はないので、

func handler(w http.ResponseWriter, req *http.Request){
   // get hoge
}
r := mux.NewRouter()
r.HandleFunc("/hoge", handler ).Methods("GET")

こう書くことも多いだろう。なぜこう書けるかというと、handler関数はHandlerFuncを実装しているから・・・

// https://golang.org/src/net/http/server.go?s=59707:59754#L1950
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

というのは半分だけ正しい。handler関数はHandlerFuncの実装でありかつ、HandlerFuncはHandler interfaceを実装しているから。

3. structを関数interfaceで結合

本題。

type HogeHandler struct {}
func (h *HogeHandler) GetHoge(w http.ResponseWriter, req *http.Request){
   // get hoge
}
func (h *HogeHandler) DeleteHoge(w http.ResponseWriter, req *http.Request){
   // delete hoge
}
r := mux.NewRouter()
h := &HogeHandler{}
r.HandleFunc("/hoge", h.GetHoge ).Methods("GET")
r.HandleFunc("/hoge", h.DeleteHoge ).Methods("DELETE")

HandlerFuncの引数に、関数(メソッド)を渡している。
同じリソースに対する複数の処理を一つのサービスにまとめながら、handler登録を柔軟に(メソッド名を気にせず)できるので、私はこれが好き。

net/http package

type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

golangは、関数がinterfaceを実装できるんだ・・・・この書き方勉強になる。

結論

ここで言いたい事は、net/httpの話じゃない。

一般的にstruct(実体)に依存するのではなく、interfaceに依存すべきとはよく聞くが、最も疎結合なのは関数interface依存だと思う。

もちろん、メソッドのセットに意味があるならその限りではない。たとえば全てのHandlerにGET/POST/DELETE/PUTの各メソッドの実装を強制したいなら、そういうinterfaceを作ってinterfaceで依存すべきかと思う。

いや、それはこういう問題が生じるぞ!などご意見あればください。
よろしくお願いします。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?