Software Engineer Blog

エンジニアブログです。技術情報(Go/TypeScript/k8s)や趣味の話も書くかもです。

1 ヶ月時間が空いたのでなにかする

概要

1 ヶ月ほど時間に余裕ができる期間ができたので、諸々スキルアップしたいと思っています。
やることリストを公開してプレッシャーを少しでもかけていきます。

やることリスト

このあたりを毎日地道にやっていきたいです。

  • OSS へ commit する
  • Recout をサービス公開する
  • LeetCode 60 問を全て解く
  • 本を毎日 10 ページは読む
  • (1 人旅をする)
  • (各種家庭向けの諸々)

詳細

各やりたいことの詳細です

OSS へ commit する

目的

OSS への commit を自然に行えるようになること

内容

OSS へ何かしら commit したいと思います。対象のめどはつけているのですが、 とりあえず commit するというところまで公開します。
あまり経験がない + いつも助けられているので少しでも貢献したい という 2 点がモチベーションです。

Recout をサービス公開する

目的

個人サービスを作りきって公開する経験を積みたいため

内容

Recout というサービスを公開しようと開発しています。

midori5.hatenablog.com

しっかりと公開して運用するところまで作りきれたことがないため、一度出してみたいと思っています。
(ここに記載した内容の日々の進捗を記録したいと思います)

LeetCode 60 問を全て解く

目的

データ構造、アルゴリズムについて実装ベースでの理解を深める

内容

下記記事に記載されている問題にチャレンジしようと思います。

1kohei1.com

元々、勉強不足でコンピュータサイエンス周りはとても弱いです。
本を読む or 講座を受ける 等も考えられましたが、おそらく私の学習スタイル的に 「問題を必死に解く」が最も良さそうなので選びました。(あと面白そうなので)

本を毎日 10 ページは読む

目的

本を読む習慣をつける

内容

本を読むのは好きなので、そこまで問題ではないのですが、あまり継続的に読んでなかったので、 ちょっとずつ読んでいこうかと思います。

所感

ちょっと色々あげすぎた感もありますが、一気にやるのではなく毎日コツコツと 続けていって達成できていれば良いかなぁと思います。

GitHubに草を生やすようにアウトプットを可視化するツールを作成している話

概要

recout という個人の学習を可視化するツールを作成しています。
(現状はアカウント登録部分が整備できていないので、URLは非公開にしてます。)

f:id:midori5:20190426195021p:plain
recout

できること

自分がしたアウトプットを投稿すると、GitHubのコミット履歴のように色が付きます。
色が付く部分は、 id:a-know さんが作成された Pixela をそのまま利用しています。
とても素晴らしいサービスで、リリースされた当時からなにか使いたいなぁと思っていました。
ありがとうございます!!!

blog.a-know.me

目的

基本的には、個人用として作成しています。
作ろうと思った動機ですが、継続的な学習ができなくなっていると感じたためです。 以前、Write Code Everyday を実施して無事1年達成できたのですが、一度途切れてしまうと 学習量が減ってしまったように感じていました。

Write Code Every Day (1年目) - Software Engineer Blog

私個人の話ですが、「継続的にやったことが見える化されること」が学習を続けるキーポイントかなと思っています。 commit だけではなくて、学習全般を記録できるようにすることで、継続的に学ぶ土台を作りたいと考えています。

今後の追加予定機能

構成

ソースコードは以下に置いています。

github.com

Front

Nuxt.js を利用していますが、必要なかったかなという感じです。 Vue.js は触ったことがあったので、学習としてNuxt.jsを導入してみましたが、 全く機能を使っていない状態です。

また静的サイトホスティングとして、 Netlify を利用しています。 (GAE上で良いのでは?と思っているので、まとめようかと考えています。)

Backend

GAE/Go で構築しています。学習の投稿データを溜める必要があったので、Datastoreを利用しています。 mercari/datastore を使うことで簡単にアクセスできました。

github.com

GAE自体ははじめて使ったのですが、サクッと作ってデプロイできる点が非常に良いと思いました。
また、Datastoreもテーブルを事前に作ることなく、データを追加できた点(追加されたデータをもとにEntityが作成される) が簡単でよかったです。

