emahiro/b.log

Drastically Repeat Yourself !!!!

goでprivateメソッドをテストする

サマリ

go でのprivate methodをテストでcallする方法

テスト手順

goでprivateメソッドをテストファイルからcallする場合には、同一package内においてはファイルが異なっても、同じpackage内に存在するprivate methodを呼ぶことができる というgoの仕様を利用します。
goではrailsrspecのようにテストをする場合に、専用のspecディレクトリを用意してもいいですが、private methodのテスト等を考慮した場合に、同一package内にテストファイルも配置することが多いです。

例えば以下のようなpackage構成になります

model
   user.go
   user_test.go

※ テストファイルへの物理的な移動距離が少なくて僕はこのテストの構成は非常に開発者に優しいと思っています。

実際にテストを書いてみます。

今回つかってリポジトリはこちら

github.com

ディレクトリ構成は以下のようにしました。

src
   main.go
   test
       sample.go
       sample_test.go

実際にテストファイルを書いてみます。

// sample.go
package test

import "fmt"

func PublicMethod(){
    fmt.Printf("public method: %s \n", "")
}

func privateMethod(){
    fmt.Printf("private method: %s \n", "")
}
// sample_test.go
package test

import "testing"

func TestPublicMethod(t *testing.T) {
    t.Logf("test public method","")
    PublicMethod()
}

func TestPrivateMethod(t *testing.T){
    t.Logf("test private method","")
    privateMethod()
}

実際にテストを動かしてみます。

$ go test -run ./test
public method:
private method:
PASS
ok      test    0.006s

package test内であればprivateメソッドでもcall出来ます。
こうすればprivateメソッドを簡単にテストすることが出来ます。

まとめ

goのprivateメソッドをcallするところで少しハマったので、備忘録に簡単にまとめました。

(続)goでのhttpの書き方あれこれ

以前書いたこれの続き

ema-hiro.hatenablog.com

以前書いたgoでのhttpの書き方の内容の続編です。
Postの書き方とAutorizationヘッダーを付けたリクエストを構築するときの方法を備忘録としてまとめました。

標準パッケージはこちら

https://golang.org/pkg/net/http/

Goのhttpを使う

  • http.Post(method, url, data)
  • http.PostForm(url, data)
  • Client{} 型を使う

の三通りの書き方があります。

http.Postを使う

jsonFile, err := os.Open("data.json")
if err != nil {
    // error
}

resp, err := http.Post("sample.com", "application/json", jsonFile)

http.PostFormを使う

val := url.Values{}
val.Add("key", "value")

resp, err := http.PostForm("sample.com", val)

Client型を使う

client := http.Clietn{}

val := url.Values{}
val.Add("key", "value")

req, err := http.NewRequest("POST", "sample.com", strings.NewReader(val.Encode()))
if err != nil{
    // request create error
}

resp, err := client.Do(req)

GAEを使う

GAE上でhttpリクエストを使うときは以前記載した内容と同じで urlfetch(appengine.context) を使います。
内容は標準のパッケージのClient型に近い。

Postを使う

// r は *http.Request
ctx := appengine.NewContext(r) // GAEのContextを作成する
client := urlfetch.Client(ctx)

jsonFile, err := os.Open("data.json")
if err != nil {
    // error
}


resp, err := client.Post("POST", "sample.com", jsonFile)

PostFormを使う

// r は *http.Request
ctx := appengine.NewContext(r) // GAEのContextを作成する
client := urlfetch.Client(ctx)


val := url.Values{}
val.Add("key", "value")

resp, err := client.PostForm("sample.com", val)

client.Do型を使う

// r は *http.Request
ctx := appengine.NewContext(r) // GAEのContextを作成する
client := urlfetch.Client(ctx)

