🙈

クリーンアーキテクチャをGoでする場合にInterfaceを無くす

2022/07/04に公開

クリーンアーキテクチャを知っている人向けです。
上辺しか喋らないので話半分です。
途中でクリーンアーキテクチャではなくなります。

Interfaceをなくそうと思ったきっかけ

とある方からGoで無駄なInterfaceは良くないと言われたことがきっかけです。
使いまわせないInterfaceはあまり良くないらしい?です。

UseCase層を無くす

goではこんな風に実装しますが複雑さをなくすためにレイヤーを一つ削除しようという魂胆です。
今回は省いていますが実際はこの実装例にInterfaceが実装されています。

type Entity struct {
  ...
}

type Repository struct {
  ...
}

func (r *Repository) Insert(entity *Entity) {
  ...
}

type Usecase struct {
  Repository *Repository
}

func (u *Usecase) Post(entity *Entity) {
  r.Repository.Insert(entity)
}

type Controller struct {
  Usecase *Usecase
}

func (c *Controller) Post(ctx) {
  entity = ctx.Hoge()
  c.Usecase.Post(entity)
}

func main() {
  repo := Repository{}
  use := UseCase {Repository: repo}
  ctrl := Controller {Usecase: use}

  ctrl.Post(ctx)
}

実装例
単純にUsecase構造体を削除してControllerにRepositoryを持たせます。

type Entity struct {
  ...
}

type Repository struct {
  ...
}

func (r *Repository) Insert(entity *Entity) {
  ...
}

type Controller struct {
  Repository *Repository
}

func (c *Controller) Post(ctx) {
  entity = ctx.Hoge()
  c.Repository.Insert(entity)
}

func main() {
  repo := Repository{}
  ctrl := Controller {Repository: repo}
  ctrl.Post(ctx)
}

Interfaceを無くす

Interfaceをただ削除するとmockが作れなくなりテストが行いにくくなります。
のでまずController層とEntity層を切り離します。
ControllerにEntityの実態を持たせないようにします。
この時点でもうCleanアーキテクチャではないですが……。

実装例
実態を持たせないようにしていきます。
この時点でInterfaceを削除できます。

type Entity struct {
  ...
}

type Repository struct {
  ...
}

func (r *Repository) Insert(entity *Entity) {
  ...
}

type Controller struct {}
func (c *Controller) Post(ctx) {
  entity = ctx.Hoge()
  c.Repository.Insert(entity)
}

func main() {
  repo := Repository{}
  ctrl := Controller {Repository: repo}
  ctrl.Post(ctx)
}

そして関数の引数をInterfaceが変わりにしてControllerにRepositoryの関数を渡せるようにします。

type Entity struct {
  ...
}

type Repository struct {
  ...
}

func (r *Repository) Insert(entity *Entity) {
  ...
}

type Controller struct {}
func (c *Controller) Post(ctx, insert func(*Entity)) {
  entity := ctx.Hoge()
  insert(entity)
}

func main() {
  repo := Repository{}
  ctrl := Controller {Repository: repo}
  ctrl.Post(ctx, repo.Insert)
}

これでInterfaceを無くせました。

問題点

必要な関数が増えると引数も増える
意図してない関数が渡される可能性がある

GitHubで編集を提案

Discussion