所感

まだまだ作り始めですが、とりあえず動くものができてよかったなという印象です。 いままでは途中で終わってしまうことが多かったので、ひとつ突破できたかと思います。
4月中にブログを書くことが目標だったので、URL公開には至りませんでしたが、引き続き開発を続けます。

Wire を活用した Web API パッケージ構成

概要

DI Tool であるWireを利用した際の、Web API のパッケージ構成について考えてみました。 また、導入するメリットについてもあわせて記載しております。

github.com

パッケージ構成

パッケージは下記の通りに組みました。ドメイン層込みのパッケージ構成に、provider パッケージを追加しています。
providerパッケージ配下に、wireを通してgenerateするコードを配置します。

パッケージ構成

.
└── wire
  ├── main.go        # package main
  ├── app            # アプリケーション層 (ロジック)
  ├── handler        # API Entrypoint
  ├── provider       # Dependency 管理
  ├── domain         # ドメインモデル 管理
  └── infra          # データ層の実装
      └── db         # DB系実装

依存関係

パッケージ間の依存関係を簡単な図にしてみました。 appパッケージの構造体は、providerを経由して、取得するようにしています。

f:id:midori5:20190203232238p:plain

ソースコード

github.com

wire を利用している部分のソースコードは下記のようになっています。 このあたりは、wire のチュートリアル 等からそのまま利用しています。
providerの役割は、appを依存関係を解決した上で生成することになります。
apphandlerから呼び出すので、下記のように実装します。

package handler

import (
    "io"
    "net/http"

    "github.com/gmidorii/api-design/wire/provider"
)