val := url.Values{}
val.Add("key", "value")
req, err := http.NewRequest("POST", "sample.com", http.NewReader(val.Encode())
if err != nil {
    // request create error
}

resp, err := client.Do(req)

Header情報を追加する

Header情報を付加するには標準パッケージ、GAE、どちらにしてもClient型を使うことになります。

http.Packageのとき

req, err := http.NewRequest("Post", "sapmple.com", url.Values{})
if err != nil{
     // request create error
}
req.Header.Set("ContentType", "application/json")
req.Header.Set("Authorization", "XXXXXXXX")

// httpパッケージを使う場合
client := http.Client{}
resp, err := client.Do(req)

GAEのとき

req, err := http.NewRequest("Post", "sapmple.com", url.Values{})
if err != nil{
     // request create error
}
req.Header.Set("ContentType", "application/json")
req.Header.Set("Authorization", "XXXXXXXX")

// GAEを使う時
// r は *http.Request
ctx := appengine.NewContext(r)
client := urlfetch.Client(crx)
resp, err := client.Do(req)

Header情報を付与するときは、予め func NewRequest(method, url string, body io.Reader) (*Request, error)http.Request 型を生成しておかなければなりません。
その Request 型に対してHeader情報を付与していくことになります。

Refs

http - The Go Programming Language

qiita.com

cURL as DSL — cURL as DSL 1.0 documentation

AbemaTV Developer Conference に参加してきました。

ちょっと遅刻してしまったけど、21日にAbemaTV Developer Conferenceに参加してきました。 (ほとんど個人のメモです。)

感想

先に感想だけ書いておきます。
※ 僕向けのメモよりスライド探したほうがわかりやすいと思うので(笑)

感想としては、 Abemaのモニタリング、負荷対策の仕組みがマジですごかったこ
AbemaTVのような大規模サービスをどう監視しているのかに、興味があったので、モニタリング関連のセッションは非常に勉強になりました。
特に、セッション中にOSS化されたpromvizはデモ含めてすごくUIがめちゃめちゃイケてました。

github.com

いやほんと超かっこいい。
AbemaTVはPrometheusを使ってたんですね。

developers.cyberagent.co.jp

iOSアプリもデイリーで7~8個PRマージしてるとか、開発もすごい活発に行われている印象を受けました。
AbemaTVといえば何度かダウンしたこともあったけど、その時のことも語られててました。

来月も「72時間ホンネテレビ」というどでかいトラフィックが来るイベントがあるので是非、来年のConferenceでそのあたりについても話が聞きたいなーと思います。

以下自分向けのメモ

iOS開発

  • 1monthで162個のmerge。
  • 営業日換算で7~8個のPRがマージされている。

毎日大量のコードが変更されいる。

  • 開発フローはスクラム
  • 開発とQAの期間の重複があると厳しい
  • QA期間中にフライングする開発も可能
  • 長期間に渡る開発はスプリントをまたぐ

タスクにはストーリーポイントを付けている。 * 大雑把な付け方をしているけれども。

優先度判断については共通言語、共通指標を持っている * 優先度を決める時に、非エンジニアとのコミュニケーションも円滑になる。

レビューはしっかりルール化されている。 * レビューは活発な文化

デザインについて

iOSアプリをAndroidに寄せている * bottom sheetとか * スマホの大画面化に合わせて、全画面モーダルは避けれる傾向にある。 * マテリアルデザインのデザインシステムは非常に優れている。

Web * モバイルページでのアプリDL率向上について * twitterからの流入率は高い。しかし、twitterから来たユーザーのDL率が低い * twitterから流入したユーザーのDLを上げた * ちょっとtwitterからのLPを変更したくらい * エンジニアの工数をほとんど使わずに大きな効果を出せた事例

  • UI変更に抵抗はない
  • UIが急に変わっても、時間が経って文句を言い続けている人はいない
  • ユーザーは慣れる、変更による使いやすさの恩恵も受ける
  • 変化を恐れることは悪

abemaTVにおけるモニタリング

モニタリングシステムの進化 * 最初半年くらいはstackdriver * abemaはほぼすべてのレイヤーでモニタリングしたい * Prometheus + grafana * kubernetsと親和性が高い * PromQLが使いやすい * sqlライク * Exporterを作りやすい * AlertManagerがある

  • stackdriverのモニタリングもPrometheusで監視している。

クラスタ全体のトラフィックの状態がわかりにくい * Prometheusのデータからデータを可視化したい。

speakerdeck.com

Microservices下におけるWebの負荷対策

  • 負荷対策
    • 亀田興毅でwebが死んだ
    • 藤井四段でwebが死んだ

webのシステム構成

  • nginx + node + grpcで構成

webのサーバーリクエストをどうスケーリングさせるのか?

  • LBレベルも通らないキャッシュが必要なのでは?
  • CDNが必要になってくるのではないか?
    • GCPは東京リージョンがあるよ
  • キャッシュミス
    • レスポンシブが合わない
    • PCとスマホで出し分けが存在する
  • フロントエンドエンジニアができることは限りがある。
    • CDNからキャッシュが返ってくればいい。
    • GoogleCloudCDNを活用
    • CDNがキャッシュしたら、nginxのassets配信は必要なくなるのでは?
      • しかし、将来的BFF化を考える、サーバーサイドレンダリングが必要になったタイミングでnginxは必要なのでは?
  • 負荷対策はパフォーマンス改善につながる
    • 負荷対策は運用時の登竜門

MPEG-DASHで行うリニア配信

  • MPEG-DASHとは
    • 会場の方で動画関連の方が使っているのは大体HLS

MPD

  • media presentation description
    • mediaで定義されているメディアに関する情報を記述するxml
    • type
      • dynamic -> 定期的に取りにいく。生放送に使われる
      • static -> 取りに行かない。

abemaTVにおけるリニア配信技術とは?

  • リニア配信とは
    • 予め決められた番組表に則って配信を続ける方法
  • mpeg-dashをどうしてabemaで対応したか?
    • DRM技術に対応したコンテンツ配信を行う必要性
    • DRMはデバイス毎に異なる
  • abemaTVではマニフェストファイルを逐一作る必要がある。
    • ts -> mp4 -> init.m4s/ medeia.m4s の2つを作る。
    • 生放送のCMがどこに入るのかわからない
      • tsが知っている。

配信品質管理

  • トラブルが起きる箇所
    • 配信トラブルをいち早く検知するのは?

AbemaTVの裏側

  • 負荷試験について
    • ヒット企画が多いが、
      • 負荷対策の試練
  • 負荷試験
    • Locus
      • python
      • 分散型ユーザー負荷テストツール
      • GKEと相性がいい
      • ユーザの綿密なシナリオはLocusで記述可能
      • python -> golangに変更したい
    • wrk
      • Lua
      • 大規模な負荷を発生させることが可能
      • abコマンドとかに触ったことがある人には手に馴染みやすい

Referense

developers.cyberagent.co.jp

webpack + typescript

なぜtypescript?

MSが作ってGoogleが採用したので、フロントエンド界隈でtypescriptがしばらく積極的に使われて来そうな気が予感がしたのと、そろそろフロントエンドのキャッチアップしとかないとなーと感じ始めていたので...

typescriptの導入

typescriptをwebpackでコンパイルできるようにします。 https://webpack.js.org/guides/typescript/ を参考にして導入しました。

yarnでinstall

$ yarn add -D typescript ts-loader inline-source-map

ts-loader
refs: https://github.com/TypeStrong/ts-loader

tsの設定

tsconfig.jsonを作成する

$ touch tsconfig.json
$ ls -la
drwxr-xr-x    - user 14 10  0:56 .idea
drwxr-xr-x    - user 14 10  0:56 dist
drwxr-xr-x    - user 14 10  1:28 node_modules
.rw-r--r--  640 user 14 10  1:28 package.json
drwxr-xr-x    - user 14 10  0:54 src
.rw-r--r--    0 user 14 10  1:29 tsconfig.json
.rw-r--r--  857 user 14 10  0:56 webpack.config.js
.rw-r--r-- 142k user 14 10  1:28 yarn.lock

tsconfig.jsonにtypescriptをes5にコンパイルする設定をカリカリ書いていく。
今回は最初というのもあり、webpackのtypescript導入に記載してあるjsonファイルをそのまま使う。

json内でjsxを使っているが今回は使っていない

{
    "compilerOptions": {
        "outDir": "./dist/",
        "noImplicitAny": true,
        "module": "commonjs",
        "sourceMap": true,
        "target": "es5",
        "jsx": "react",
        "allowJs": true
    }
}

webpack.config.js

以下の用にts-loaderを入れてtypescriptをコンパイルできるようにする

const src = __dirname + "/src";
const dist = __dirname + "/dist"

var webpack = require('webpack');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
    context: src,
    entry: "./ts/index.ts",
    devtool:"inline-source-map",
    module: {
        rules:[
            {
                test: /\.html$/,
                loader: "html-loader"
            },
            {
                test:/\.tsx?$/,
                loader: "ts-loader",
                exclude: /node_modules/
            },
        ]
    },
    resolve: {
        extensions:[".js", ".ts", ".tsx"]
    },
    output: {
        path: dist,
        filename: "bundle.js"
    },
    devServer: {
        contentBase: dist,
        port: 3000
    },
    plugins: [
        new UglifyJSPlugin(),
    ]
};

