GeekFactory

int128.hatenablog.com

uber-go/dig を使う

GoのDIコンテナ実装である uber-go/dig を使ってみました。

github.com

基本的な使い方はGoDocに書いてあります。あえて3行にまとめると以下になります。

c := dig.New()
c.Provide(func (/* 生成に必要な型... */) /* 生成される型 */ { /* 生成処理 */ })
c.Invoke(func (/* 実行に必要な型 */) { /* 実行処理 */ })

例えば、以下のように Provide メソッドを記述すると、 interface GetUserUsecase を実装するオブジェクトを生成するには interface UserRepository が必要であると宣言できます。

c := dig.New()
c.Provide(func (r UserRepository) GetUserUsecase {
    return &GetUserUsecaseImpl{UserRepository: r}
})

Springでいうconfiguration classの @Bean と同じです。

// これはJava/Springの例
@Bean
public GetUserUsecase(UserRepository r) GetUserUsecase {
  return new UserRepositoryImpl(r)
}

依存関係に基づいて実際に値を取得するには Invoke メソッドを実行します。例えば、以下のように記述すると interface GetUserUsecase を実装するオブジェクトを取得して実行できます。

c.Invoke(func (u GetUserUsecase) {
    u.Do()
})

Provide で宣言した依存関係は再帰的に解決されます。例えば、以下のように記述すると interface GetUserUsecaseinterface UserRepositoryinterface UserAPIClient の依存関係を宣言できます。

c := dig.New()
c.Provide(func (r UserRepository) GetUserUsecase {
    return &GetUserUsecaseImpl{UserRepository: r}
})
c.Provide(func (c UserAPIClient) UserRepository {
    return &UserRepositoryImpl{UserAPIClient: c}
})

実践的な使い方としては、依存関係を別のパッケージに切り出すとよいでしょう。

package di

func New() (*dig.Container, error) {
    c := dig.New()
    if err := c.Provide(func (r UserRepository) GetUserUsecase { return &GetUserUsecaseImpl{UserRepository: r} }); err != nil {
        return nil, errors.WithStack(err)
    }
    if err := c.Provide(func (c UserAPIClient) UserRepository { return &UserRepositoryImpl{UserAPIClient: c} }); err != nil {
        return nil, errors.WithStack(err)
    }
    return c, nil
}

宣言した依存関係が正しく解決できるかテストを書いておくとよいでしょう。

package di_test

func TestInvoke(t *testing.T) {
    c, err := di.New()
    if err != nil {
        t.Fatalf("error while di.New: %+v", err)
    }
    if err := c.Invoke(func(u GetUserUsecase) {
        t.Logf("%+v", u)
    }); err != nil {
        t.Fatalf("error while c.Invoke: %+v", err)
    }
}

構造体に dig.In を埋め込むと構造体のメンバに定義されている型を解決して代入してくれます。この方法を使うと Provide に渡す関数を簡潔に書けます。

type GetUserUsecaseImpl struct {
    dig.In
    UserRepository UserRepository
    FooRepository FooRepository
    BarRepository BarRepository
}
c.Provide(func (i GetUserUsecaseImpl) GetUserUsecase { return &i })

Springでいう @Autowired に近いですね。

// これはJava/Springの例
public class GetUserUsecaseImpl {
  @Autowired private UserRepository userRepository
  @Autowired private FooRepository fooRepository
  @Autowired private BarRepository barRepository
}

なお、小規模なアプリケーションの場合は、digを使わずに手作業で依存関係を解決する方法もあります。例えば、以下のようにmainにすべての依存関係を書いてしまいます。

func main() {
    u := &GetUserUsecaseImpl{
        UserRepository: &UserRepositoryImpl{
            UserAPIClient: &UserAPIClientImpl{
                Client: http.DefaultClient,
            },
        },
    }
    if err := u.Do(context.Background()); err != nil {
        log.Fatalf("Error: %s", err)
    }
}

依存関係が単純な場合は手作業による解決でも十分ですが、複雑になってきた場合はdigのようなDIコンテナの導入を検討するとよいでしょう。

Cloud Buildで任意のシェルスクリプトを実行する

Google Cloud Buildでは公式のイメージが数多く提供されており、コマンドに対応するイメージを指定する形になっている。例えば、gcloud コマンドを使いたい場合は以下のように gcr.io/cloud-builders/gcloud イメージを指定すればよい。

steps:
  - name: gcr.io/cloud-builders/gcloud
    args:
      - app
      - deploy