func Hello(w http.ResponseWriter, r *http.Request) {

    // providerよりapp を取得
    // Hello structにはすでに依存関係が注入されている
    hello, err := provider.InitHelloApp()
    if err != nil {
        return

    message, err := hello.GetMessage("id")
    if err != nil {
        return
    }

    io.WriteString(w, message)
}

handler以降は、 appdomaininfra/db の順に呼び出して、データを取得しています。
このあたりに関しては、一般的に考えられているものに沿っているのではないかと考えています。
参考

www.slideshare.net

メリット

wire を導入する前と後で、何が良くなったのかについてですが、2点あると考えています。

  1. 利用元に変更を加えることなくアプリケーション層の依存性の修正ができる
  2. 依存関係を作る処理をwireで自動生成することができる

1. 利用元に変更を加えることなくアプリケーション層の依存性の修正ができる

アプリケーション層の実装の際、テスタビリティを上げるため、interfaceを通して互いに依存させ、 実態をInjectするといった実装をします。 この際、コンストラクタインジェクションを利用して、依存性のInjectを実施します。
(Goの場合は、Newはじまり関数を利用して、structを生成します。
余談: ここでは、フィールドをパッケージプライベートにすることで、外部からはコンストラクタでのみ依存関係を制御するようにしています。)

app package

type Hello struct {
    user domain.UserRepository
}

func NewHello(user domain.UserRepository) Hello {
    return Hello{
        user: user,
    }
}

上記の実装の際に、アプリケーション層の仕様が変更され、依存を増やしたい(新しいデータを取りに行きたい)といったことは、 よくあるかと思います。 コードの変更は、依存をコンストラクタで受け取れるように修正し、受け取った依存性をフィールドにつめるようにします。

type Hello struct {
  user domain.UserRepository
  // 増やしたい依存
  book domain.BookRepository
}

// 引数を増やす
func NewHello(user domain.UserRepository, book domain.BookRepository) Hello {
    return Hello{
    user: user,
    booK: book,
    }
}

この変更を加えた場合、呼び出し元をすべて修正する必要があります。(コンストラクタの引数が変わったため)

before

user := db.NewUser()
hello, err := app.NewHello(user)

after

user := db.NewUser()
book := db.NewBook()
hello, err := app.NewHello(user, book)

この変更は、実際に行うと面倒ですし、変更したいところ以外にも差分がでてしまうので、 あまりよろしくないのではないかと思います。 この問題を解決するに当たり、依存関係を管理する中間層(=provider)を用意してあげる方法をとります。 このようにすることで、呼び出し元の修正なく、appの依存関係を変更することができるようになりました。
(※ ちなみにここまでのことは、wireを利用せずとも実現可能です。)

package provider

func InitHelloApp() (app.Hello, error) {
    user := db.NewUser()
    book := db.NewBook()
    hello := app.NewHello(user, book)
    return hello, nil
}

2. 依存関係を作る処理をwireで自動生成することができる

では、wireを導入するメリットですが、これは依存関係を解決した上で、コードを自動で生成してくれることに あるのではないかと思います。 wireで生成するためには、生成元コードが必要になりますが、それは下記のように実装します。

func InitHelloApp() (app.Hello, error) {
    wire.Build(db.NewUser, app.NewHello)
    return app.Hello{}, nil
}

wire.Build に依存関係を生成する関数を引数として、渡してあげるだけです。あとは、wire側で依存関係を整理して、コードを生成してくれます。

生成後

func InitHelloApp() (app.Hello, error) {
    userRepository := db.NewUser()
    hello := app.NewHello(userRepository)
    return hello, nil
}

特筆すべき点は、 hello structにuserRepositoryが必要であることを読み取って、生成する順序を自動で調整してくれる点 です。 wire.Build() に渡す順番を実装者は意識する必要はありません。(適当な順番で渡してもうまく生成してくれました。)
また、依存が足りない場合は、生成時にエラーを吐いてくれます。

エラー例

$ wire gen github.com/gmidorii/api-design/wire/provider
github.com/gmidorii/api-design/wire/provider: generate failed
~/src/github.com/gmidorii/api-design/wire/provider/hello.go:11:1: inject InitHelloApp: no provider found for github.
com/gmidorii/api-design/wire/domain.UserRepository

所感

wireを実際のプロダクトコードで利用する際は、どういった構成にすれば良いか、実装してまとめてみました。 記載したメリットの他に、依存性を切り出す意識をパッケージ構成レベルですることができるので、テスタブルなコードを強制できる点も、 良いのではないのかなぁと思いました。実際に使っていきたいと思います。

参考

React.js 学習 (Soft Skillsフォーマット)

概要

React.jsに入門してみました。その際に、 Soft Skillsで言及されていたフォーマットに沿って学習しました

Soft Skills

SOFT SKILLS ソフトウェア開発者の人生マニュアル

SOFT SKILLS ソフトウェア開発者の人生マニュアル

書籍内にて、学習のフォーマットが提示されていたので、そのとおりにやってみています。
番号が振ってある部分が、フォーマットになります。

フォーマット

1. 日付

2018/08/27 ~ 2018/09/12 (=> 伸びて 2018/10/09になりました)

2. スコープを調べる

調査

メモ

  • viewの状態を動的に変えれる
  • 親と子のコンポーネントがあって、うけわたしする
  • 状態遷移があって、どの状態で何をするかを考える必要がありそう
  • Facebook
  • 入力した値を即座に反映したいときに便利
  • 仮想domは速いらしい
  • reactiveプログラミング

3. スコープを決める

  • React.jsを利用したシングルページアプリを作成
  • TypeScript+React.jsの構築/コンパイルができる
  • アプリを外部公開できる

4. 成功の基準

React.jsを利用した、シングルページアプリを公開してブログを書く

5. 参考資料を見つける

(できる限りたくさん見つけるのが良い模様)

Essence

  • Declarative
  • component base
    • render()Componentを記載
    • this.propsでpropertyにアクセス
  • Virtual Domは高速
    • Virtual Dom全更新→差分計算→Domへの全適用
    • Domの全更新より早い
  • props
    • 外部とのInteface
    • Immutableに扱う必要がある
  • state
    • Component内部の状態を扱う変数

6. 学習プラン作成

step title Goal describe
1 TS + React環境構築 Hello World !表示 - tsコンパイルができる環境作成
- webpackを利用する
2 公式チュートリアル実施 チュートリアル終了 - 公式チュートリアルをそのまま実施する
- ただし、TypeScript対応はする
3 シングルページ作成 ✅ローカルでページが見れる - 作成物は要検討
- 掲示
4 公開 ✅特定のIP/ドメインでリクエストしてページが見れる - Nowを利用して公開
5 このページをブログ公開 ブログとして公開される

7. リソースを絞り込む

8. 使い始められるようにする方法を学ぶ

環境構築

mkdir -p dist src/components
npm init

# react domと type packageを取得
npm install --save react react-dom @types/react @types/react-dom

# typescript loader(tsconfig.json) + sourcmap作成(debug時にtsから見れるようになる)
npm install --save-dev typescript awesome-typescript-loader source-map-loader

その他

# globalにinstallしたpackage list
npm ls -g

# webpack
npm install -g webpack
# webpack-cli or webpack-commandのどちらかが必要
npm install -g webpack-command

# --save-dev(-D) => 開発時のみ利用のオプション

tsconfig.json

{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true,
    "noImplicitAny": true,
    "module": "commonjs",
    "target": "es5",
    "jsx": "react"
  },
  "include": [
    "./src/**/*"
  ]
}