typescriptファイルの作成

$ touch src/ts/index.ts
console.log("test") //のみを記載

コンパイル

ema-hiro.hatenablog.com

で作ったyarnのscriptの再利用。

$ yarn run build
yarn run v1.1.0
$ yarn run --config webpack
ts-loader: Using typescript@2.5.3 and ~/emahiro/ts_sample/tsconfig.json
Hash: bcac9e140803472acb6d
Version: webpack 3.7.1
Time: 1114ms
     Asset       Size  Chunks             Chunk Names
 bundle.js  498 bytes       0  [emitted]  main
index.html  182 bytes          [emitted]
   [0] ./ts/index.ts 21 bytes {0} [built]
Child html-webpack-plugin for "index.html":
     1 asset
       4 modules
✨  Done in 2.27s.

動作確認する

以前作成したものと同様の設定で webpack-dev-server を起動し localhost:3000 にアクセスする

$ yarn run start // ※ リンクにあるブログで作成済みのコマンド
Project is running at http://localhost:3000/
webpack output is served from /
Content not from webpack is served from ~/emahiro/ts_sample/dist

開発者画面 -> consoleを見て test とlogが記載されていればtypescriptはes5にコンパイルされて正常にブラウザで読み込まれている。

所感

webpack + typescriptは想像以上に導入が簡単。
次は簡単なアプリを作ってみたい。

webpackでhtmlファイルも出力する

ema-hiro.hatenablog.com

昨日の上記エントリへの追記

TODO

  • htmlファイルをdist以下に作成していましたが、frontendのファイルはsrc以下に管理します。
  • webpackでコンパイルしたらdist以下にhtmlファイルも出力させます
  • html-webpack-pluginを使います

install

$ yarn add -D html-webpack-plugin
$ yarn add -D html-loader

webpack.config.js

html-webpack-pluginhtml-loader を加えてコンパイルの設定を更新する

const src = __dirname + "/src";
const dist = __dirname + "/dist"

var webpack = require('webpack');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  context: src,
  entry: "./js/index.js",
  output: {
    path: dist,
    filename: "index.min.js"
  },
  devServer: {
    contentBase: dist,
    port: 3000
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude:/node_modules/,
        loader: "babel-loader",
        query: {
          presets:[
            ["env", {
              "targets": {
                "node": "current"
              }
            }]
          ]
        }
      },
      {
        test: /\.html$/,
        loader: "html-loader"
      }
    ]
  },
  plugins: [
    new UglifyJSPlugin(),
    new HtmlWebpackPlugin({
      template: "./html/index.html"
    })
  ]
};

ディレクトリの変更

- root
  - src
    - js
      - index.js
    - html
      - index.html
  - dist
    - 出力先

index.htmlを作成する

src/html/index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>test</title>
  </head>
  <body>
    hello world
  </body>
</html>

コンパイルする

$ yarn run build
yarn run v1.1.0
$ yarn run --config webpack
Hash: 1c4cb048a83c8c452b7b
Version: webpack 3.6.0
Time: 577ms
       Asset       Size  Chunks             Chunk Names
index.min.js  814 bytes       0  [emitted]  main
  index.html  195 bytes          [emitted]
   [0] ./js/index.js 400 bytes {0} [built]
   [1] ./js/person.js 171 bytes {0} [built]
