Embedding を利用してアプリケーションのサービスを切り替える

## はじめに
プロダクトで利用する一部のデータは別プロダクト（サービス）との gRPC 通信を経由して取得しています．担当しているプロダクトで，今後問い合わせる先のサービスが変更されることから，サービスの切り替え対応のタスクに取り組みました．この際，円滑な切り替えを実現するために，Go の embedding を利用した方法をとったので，それを紹介します．

## Embedding とは
Go には典型的なサブクラス化の概念はないが，構造体やインターフェースの中に型を [embedding](https://go.dev/doc/effective_go#embedding) することで，実装を部分的に借りることができます．
> Go does not provide the typical, type-driven notion of subclassing, but it does have the ability to “borrow” pieces of an implementation by embedding types within a struct or interface.

サブクラス化（継承）と異なる点としては，embed された型は外側の型のメソッドとして呼び出せますが，メソッドが呼び出された際のレシーバーは内側の型のものになります．
> When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one.

```golang
type Inner struct {
	Name string
}

func (i *Inner) Greet() {
	fmt.Printf("Hi, %s\n", i.Name)
}

type Outer struct {
	Name string
	Inner
}

func main() {
	outer := Outer{
		Name:  "Alice",
		Inner: Inner{Name: "Bob"},
	}

	// outer.Greet() では Inner 構造体の Name が表示される
	outer.Greet() // -> "Hi, Bob"
}
```

また，embedding した型は名前が衝突する問題が発生するため，以下のルールで解決されています．

- 同じ名前のフィールドやメソッドがある場合，より深いネストに入っているものが隠されます．
> a field or method X hides any other item X in a more deeply nested part of the type.
```golang
...

func main() {
	outer := Outer{
		Name:  "Alice",
		Inner: Inner{Name: "Bob"},
	}

	// outer.Name では Outer 構造体の Name が表示される
	// Inner 構造体の Name は同名のため隠されている
	fmt.Println(outer.Name) // -> "Alice"
}
```

- 同じ名前のフィールドやメソッドが同じネストの深さにある場合，通常はエラーが返ります．しかし，その名前が型定義の外で一度も使われない場合は，問題になりません．
> if the same name appears at the same nesting level, it is usually an error[, h]owever, if the duplicate name is never mentioned in the program outside the type definition, it is OK.
```golang
type FirstInner struct {
	Name string
}

func (i *FirstInner) Greet() {
	fmt.Printf("Hi, %s\n", i.Name)
}

type SecondInner struct {
	Name string
}

func (i *SecondInner) Hello() {
	fmt.Printf("Hi, %s\n", i.Name)
}

type Outer struct {
	FirstInner
	SecondInner
}

func main() {
	outer := Outer{
		FirstInner:  FirstInner{Name: "Alice"},
		SecondInner: SecondInner{Name: "Bob"},
	}

	// outer.Name が FirstInner.Name と SecondInner.Name のどちらかわからない
	fmt.Println(outer.Name) // -> エラー："ambiguous selector outer.Name"

	// outer.Name が直接参照されないため，エラーにならない
	outer.Greet() // -> "Hi, Alice"
	outer.Hello() // -> "Hi, Bob"
}
```

## サービスの切り替え実装について
プロダクトで利用しているサービスは元々以下のように実装されていました．
```golang
// Service ではプロダクトが利用するサービスが持つべき機能を定義
type Service interface {
	GetUser()
	UpdateUser()
}

// 既存の gRPC を利用するサービス
type ExistingService struct {
	SomeClient proto.SomeServiceClient
}

// ExistingService は Service インターフェースのどちらのメソッドも実装している
func (s ExistingService) GetUser()    {}
func (s ExistingService) UpdateUser() {}

// Service を返す関数
func NewService() Service {
	return ExistingService{
		SomeClient: proto.NewSomeServiceClient(),
	}
}
```

そこで，以下のように切り替え先のサービスを別の構造体で定義することで，メソッド単位で利用するサービスを切り替えできるようになります．
```golang
...

// 新規の gRPC を利用するサービスで，embedding を利用した構造体
type ReplacingService struct {
	SomeClient proto.SomeServiceV2Client // gRPC client が変わる想定
	ExistingService
}

// ReplacingService は Service インターフェースの片方のメソッドしか実装していない
func (s ReplacingService) GetUser() {}

// Service を返す関数を，上記の追加に合わせて変更
func NewService() Service {
	return ReplacingService{
		SomeClient: proto.NewSomeServiceV2Client(),
		ExistingService: ExistingService{
			SomeClient: proto.NewSomeServiceClient(),
		},
	}
}
```

上記の場合，`GetUser()` は新たに実装している `ReplacingService` のメソッドとして呼ぶことができ，まだ実装できていない `UpdateUser()` は `ExistingService` のメソッドを呼ぶことができ，`Service` インターフェースを満たしている状態を維持できます．

## まとめ
Embedding を利用することでサービスの切り替えを DI などを用いずに実現することができました．サブクラス化とは違う挙動には気をつけなければなりませんが，とても強力な機能なので機会があれば是非利用しましょう！