Open3

Goの依存性逆転について

Hina🐣Hina🐣

まず依存性(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がなければデータ取得ができない

参考資料これ読む
http://qiita.com/okazuki/items/a0f2fb0a63ca88340ff6
https://qiita.com/iTakahiro/items/353a11f6c9d2a927158d
https://zenn.dev/tokium_dev/articles/dependency-injection-watanabe
https://zenn.dev/yoshinani_dev/articles/c743a3d046fa78

Hina🐣Hina🐣

依存性逆転とは?

下位モジュール(具体的な実装)に依存するのではなく、抽象(Interface)に依存しよう!!
そして、下位も上位も「抽象」に依存するようにしよう!! という考え方

普通の依存関係

type UserService struct{
    rpeo *MySQLUserRepository
}
  • UserServiceはMySQLUserRepositoryに依存
  • →MySQL以外のDBにしたくなっても変更が大変!!(ほぼ不可能)

依存性逆転を適用すると

type UserRepository interface {
    FindByID(id int) (*User error)
}

type UserService struct {
    repo UserRepository
}

このように構造体のフィールドにインタフェースを持つことによって、間接的にメソッドを定義しているような状態にすることができる。

Hina🐣Hina🐣

具体的な実装例

簡単な依存性逆転とDIの実装例を示す

domain/user.go
package domain

type User struct{
	ID  int
	Name string
}

Userモデルの定義

infra/user_repository.go
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構造体のメソッドである。

usecase/user_service.go
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)を実行することができる(実装の内容は知らない)

main.go
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]