Child html-webpack-plugin for "index.html":
     1 asset
       [0] ../node_modules/html-webpack-plugin/lib/loader.js!./html/index.html 168 bytes {0} [built]
✨  Done in 1.71s.

buildpackage.json で新しくscriptsに追加したカスタムビルドコマンド

生成された index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>test</title>
  </head>
  <body>
    hello world
  <script type="text/javascript" src="index.min.js"></script></body>
</html>

コンパイルした index.min.jsscript タグに入ってちゃんと生成されている。

この状態で webpack-dev-server を動かしてちゃんと動作するかを確認する

おまけ

複数のhtmlファイルをコンパイルしたい

refs: https://github.com/jantimon/html-webpack-plugin#generating-multiple-html-files

SPAならindex.htmlだけ作成してそこにSPAの要素を追加していくのもありであるが、複数のhtmlファイルを出力したい場合がある。 そういう場合は html-webplack-plugin はデフォでは index.html しか出力しないが、別のファイルを新しくpluginで指定してfile名とコンパイル対象のtemplateを指定すれば良い。

// 略
plugins: [
  // index
  new HtmlWebpackPlugin({
    template: "./html/index.html"
  }),
  // その他
  new HtmlWebpackPlugin({
    filename: "admin.html",
    template: "./html/admin.html"
  })
]
$ ls dist/
admin.html  index.html

htmlファイルが複数出力されている

所感

webpack一発でフロントエンドの環境が構築されるっていうのはこういうことかってわかってきた。
今回はベタなhtmlをそのままコンパイルしているがテンプレ(ejsファイルとか)をコンパイルすることも可能

いまさらだけどyarn + webpackを試す

仕事の中でjsの開発環境を構築する機会があったので、自前でes6をコンパイルする環境をととえてみた備忘録。
jsは動きが早くて色んなツールが出て来るなか、一定出尽くして落ち着いてきそうな感じをここ最近感じていたので、ようやく少しずつキャッチアップしていこうと思いました。

サマリ

  • yarn + webpack導入
  • es6を動かす
  • es6で書いたコードを動かす

どうしてyarnにしてのか

yarnとは?
facebookが開発したnext npm的なポジションのpackageマネージャー

去年10月にローンチして、徐々に使用事例が多くなってきたので自前でjsのモダンな開発環境を作るにあたって採用してみようと思ったから。

yarnのいいところ

https://qiita.com/0829/items/ec5271c06f8ff0633dd3 あたりを参考にした。

npmが生成した package.json を読み込める用に作られているので、npmとの互換性が保たれているので、正直どちらを使ってもいいと思われるが、npmより高速に動作し、依存関係やバージョン等も厳格に管理することができるため、npmでもできるが、yarnを積極的に使わない理由も弱い。

といった感想を持っている。

導入手順

yarn init

$ mkdir MyProject
$ cd MyProject
$ yarn init
yarn init
yarn init v1.1.0
...
success Saved package.json

対話式でプロジェクトの概要をセットアップしていく。
完了したらpackage.jsonができる

babelの設定

babelを使う場合は、 .babelrc が必要なものかと思ってましたが、webpackの場合は webpack.config.jsコンパイルの定義を書けます。

babelと一緒にes2015(es6)の構文対応できるように、 babel-preset-env をインストールします。

$ yarn add -D babel-loader babel-core babel-preset-env

当初 babel-preset-es2015 を入れてましたが、yarn add している最中に warning babel-preset-es2015@6.24.1: 🙌 Thanks for using Babel: we recommend using babel-preset-env now: please read babeljs.io/env to update! というwarnを受けたので、 babel-preset-env を入れました。

refs: https://babeljs.io/env/
github: https://github.com/babel/babel-preset-env

webpack.config.jsの設定

コンパイル元のファイルはentryに書いて、1つに固めたファイルの出力先がoutput。

というわけで出来上がった webpack.config.js はこちら

const src = __dirname + "/src";
const dist = __dirname + "/dist"

var webpack = require('webpack');

module.exports = {
  context: src,
  entry: "./index.js",
  output: {
    path: dist,
    filename: "index.min.js"
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude:/node_modules/,
        loader: "babel-loader",
        query: {
          presets:[
            ["env"]
          ]
        }
      }
    ]
  }
};

es6の導入

ディレクトリ構成は以下

MyProject
  src
    index.js
  dist
    index.min.js

このindex.jsにes6の構文で書いて、コンパイル後index.min.jsがちゃんと読み込まれるかを確認する

おまけ

難読化(UglifyJS)

コンパイルしたファイルを難読化する。
webpackには標準でUglifyJSがあるので、これを plugins に追加したが、defaultの難読化プラグインコンパイル時にエラーが発生した

$ yarn run build
yarn run v1.1.0
$ yarn run --config webpack
Hash: 2689b764da6353bc4101
Version: webpack 3.6.0
Time: 341ms
       Asset     Size  Chunks             Chunk Names
index.min.js  3.11 kB       0  [emitted]  main
   [0] ./index.js 371 bytes {0} [built]
   [1] ./person.js 162 bytes {0} [built]

ERROR in index.min.js from UglifyJs
Unexpected token: name (Friend) [index.min.js:79,6]
error Command failed with exit code 2.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

標準のuglify-jsが動かなかったので、uglifyjs-webpack-pluginを使う
refs: https://www.npmjs.com/package/uglifyjs-webpack-plugin

$ yarn add -D uglifyjs-webpack-plugin
const src = __dirname + "/src";
const dist = __dirname + "/dist"

