Goの依存性逆転について

まず依存性(Dependency)とは
あるモジュール(パッケージ、構造体、関数)が別のモジュールがないと成立しない関係のこと
→ 「AがBに依存」=Aの中でBを使っている、 つまりBがないとAは動かないということ
依存関係の例
type UserRepository struct {
db *sql.DB
}
func (r *UserRepository) FindByID(id int) (*User, error) {
row := r.db.QueryRow("SELECT * FROM users WHERE id = ?", id)
...
}
- UserRepositoryは*sql.DBに依存
- dbがなければデータ取得ができない
参考資料これ読む

依存性逆転とは?
下位モジュール(具体的な実装)に依存するのではなく、抽象(Interface)に依存しよう!!
そして、下位も上位も「抽象」に依存するようにしよう!! という考え方
普通の依存関係
type UserService struct{
rpeo *MySQLUserRepository
}
- UserServiceはMySQLUserRepositoryに依存
- →MySQL以外のDBにしたくなっても変更が大変!!(ほぼ不可能)
依存性逆転を適用すると
type UserRepository interface {
FindByID(id int) (*User error)
}
type UserService struct {
repo UserRepository
}
このように構造体のフィールドにインタフェースを持つことによって、間接的にメソッドを定義しているような状態にすることができる。

具体的な実装例
簡単な依存性逆転とDIの実装例を示す
package domain
type User struct{
ID int
Name string
}
Userモデルの定義
package infra
import (
"fmt"
"test/domain"
)
type MySQLUserRepository struct{}
func NewMySQLUserRepository() *MySQLUserRepository {
return &MySQLUserRepository{}
}
func (r *MySQLUserRepository) FindByID(id int) (*domain.User, error) {
fmt.Println("DBからユーザーを取得")
return &domain.User{ID: id, Name: "Yura"}, nil
}
Repository層では実際にDBアクセスする実装がされている(今回は固定文字列のNameを返すようにしているが)
このDBアクセスを実現する関数(FindByID)はSerive層で定義されているインターフェースを実装しており、MySQLUserRepository構造体のメソッドである。
package usecase
import "test/domain"
type UserRepository interface {
FindByID(id int) (*domain.User, error)
}
type UserSerive struct {
repo UserRepository
}
func NewUserService(repo UserRepository) *UserSerive {
return &UserSerive{repo: repo}
}
func (s *UserSerive) GetUserByID(id int) (*domain.User, error) {
return s.repo.FindByID(id)
}
Service層ではRepository層で実装される関数のインターフェースを定義しており、そのインターフェースをもつUserService構造体を定義することによって、抽象的にUserRepositoryが持つ関数(今回はFindByID)を実行することができる(実装の内容は知らない)
package main
import (
"fmt"
"test/infra"
"test/usecase"
)
func main() {
repo := infra.NewMySQLUserRepository()
service := usecase.NewUserService(repo)
//Service層の関数へ
user, _ := service.GetUserByID(1)
fmt.Println(user.Name)
}
main.goからはService層しか意識しないのでService層の関数を呼び出したい
user, _ := service.GetUserByID(1)
↓
そのためにはUserSeriveのインスタンスが必要(コンストラクタを呼び出す必要がある)
service := usecase.NewUserService(repo)
↓
そのためにはrepo,つまりUserRepository(Interface)を満たす何かが必要
UserReposirotyはRepositiory層のMySQLUserRepository構造体が実装しており、この実態をrepoとして扱うことができる。
[main.go]
↓
[user_service.go]
↓依存
[UserRepositoryインターフェース]
↑依存
[user_repository.go]