webpack.config.js

module.exports = {
  entry: "./src/index.tsx",
  output: {
    filename: "bundle.js",
    path: __dirname + "/dist"
  },
  devtool: "source-map",
  resolve: {
    extensions: [".ts", ".tsx", ".js", ".json"]
  },
  module: {
    rules: [
      { test: /\.tsx?$/, loader: "awesome-typescript-loader"},
      { enforce: "pre", test: /\.js$/, loader: "source-map-loader"}
    ]
  },
  externals: {
    "react": "React",
    "react-dom": "ReactDOM"
  }
}

.gitignore

node_modules
dist

index.ts

import * as React from 'react'
import * as ReactDOM from 'react-dom'

ReactDOM.render(undefined, document.getElementById('root'))

HTML

<body>
  <div id="root"></div>
  
  <!-- Dependencies -->
  <!-- https://reactjs.org/docs/getting-started.html#development-vs.-production-builds -->
  <script src="./node_modules/react/umd/react.development.js"></script>
  <script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
  <!-- Main -->
  <script src="./dist/bundle.js"></script>
</body>

tslint + perettier

npm install -D tslint prettier tslint-config-prettier tslint-plugin-prettier tslint-config-standard

tslint.json

{
  "rulesDirectory": ["tslint-plugin-prettier"],
  "extends": ["tslint-config-standard", "tslint-config-prettier"],
  "rules": {
    "prettier": [
      true, {
        "singleQuote": true,
        "semi": false
      }
    ]
  }
}

テスト環境構築

# jest
npm install -D jest babel-jest babel-preset-env babel-preset-react react-test-renderer ts-jest
# 型ファイル
npm install -D @types/react-test-renderer @types/jest

# package.json
"scripts": {
  "test": "jest"
},
"jest": {
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js"
    ],
    "testMatch": [
      "**/__tests__/*.+(ts|tsx|js)"
    ]
}

9. 遊び回る

チュートリアル実施

  • JavaScriptではclassのconstructorで、super(props)を最初に呼ぶ必要がある
  • 子Component同士でやりとりするのではなく、親Componentを介してstateを共有する
  • immutable
    • Complex Features Become Simple
    • Detecting Changes
    • Determining When to Re-render in React
  • It’s strongly recommended that you assign proper keys whenever you build dynamic lists
  • チュートリアル完了
    • +α で押されたボタンをハイライトする機能を追加

10. 役に立つことができるところまで学ぶ

掲示板作成

- ソースコード

github.com

  • 機能
    • 入力した文字をそのまま下に表示
    • clear

Nowへデプロイ

  • expressでサーバーを立てる
    • index.js
  • webpack/webpack-commandをdevdependenciesに追加
    • サーバー上でbuildする際にwebpackが必要になるため
npm install -D webpack webpack-command
  • package.jsonにbuildとstartを追加
{
   "scripts": {
       "build": "./node_modules/.bin/webpack",
       "build-auto": "webpack --progress --watch",
       "start": "node index.js"
   }
}

11. 教える

ブログ書く

所感

(後日記載)

AWSコンソールからShellを実行できる機能を試してみた (AWS Systems Manager Session Manager for Shell Access)

概要

AWSGCPのCloud Shell的な機能が出たとあり、ちょっと面白そうなので触ってみました。