var webpack = require('webpack');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin') //追加

module.exports = {
  context: src,
  entry: "./index.js",
  output: {
    path: dist,
    filename: "index.min.js"
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude:/node_modules/,
        loader: "babel-loader",
        query: {
          presets:[
            ["env", {
              "targets": {
                "node": "current"
              }
            }]
          ]
        }
      }
    ]
  },
  plugins: [
    new UglifyJSPlugin() //追加
  ]
};

webpackを動かす

yarn run build
yarn run v1.1.0
$ yarn run --config webpack
Hash: 2689b764da6353bc4101
Version: webpack 3.6.0
Time: 381ms
       Asset       Size  Chunks             Chunk Names
index.min.js  786 bytes       0  [emitted]  main
   [0] ./index.js 371 bytes {0} [built]
   [1] ./person.js 162 bytes {0} [built]
✨  Done in 1.37s.

$ cat dist/index.min.js
!function(e){function t(n){if(r[n])return r[n].exports;var u=r[n]={i:n,l:!1,exports:{}};return e[n].call(u.exports,u,u.exports,t),u.l=!0,u.exports}var r={};t.m=e,t.c=r,t.d=function(e,r,n){t.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:n})},t.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(r,"a",r),r},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=0)}([function(e,t,r){"use strict";var n=function(e){return e&&e.__esModule?e:{default:e}}(r(1));class u extends n.default{constructor(e){super(name)}callName(){alert(this.name)}}new u("Taro").callName()},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});class n{constructor(e){this.n=e}}t.default=n}]);⏎

ちゃんと難読化されている。

動かしてみる

webpackにはlocalにサーバーを立てるライブラリ webpack-dev-server があるのでこちらを使う

$ yarn add -D webpack-dev-server
$ yarn run webpack-dev-server //これで動作させることができる。

上記でも問題ないがscriptを作成した

"scritps": {
  "start": "yarn run webpack-dev-server"
  "build":...
}

devserverはデフォルトは8080ポートで動作するが、手元のgoとパッティングするので3000番portに変更。
以下を webpack.config.json に追加する

const src = __dirname + "/src";
const dist = __dirname + "/dist"

var webpack = require('webpack');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
  context: src,
  entry: "./index.js",
  output: {
    path: dist,
    filename: "index.min.js"
  },
  devServer: {
    contentBase: dist,
    port: 3000
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude:/node_modules/,
        loader: "babel-loader",
        query: {
          presets:[
            ["env", {
              "targets": {
                "node": "current"
              }
            }]
          ]
        }
      },
      // 以下でhtmlの読み込みも追加する
      {
        test: /\.html$/, 
        loader: "file?name=[path][name].[ext]"
      }
    ]
  },
  plugins: [
    new UglifyJSPlugin()
  ]
};

サーバーを起動させる

yarn run start
yarn run v1.1.0
$ yarn run webpack-dev-server
Project is running at http://localhost:3000/
webpack output is served from /
Content not from webpack is served from ~/emahiro/es6_sample/dist
Hash: 093cadee3f8742a9e298
Version: webpack 3.6.0
Time: 2247ms
       Asset    Size  Chunks             Chunk Names
...
webpack: Compiled successfully.

注目すべきは以下

webpack output is served from /
Content not from webpack is served from ~/emahiro/es6_sample/dist

サーブされているのは / だけど、実際にはdistがwebから見たときのcontentのrootになる

dist
  index.min.js

というディレクトリ構造をしている場合、htmlからjsを呼び出すときは ./index.min.js と直書きする。

サーバーが起動した状態で localhost:3000 にアクセスしたら alert が発火するはず。
発火したら正常にes6がコンパイルされて動作していることになる。

所感

今までjsの潮流が早すぎて、正直足突っ込んだら抜けられなくなりそうだったので、避けてきた分野でしたが、webpackを使うことでjsド素人でも簡単にes6をプロジェクトに取り入れることが出来ました。

色々試せそうですが、次はtypescript入れてコンパイルしてみようと思います。

goでのhttpの書き方あれこれ

goの標準

goでhttpの処理を書きたいときはnet/httpパッケージを使う。
強力なパッケージなので、基本これを使うで、やりたいことの殆どはまかなえてしまうと思う

http.Get(url)

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    resp, err := http.Get("http://www.google.co.jp")
    if err != nil {
        fmt.Printf("err: %v", err)
    }
    defer resp.Body.Close()

    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf("ioutil err: %v", err)
    }
    fmt.Printf("html: %v", string(b))
}

http.NewRequest(url)

単純にGetをするだけであれば http.Get(url) で事足りると思われるが、その他のMethodにも対応させるために、httpクライアントを明示的に作ることも可能

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    req, err := http.NewRequest("GET", "http://www.google.co.jp", nil)
    if err != nil {
        fmt.Printf("new request err: %v", err)
    }
    client := http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Printf("request response err: %v", err)
    }

    defer resp.Body.Close()

    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf("ioutil err: %v", err)
    }
    fmt.Printf("html: %v", string(b))
}

GAEの標準

GAE/Goでhttpを書きたいときにはちょっと1つ考慮しておかなければいけないことがある。

  • GAE/Goでは、GAEのContextを使わないといけない
  • 通常のリクエストからGAEのContextを作成し、それを使ってhttpのrequest/responseを実装する
package main

import (
    "net/http"

    "io/ioutil"

    "google.golang.org/appengine"
    "google.golang.org/appengine/log"
    "google.golang.org/appengine/urlfetch"
)