このように基本的な操作を行う場合はとてもシンプルに記述できるが、条件分岐や文字列置換などの複雑な操作はサポートされていない。そのような複雑な操作を行いたい場合はbashシェルスクリプトを実行すればよい。

例えば、以下のように entrypoint を記述すると bash -c を経由してシェルスクリプトを実行できる。

steps:
  - name: gcr.io/cloud-builders/gcloud
    env:
      - TAG_NAME=$TAG_NAME
    entrypoint: bash
    args:
      - -c
      - |
        gcloud app deploy --version="${TAG_NAME//./-}"

この例では、タグ名のピリオドをハイフンに置換した上で、App Engineにアプリケーションをデプロイしている。Cloud BuildのYAMLではサポートされていない文字列置換を使いたい場合はbashを利用すればよい。

See Also

二回目の育休日記(生後3ヶ月)

娘3歳、息子3ヶ月になりました。

最近は以下のスケジュールで生活しています。

  • 7:00 起床
  • 3人分の朝食を作る
  • 着替え、顔を洗う、歯磨き(自分)
  • 娘を起こす
  • 朝食を食べる
  • 着替え、顔を洗う、歯磨き(娘)
  • 食器を洗う
  • 保育園の荷物をまとめる
  • 8:45 出発
  • 9:00 登園
  • 15:40 お迎え
  • 近くの公園で放牧する
  • 夕食を作る
  • お風呂に入れる
  • 歯磨き(自分、娘)
  • 21:00 寝かしつけ

娘の世話はパパ、息子の世話はママが主担当になっています。息子は夜中の授乳があって生活時間が異なるため、基本的にはパパとママはそれぞれ独立して動いています。病気などでバタバタしている時は朝食や夕食を別々に取ることもあります。そのままでは情報共有が不足するので、日中帯になるべく会話するようにしています。

上記以外にも調達や掃除などの定期的なタスクがあります。

  • ネットスーパーで食料を発注する(週1)
  • コープデリで食料を発注する(週1)
  • ごみを捨てる(週2)
  • ルンバをかける(週1〜2)
  • 水回りを掃除する(週1)
  • Amazon楽天などで生活雑貨を発注する
  • 家庭内共通費の精算(月1)
  • 子どもを病院に連れて行く

家にいる間はおむつ替えやトイレなどのイベントが断続的に発生します。特におむつ替えはポーリングする必要があるので大変です。そのため、何かに集中して取り組める時間が少ない悩みはあります。

ちょっと時間に余裕が出てきたので、週1〜2日はそれぞれで自由時間を持つようにしています。カフェで勉強してもいいし、買い物に出かけてもいい。この記事も娘を保育園に送った後にカフェで書いています。ずっと赤ちゃんの世話をしていると精神的に疲れるので、交代で見るようにしています。

育休中のネットワーキング

娘のクラスでは今年に入って第二子が生まれた方が数人います。産前産後や育休中はお迎え時間が早くなるため、夕方になると数人が同じ時間に帰ることになります。そのまま家に帰ると退屈してしまうので、最近は保育園の近くにある公園で一緒に遊ばせる流れになっています。

数人の中でパパは私だけですが、他のママたちと仲良くしています。幸いにして感じのいい方ばかりなのでうまくやっています。ママ友がいると新しい情報を入手できたり、子育ての悩みを共有できてよいですね。もっと育休中のパパも増えてほしいですが。

第一子の育休中は家にいることが多く、他人と会うことも少なかったので、割と孤独感がありました。今回は保育園のネットワークが役に立っています。パパの育休取得率を上げるには、国や会社の制度を整備するだけでなく、孤独を感じないような社会づくりも必要な気がします。

育児とDevOps

だいたいの家事や育児はこなせるようになってきた*1ものの、子どもの新しい服を買ったり、離乳食の計画を立てたりするといった非定型のタスクを巻き取るのは難しいと感じています。子どもの年齢が上がると、習い事をさせたり、教育方針を決めたりといったタスクが出てきます。そのようなタスクは明確な答えがなく、いろんな選択肢の中から一つの結論を決める必要があります。ちょうどDevとOpsの関係のような気がします。

ママがずっと意思決定しているとストレスを感じるので、パパも適度に意思決定を受け持つようにした方がよさそうです。オーナーシップの分担ですね。最近は娘のお出かけはパパが担当しているのでうまくいってる気がします。気がするだけかもしれないけど。

とりとめもなくなってきたのでこのへんで。

*1:妻のお墨付きあり