aws.amazon.com

やったこと

  • ログインIAMユーザーに AmazonSSMFullAccess ポリシーをアタッチ
  • AmazonEC2RoleforSSM ポリシーをアタッチしたロールを作成
  • 作成したロールをアタッチしたEC2を作成
  • AWS Systems Manager > Run Command から AWS-UpdateSSMAgent を実行してssm agentをアップデート
  • AWS Systems Manager > Session Manger から起動

スクリーンショット

起動画面 (200%拡大)

f:id:midori5:20180912231128p:plain

デフォルト

試して見た感想

普通にコンソールとして動作する印象です。

  • 起動は高速で待たされることなく画面がすぐ開く
  • 動作はけっこう重め
  • Ctrl + xxx のコマンドは利用可能
  • マウススクロールで画面スクロールができる
  • vi 開ける

所感

GCPのCloud Shellが便利で、AWSにもこういった機能が欲しかったので待望のリリースでした。
ただ立ち上げてみただけですが、sshポートを開けることなく接続できて、セキュアだと思うので、
動作が軽くなれば、こちらを積極利用していけると良さそうです。

参考

builderscon tokyo 2018に参加してきました

概要

ブログを書くまでが以下略 のため、ブログ書きます。

はじめに(スタッフの方々)

準備/当日運営含めて、大変ありがとうございました + お疲れ様でした。はじめての参加でしたが、大変楽しかったです。 テーマになっていた「知らなかったを聞く」に満たされた、2日間でした。
普段、ごった煮のイベントはあまり行かないので、他の分野についての知見が広まりました。
来年も是非参加させていただきたいです。

良かった点/気になった点だけ記載しておきます。(Feedbackの意味も込めて)

良かった点

  • 運営が基本的にとてもスムーズ
    • 変な引っかかり等なく、トークを聞くことに集中できました
  • 翻訳の用意がありがたかった
  • (注意説明が、ムービーになっており面白かった)

気になった点

  • 会場のキャパ問題
    • 今回は? 人が多かったからなのか満席/立ち見が多かった気がします
    • また、ランチセッションも早く並べた人限定という部分が少し気になりました。(ちなみに2日ともいただきました。)
  • Webページのタイムテーブルで会場名がヘッダー固定ではなかった点
    • 場所が何度かわからなくなりました..
  • スライドが見づらいことが多かった
    • 光の問題やスライド自体の問題?等で、文字が見えないことが何度かありました..
    • ただ、事前に共有いただけていたので手元で見ながら聞けました

トーク一覧

下記のGistに各トークのメモ書きを記載しております。
(諸事情により、しっかりメモを取っています。)
https://gist.github.com/midorigreen/7409399469c39eeb0f96214d33b76eae

TimeTable

1日目

Envoy internals deep dive

Matt Klein@mattklein123 さんが直々に、Envoyの設計についてお話されていました。 所々、わからなくなっていきましたが、「観測可能性」と「ロックを起こさない設計」の2点について印象に残っています。 プログラムの設計をする際に見返したいスライドだと思いました。

ランチセッション (サイボウズ株式会社): Kubernetes で実現するインフラ自動構築パイプライン

日毎に、VPCから作り直している話は、以前一度聞いたことがありましたが、驚きです。

Algorithms in React

Reactほとんど書いたことないのですが、内部実装が気になったので聞きました。 LinkedList + byteをうまく利用して、高速化を図っている部分が印象的です。

事前知識なしで理解する、静的検査のいろは

早足で進むのがもったいないくらい、まとまってて勉強になる内容でした。あと、スライドが好みで真似したくなりました。
静的検査の教科書的な網羅性で、やりたくなったら読み返すと思います。

機械学習を用いず数学でゲーム内の需要予測をする

数学ががっつり?というほどではないですが、出てきて講義感があって面白かったです。 分析に携わる仕事をしていることもあり、分析の原則について知れてよかったです。(知らなかったのがまずそうです..)

JavaCardの世界

2日間の中での個人的ベストトークでした。(※ 実際のベストトークもこちらでした!!)
Java Cardという全く知らない世界の、めったに外に出てこない知見が山盛りでした。
また、発表されていた方のプレゼンスキルが高く感じました。
特に、このあたりが気になりました。

  • 各仕様を正確に理解した上での、網羅的な説明
  • 質問への正確の返答のための聞き返し
  • (随所で笑いを生み出す力..)