func init() {
    http.HandleFunc("/", SampleHandler)
}

func SampleHandler(w http.ResponseWriter, r *http.Request) {
    ctx := appengine.NewContext(r) // GAEのContextを作成する
    httpClient := urlfetch.Client(ctx)
    resp, err := httpClient.Get("http://www.google.co.jp")
    if err != nil {
        log.Debugf(ctx, "html: %v", err)
    }
    defer resp.Body.Close()
    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Debugf(ctx, "html: %v", err)
    }
    log.Debugf(ctx, "html: %v", string(b))
}

ハマったところ

  • gaeのプロジェクトの最小構成単位はhandlerを受け付けるgoのファイルとapp.yamlがあること。
  • しかし、この2つのファイルは同じ階層においては行けない
- my-project
    - app.yaml
    - src
        - main.go

というディレクトリ構成にする必要がある。

  • gaeではmain関数でなく init() を使う。
  • main() を使っていた最中にずっと起動後 localhost:8080 に繋いでも 404 page not found が返ってきてしまっていた。
  • gaeのhttpのHandlerには http.ListenAndServe() メソッドは使わない。

The Twelve-Factor App を読む

ちょっと前にはてブで上位に上がっていた以下のスライド

speakerdeck.com

の中に出てきていた「The Twelve-Factor App」を読んだ感想を書く。

原典の邦訳はこちら ▶ https://12factor.net/ja/

今更感があるがちょっと最近基礎が揺らいでいるなという感覚もあったのでざっと通読してみた。

  • 12個の原則はどれも昨今のウェブサービスを作るのであれば当たり前とも言える内容だった。
  • 自分がそういう環境でしか仕事をしたことがないからかもしれないが、既存のチームにJOINするにしても、新しく1からサービスを作るにしても意識、というか当たり前にできなければいけないことだと思う。
  • 内容としては昨今のフルマネージドが流行りつつある状況ではもしかしたらvagrantやポートバインディングなどは自分で管理するものではないかもしれない。
  • しかし、背景にこういう法則があって、というContextを理解しておくことは大事だと思う。

結構忘れていることも多く、API開発の名著も読み直そうと思う。

goで一時的なスクリプトを書く方法

Goで開発しているときに

  • 標準パッケージの動作確認したい
  • ライブラリの動作確認したい
  • 簡単なスクリプト(HTTPやgoroutineなど)を書きたい

と思って調べてみました。

Rubyならirbやpryを使ってコンソールで簡単な動作検証を行えたり、変数の内部見ることができますが、Goは静的な言語でコンパイルすることが必要になるので、デフォルトではそういうちょっとした確認のためのスクリプト書いたりみたいなことができません。

Goで一時的なスクリプト確認したい場合はいつも The Go Playgroundを使っていましたが、

  • 補完が効かない
  • 標準パッケージしか使えない

という2点があって、例えばgithub上のライブラリの検証は実際に手元に落としてきて、Goでsampleファイル書くっていう手順をいつも行っててめんどくさいなーって思ってました。

そこでGoでもREPL的なものが無いかを調べてみたところ

ありました

github.com

作者さんが書いた使い方のエントリー motemen.hatenablog.com

実際に入れてみたところ

$ gore
gore version 0.2.6  :help for help
gore> :import encoding/json
gore> b, err := json.Marshal(nil)
[]uint8{
  0x6e, 0x75, 0x6c, 0x6c,
}
nil

おおおお!これはよい。簡単なスクリプトを試すにはすごく便利です。
:import すると手元に落としてきたライブラリもimportできるので、標準以外のメジャーなライブラリのサンプル書くのもいちいちGoのファイル作らなくていいので楽になります。

ppgocode をあわせて入れとくと出力をきれいに整形してくれたり、補完が効くようになります。
可読性も効率もアップできるのでとても便利なデバック用のライブラリだと思います。

GoonのGet()とGetAll()の違いでハマる

Go+GAEの環境でCloudDataStoreからデータを取得するときに

  • Get()
  • GetMulti()
  • GetAll()

の挙動の違いでハマったので備忘録として挙動をまとめておきます。

Datasotreのライブラリ

datastore

https://godoc.org/google.golang.org/appengine/datastore

googleappengineには標準でdatastoreのクライアントとして datastore が用意されています。 僕も使うようになってこの便利さに気づいたのですが、structでRDBでいうところのレコード、datasotreではentityの構造を定義してしまえば、かなり簡単にCRUDの操作ができます。
※ ここでいうentityの構造とはモデルの構造のことです。

goon

https://godoc.org/github.com/mjibson/goon

goonはdatastoreとほぼ同じようなインターフェースを持っていて、自動キャッシュがついているので、キャッシュのことまで考えた場合に使いやすいライブラリです。

データを取得する

goonを使ってDatastoreからentityを取得する場合以下のようにします。

type entity struct {
    Id      int64 `datastore:"-" goon:"id"`     
    Field_1 string
    Field_2 string
}

func GetInstances(r *http.Request){
    g := goon.NewGoon(r)
    entity := &entity{ID:1}
    g.Get(entity) // ID(key) = 1のentityを取得する
}

goon.NewGoon(r) としている箇所はhttp.Requestから新規でappengineのContextを作成しています。NewGoonメソッドの内部はgoonのソースコード読むと

// NewGoon creates a new Goon object from the given request.
func NewGoon(r *http.Request) *Goon {
    return FromContext(appengine.NewContext(r))
}

