概要
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で依存すべきかと思う。
いや、それはこういう問題が生じるぞ!などご意見あればください。
よろしくお願いします。
参考