あそこまで、ある技術に対して細かい点まで抑えられている事自体がすごいと感じましたし、詳しい分野を作りたいなと思いました。
(ここまでニッチではなくても良いですが...)

lld − 開発ツールの主要コンポーネントの1つをスクラッチから作成した話

tcfmの @rui314 さんの lld に関する発表でした。 実際に作られたlldに関しても面白い話でしたが、「勇気を持って自分が正しいと思う設計を突き通す」というお話がとても印象的です。 あと、速度を上げたければ データ構造 をうまく設計する必要があるとおっしゃっていた点も勉強になりました。

Conference Dinner

何人かの方々とお話できました!! (珍しい)

2日目

全てのエンジニアに知ってもらいたいOSの中身について

OSの中身? ≒ CPUのお話でした。CPUの歴史的背景がゴロゴロ出てきて、面白かったです。

高集積コンテナホスティングにおけるボトルネックとその解法

Linuxボトルネックをガツガツ潰していくお話でした。手になじませるツールをいくつか持っておくという部分が印象的です。 Linux系 + Go系でツールをゴリゴリ使えるようになりたいです。

ランチセッション (株式会社VOYAGE GROUP)

AjitoFmの公録が聴けて、純粋に楽しかったです。普段Podcastで聞いている声が目の前から聞こえてくる、不思議な感覚でした。

ブログサービスのHTTPS化を支えたAWSで作るピタゴラスイッチ

聞いてきた中で、最も仕事感のある内容で、ちょっと胃が痛くなりそうでした。高要求に対して、AWSのツールを活用して、 要件を満たしていました。その結果、ユーザに対して素晴らしい機能を提供できており、こういったことができる職業エンジニアになりたいと思いました。

業務時間で書いたパッチは誰のもの? OSS 活動にまつわる罠

とても珍しい、会社のOSS Policyに対するお話でした。基本的には、各社員が自由にやれるとい方向に倒れており、すごいなと思いました。

Building Self-Hosted Kubernetes

k8sをself hostするお話です。self host自体がよくわかっていなかったので、そこの知識を埋められてよかったです。 実際に、self hostを動かしてみてどうだったかとのお話がまた聞きたいです。

Extending Kubernetes with Custom Resources and Operator Frameworks

全セッションの中で最も難しかったです。(途中からさっぱりわからなくなっていきました..)
k8s周りについての理解が浅かった部分が大きかったです。ただ、k8sの設計思想(あのロゴの意味)等が知れました。

Lightning Talks

なんでもありのトークで、LTっぽくて楽しかったです。 下記が印象に残っています。

  • Macがいやだからブラウザで動くエディタを作成(https://nedi.app/)
    • めちゃめちゃ速くてびっくりしました
  • 画像加工 (kaoriyaさん)
    • 衝撃でした
  • カオスエンジニアリングの文脈でネットワークの検証
  • ファミコンエミュレータでBest Practiceを目指す
  • 登壇するための背中を押せていただける話

感想

とにかく、面白いイベントでした。もともと、他の分野の話を聞いて知見を広めたいとの目的で参加したので、完全に達成された気でいます。 あとは、聞くだけではなくて手を動かしていくことが大切だと思うので、ゴリゴリコード書いていきます。 (発表もできたらいいなぁともちょっと思っています。)

Go-Cloudで利用されているDIツールWireについて

概要

Go-Cloud Projectで利用されている、 Wire と呼ばれる
Dependency Injection Tool について触ってみました。
(README.md を試した形です)

Install

github.com

wire は go get でインストールします。

go get github.com/google/go-cloud/wire/cmd/wire

実装

github.com

基本的な使い方

ProviderとInjecterという2つの概念を持ちます

Provider

Providerは、依存させたいstructの実体を返却します。

例)
依存させたいstructの実態を定義します。

// foo.go
type Foo struct{
    Name string
}

Provider関数を定義します。

func ProviderFoo(name string) Foo {
    return Foo{
        Name: name,
    }
}