ちなみに以下のような書き方も可能

g := goon.NewGoon(r)
entity := &entity{ID:1}
g.Goon().Get(entity) // ID(key) = 1のentityを取得する

とあるので、ここでappengineから新しくContext作っているのが一目瞭然ですね。

遭遇したエラー

Datastoreからデータを取得するときに以下のエラーに遭遇しました。
順を追ってどうしてエラーが発生したのか見ていきます。

  1. goon: cannot find a key for struct
  2. goon: Expected dst to be a pointer to a slice or nil, got instead
  3. invalid entity type

1. goon: cannot find a key for struct

こちらはGet()とGetMultiが ID(key) をKeyにしないとDatastoreからデータを取得できない性質を持っているということを知らなかったために発生しました。

RDBのようにprimaryキーだけでなく別のfieldをフックにしてデータを取得したい場合があると思います。
しかし、DataStoreのGet()とGetMulti()はID(DatastoreでいうところのprimaryKey)をKeyに取ることしかできません。

そのため、ID以外のfieldをKeyにしたい場合には上記2つのメソッドではなく、GetAll()メソッドを使ってクエリを直に指定する必要があります。

query.Filter("someFiled =", hogehoge) とqueryでFilterを指定すれば可能。

2. goon: Expected dst to be a pointer to a slice or nil, got instead

これはGetAll()を使うときに発生しました。 dstは GetAll(query *datastore.Query, dst interface{}) とあるようにGetAll()メソッドの第二引数です。

DataStoreからデータを取得する場合には dst に対して指定した参照型の変数にデータが格納されて入ってきます。
これがそのまま、DataStoreから取得したデータの結果になります。
そのため、dstには slice化されたpointer型の変数 もしくは何も指定指定しない nil しかいれることができません。
上記のサンプルでも entity が参照型になっています。

3. invalid entity type

これも2と要点は同じなんですが、Datastoreから取得したEntityの入れ物となる参照型の変数の構造は取得したいDatastoreのkindのfield構成と一致している必要があるそうです。

言われてみれば当たり前のような気もしますが、参照型、かつ構造が同じでないといけません。

Datastoreのルールで詰まったり、構造で詰まったり、そもそもなぜdstは参照型で渡すのか、色々知るいい機会になりました。

appengineのmemcacheを使う

GAEではmemcache一択

appengineのmemcacheパッケージを使ってみました。

リファレンスはこちら

https://cloud.google.com/appengine/docs/standard/go/memcache/reference

goでキャッシュを扱うときに最初 go-cache を使おうと思っていたのだけれど、GAEでキャッシュを使うときはmemcacheが標準のパッケージでついているのでこれを使うと簡単に実装できることを知る。

go-cache https://github.com/patrickmn/go-cache

実装方法

キャッシュなので、

  • キャッシュを取得する。
  • キャッシュがexpiredを過ぎていれば新しくキャッシュをセットする

の2通り

sampleコード

import (
  "encoding/json"
  "google.golang.org/appengine/memcache"
  "google.golang.org/appengine"
)

var (
  cacheKey := "KEY"
  casheExpTime := 60 // seconds
)

type Data struct{
  ID   int
  Name string
}

func CacheProcess(r *http.Request){
  ctx := appengie.NewContext(r)
  item, err := memcache.Get(ctx, cacheKey)
  if err != nil {
    // cacheを取得するときのエラー
  }
  
  if item != nil {
    // cacheが存在する時
    fmt.Fprintf("memcache key: %v", item.Key)
    fmt.Fprintf("memcache value: %v", item.Value)
    
    // cacheのValueの中身を取り出す。Valueは[]byte型
    data := &Data{}
    err := json.Unmarshal(item.Value, data)
    if err != nil {
      // jsonをunmarschalした時のエラー
    }
    
  } else {
      // cacheが存在しない時
      data := &Data{
        ID:   1,
        Name: "emahiro"
      }
     
      // dataを[]byte型に変換
      byte, err := json.Marshal(data)
      if err != nil{
        // dataをbyteにMarshalするときのエラー
      }
     
      // cacheをセットする
      i := &memcache.Item{
        Key:        cacheKey,
        Value:      byte
        Expiration: time.Duration(cacheExpTime) * time.Seconds // time.Duration(ns) 型
      }
     
      err := memcache.Set(ctx, i)
      if err != nil {
        // cacheをSetする時のエラー
      }
  }
}

Cacheを登録するときにその値を memcache.Item 型に変換することと、itemの Valueプロパティは[]byte型であるというところに注意すればかなり簡単にキャッシュを実装できます。

Goglandでソフトラップを消す

Preference ▶ Editor ▶ Appearance で show right margin をoffにする これでソフトラップがoffになる。

f:id:ema_hiro:20170817011538p:plain

goは変なところでソフトラップがかかって改行されるとコンパイルエラーになるのが嫌なので、ソフトラップをオフにしてます。

参考 http://samuraism.com/products/jetbrains/intellij-idea/quickstart/editor-basics

JetBrains系のソフトウェアなので同じような設定の仕方でいけますね。

[20171108追記]
GogLandがアップデートしたので show hard wrap guide(configured in Code Style options) にUIが変わっていました。

IDEを使うという選択肢

IDE統合開発環境

エディタをカスタマイズすること

