DailyHack

文系出身で Software engineer として渋谷で働いています。

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()メソッドを使ってクエリを直に指定する必要があります。

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系のソフトウェアなので同じような設定の仕方でいけますね。

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

結果

f:id:ema_hiro:20170815223355p:plain

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

便利。目に優しい!