Providerの利用元も実装します。
(今回は1つですが) ProviderSetを定義することで、複数のProviderをまとめて処理します。
main.go

// ProviderSet
var SuperSet = wire.NewSet(ProviderFoo)

func main() {
    // flagで名前を切り替えられるようにしてみます
    n := flag.String("n", "foo", "foo name")
    flag.Parse()

    foo, err := setUp(context.Background(), *n)
    if err != nil {
        log.Fatalln(err)
    }

    // fooの名前を出力
    fmt.Println(foo.Name)
}

Injector

Injectorは、Provider関数を利用して実体を実装へinjectします。
injectorのコードは仮実装を元に、wire が生成してくれます。

例)
Injectorの仮実装を定義します。
コード生成の元コードのため、buildタグを付与して build時に利用されないようにします。 injector.go

// buildタグを付与
//+build wireinject

// 実装
func setUp(ctx context.Context, name string) (Foo, error) {
    // ProviderをBuildする
    wire.Build(SuperSet)
    // 特に意味はない
    return Foo{}, nil
}

ここで、injectorの実装を生成していきます。 下記コマンドを実行します。
(go getで入れているので、バイナリが$GOPATH/bin配下に配置されてます。)

% wire

wire コマンドを実行すると、下記のような wire_gen.go が生成されます。
参考) wire_gen.go

// Code generated by Wire. DO NOT EDIT.

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

package main

import (
        context "context"
)

// Injectors from inject.go:

func setUp(ctx context.Context, n string) (Foo, error) {
        foo := ProviderFoo(n)
        return foo, nil
}

実行

wire_gen.go が生成された段階で、buildして実行してみます。

% go build -o wire-sample

(補足)
buildタグで、!wireinject となっているため go build 時は、 injector.goの代わりにwire_gen.go が利用されます。

実行

% ./wire-sample
foo

# ちゃんとフラグで渡した値が利用されている
% ./wire-sample -n bar
bar

ちなみに、一度 wire_gen.go を生成したあとで、修正したい場合は、 go generate を利用します。

# 再ビルド
% go generate && go build -o wire-sample

Tips

① 独自型を定義してProviderへ渡す

Providerに、一般的な型(string, int, etc..)を利用すると、コードを生成する際に一意に定まらず、期待しない動作になる可能性があります。
そのため、独自型で定義してsetUpの引数に渡してあげることで、確実に狙ったProviderの引数に渡すことができます。

例)
injectしたいstruct

type Foo struct {
    Name: string
}

type Hoge struct {
    Hoge: string
}

type FooHoge struct {
    Foo Foo
    Hoge Hoge
}

providerの実装

// 型定義
type fooName string

func ProviderFoo(name fooName) Foo {
    return Foo{
        Name: string(name),
    }
}

func ProviderHoge(name string) Hoge {
    return Hoge{
        Name: name,
    }
}

func ProviderFooHoge(foo Foo, hoge Hoge) FooHoge {
    return FooHoge{
        Foo: foo,
        Hoge: hoge,
    }
}
var SuperSet = wire.NewSet(ProviderFoo, ProviderHoge, ProviderFooHoge)

func main() {
    fh, _ := setUp(ctx, "foo", "hoge")
    fmt.Println(fh.Foo.Name)
    fmt.Println(fh.Hoge.Name)
}

injector.go

func setUp(ctx context.Context, fn fooName , n string) (FooHoge, error) {
    wire.Build(SuperSet)
    return Foo{}, nil
}

↓ go generate

// generateした際に、stringのままだとFoo/Hogeの
// どちらの名前かわからなくなってしまう
func setUp(ctx context.Context, fn fooName , n string) (FooHoge, error) {
    foo := ProviderFoo(fn)
    hoge := ProviderHoge(fn)
    fooHoge := ProviderFooHoge(foo, hoge)
    reutnr fooHoge, nil
}

所感

GoでDIする際に、あまりツールを利用できていなかったので、 利用が増えてくれば、実コードに入れるのもありかと思いました。 DIに関しては、 Guice を一度触れていたので、比較的すんなり入ってきました。 テスタビリティがきちんとあがっているかは、テスト書いてみて確認したいです。

参考