エディタをバリバリカスタマイズしてこそエンジニアだ(# ゚Д゚)

なんてことを思っていた時期もありました。 (今でも少し思ってます)

バリバリエディタをカスタマイズして、キーバインドバチバチで、開発効率上がってドヤ!ってしたいときもありました。 (今でも少し思ってます)

と数々のeditorを使っては、カスタマイズし、使ってはカスタマイズし、そのうち何が目的かわからなくなってしまいました。 (本末転倒)

editorをカスタマイズするのは開発効率を上げるため。
しかし、カスタマイズしていくうちに設定に凝ったり、新しいPCに同じ環境を作るのに時間を食ったりするようになりました。

もちろん、設定ファイルをgithubにあげてたり、極力設定に時間を使わないようにしているのですが、そもそも設定を管理することそれ自体がめんどくさくなってしまいました。

IDEを使うということ

そんなおり、IDEを使うということを始めてみました。

僕が使ったことがあるIDEは以下です。

スマホアプリ開発環境についてはSDKがそれじゃないと動かなかったりするので、数えるべきかは微妙なところではありますが、特にrubyとgoのIDEを使い始めてからは Editorに戻れなくなりました

理由は大体以下です。

  • 設定が大体同じ(jetBrains系なら)
  • コードジャンプが標準装備されているので、コードリーディングが捗る
  • プラグインである程度はカスタマイズ効く(xcodeはほぼ無理)

特に二つ目のコードジャンプの恩恵が大きいです。
この機能のお陰で、ライブラリのソースコードを読むようになりました。

開発していると、

  • メソッドやクラスの定義元に素早く移動したい
  • テストファイルに素早く移動したい
  • ライブラリのコード読みたい

となることが多々あります。
特にプロジェクトに後からjoinする場合なのは既存のコードをどれだけ早く解析できるかというのは一つのポイントだと思います。

もちろん各種エディタもカスタマイズすればコードジャンプできますが、ひょんなことから動かなくなったりするなど、結構その機能を維持するのがストレスだったので、最初からある程度動いてくれて、特に開発に支障を来たすことなく、ストレスレスに使えるIDEはコードを理解する上でもすごく便利です。

また、これはあとから気づいたんですが、少しずつ勉強のログにブログをつけ始めていく中で、ここでもライブラリのコードを読む週間が増えてきて、学習効率も上がってきました。
よくライブラリなどの良いコードを読めと言われることが多いですが、とは言え定義元にすぐに移動できなかったら読むのもめんどくさいです。めんどくさいからなんとなく読まなくなります。

プライベートでも大きなメリットがあるので、例え課金したとしても離れることのできないツールになりつつあります。

IDEを使うということは僕みたいなエンジニア歴が浅い人にとっては、いい選択肢であり、武器を増やすことにつながると感じてるので、エディターのカスタマイズに疲れたらIDE使ってみてはどうでしょうか?

lsに変わってexaを使う

少し前に以下の記事をはてブで見つけたので、いい機会だったので使ってみました。 http://wonderwall.hatenablog.com/entry/2017/08/07/222350

普段はfishを使っているので、fish上では ls コマンドを拡張して exa コマンドを叩けるようにしました。

インストール

$brew install exa

lsコマンドを拡張

# 略
function ls
  command exa -la
end

[追記] exa の optionにも対応するために以下のようにして使ってます。

function ls
  command exa $argv
end

結果

f:id:ema_hiro:20170815223355p:plain

こんな感じでカラフルになりました。

便利。目に優しい!

GoglandでFWのsrc配下のvendorを参照しなくなった時

前置き

ginやechoと言ったgoのwebフレームワーク(FW)を使って開発するときに、glideみたいなパッケージ管理ツールを使って依存パッケージを管理しているケースはよくあると思います。

その依存パッケージの管理ディレクトリはプロジェクトルートではなく、用途によっては src/ProjectName/vendor みたいにFWのsrcディレクトリの配下に置くことも往々にしてあると思います。

起きた問題

僕はgoで開発するときにatomを使ってましたが、イマイチ使い勝手悪くなってしまったので、intellijさんのgogland(まだEAP)を使っています。
しかし、このgogland(EAP)をバージョンアップしたらそれまで開けていたプロジェクトでフレームワーク(FW)のsrc配下のvendorを参照せずにGlobalなGOPATHのsrc配下を参照するようになってしまってプロジェクトに入れていたパッケージを参照できず、プロジェクトはビルドできるがgogland上ではエラーが表示されまくったり、メソッドジャンプできなくなるっていう無能ツールになってしまったのでどう治したのかっていう備忘録です。

試したこと

.ideaファイルを消す

プロジェクト毎に設定等を管理している .idea ファイルを消しました。
効果はありませんでした。

プロジェクトを入れ直す。

プロジェクトディレクトリをcloneし直しました。
効果はありませんでした。

ここまで試して結構困りました。

原因がプロジェクトごとに direnv で設定しているGOPATHではなく、なぜか GLOBALなGOPATHを参照してしまっていたからです。

最終的にこのGOPATHをプロジェクトごとに変更してやれば良さそうなのですが、この方法がわからず。。。

手動で追加

最終的に行き着いた方法が Project Structure でプロジェクトのROOTを個別に指定するっていう方法でした。

f:id:ema_hiro:20170811022330p:plain

この画面で 「Add Content Root」で該当のプロジェクトROOTになっている箇所をsrc以下に設定。そのとかJSファイルのディレクトリ等も追加しておくとgoglandのプロジェクトファイルに追加されます。

かなり対処療法的な内容になりましたが、一応これで動作するようになりました。

どうしてGLOBALなGOPATHを参照してしまうのか? という疑問は全く解消してません。