インドカレーファンクラブ

パソコン、カメラ

【Go】WireでよくあるController, Service周辺をDIするメモ

はじめに

MVCな構成でよくある感じのControllerがServiceに、ServiceがRepositoryに依存している感じの構成をDIする

Wireについてはここらへんが参考になる

参考

Go言語とDependency Injection | リクルートテクノロジーズ メンバーズブログ

GoのDIツールwireで知っておくと良いこと - Carpe Diem

2つ目の記事が大変わかりやすくて、そこの「interfaceに依存している場合」をより自分が使う環境に落とし込んだメモが今回の内容

なので基本的な説明は端折ってる

C#でいうDIコンテナを使わないコードだとこんな感じ
自分的にわかりやすくするためにC#で書いているけど、この話は本質的なところじゃないから読まなくていい

public class Hoge
{
    public static void Main()
    {
        var repo = new UserRepository();
        var service = new UserService(repo);
        var controller = new UserController(service);

        // あんまこういう呼び方はしない気がしてきた
        controller.Service.CreateUser();
    }
}

// Controller
public class UserController
{
    public readonly IUserService Service;
    public UserController(IUserService service) => this.Service = service;
}

// Service
public class UserService : IUserService
{
    private readonly IUserRepository repository;
    public void CreateUser() { repository.Save(); }
    public UserService(IUserRepository repository) => this.repository = repository;
}
public interface IUserService
{
    void CreateUser();
}

// Repository
public class UserRepository : IUserRepository
{
    // ほんとはDBの接続をあれこれするやつをフィールドにもつ
    public void Save() { }
}
public interface IUserRepository
{
    void Save();
}

これをgoでかいて、尚且DIコンテナのWireを使うようにしていくという話

Wireなしで書く

まずWireなしで書くと...

main.go

package main

import (
    "fmt"
)

func main() {
    // ここで依存性の注入
    repository := NewUserRepository()
    service := NewUserService(repository)
    controller := NewUserController(service)

    controller.service.CreateUser()
}

// Controller
type UserController struct {
    service IUserService
}
func NewUserController(service IUserService) *UserController {
    return &UserController{service: service}
}

// Service
type IUserService interface {
    CreateUser()
}
type UserService struct {
    repository IUserRepository
}
func (service *UserService) CreateUser() {
    fmt.Println("run UserService.CreateUser...")
    service.repository.Save()
}
func NewUserService(repository IUserRepository) *UserService {
    return &UserService{repository: repository}
}

// Repository
type IUserRepository interface {
    Save()
}
type UserRepository struct {
}
func (*UserRepository) Save() {
    fmt.Println("run UserRepository.Save...")
}
func NewUserRepository() *UserRepository {
    return &UserRepository{}
}

京都人「オーバーロードがない言語はコンストラクタ関数(Newなんとか)なんて素敵なものを使うんどすなぁ」

Wireありで書くと...

wire.go

//+build wireinject

package main

import (
    "github.com/google/wire"
)

func InitUser() *UserController {
    wire.Build(
        NewUserController,
        NewUserService,
        // IFを使っているとここがめんどくさい
        wire.Bind(new(IUserService), new(*UserService)),
        NewUserRepository,
        // ここも然り
        wire.Bind(new(IUserRepository), new(*UserRepository)),
    )
    return &UserController{}
}

$ wireするとココから以下のファイルが生成される

wire_gen.go

// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

package main

// Injectors from injector.go:

func InitUser() *UserController {
    userRepository := NewUserRepository()
    userService := NewUserService(userRepository)
    userController := NewUserController(userService)
    return userController
}

そうするとmain.goの呼び出しはこう変えられる

func main() {
    // 古い方
    // repository := NewUserRepository()
    // service := NewUserService(repository)
    // controller := NewUserController(service)

    // 新しい方
    controller := InitUser()

    controller.service.CreateUser()
}

これで $go run main.go wire_gen.go すれば動く
wire_gen.goも含めてあげることに注意(ここんところもっと上手くやれそう)