emahiro/b.log

Drastically Repeat Yourself !!!!

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を参照してしまうのか? という疑問は全く解消してません。

GQLでプライマリーキーのwhere句指定を行う

GoogleCloudStrageでkey(RDBでいうところのprimarykey)をフックにしてentityをselectしようと思ったらちょっとハマったのでそのメモ

最初に書いたクエリ

SELECT * FROM KIND_NAME where ID = 123

この形式ではentityを取得できない。

正しいクエリ

SELECT * FROM KIND_NAME where __key__ = key(KIND_NAME, 123)

とするとうまくいく。

GQLではプライマリーキーをwhereに指定する場合にはIDではなく、 __key__ を指定し、かつ key(KIND_NAME, (ID)) という形式にしないと検索できない。

こんなことでハマってしまったのであるが、 リファレンス には

Every GQL query string always begins with SELECT <something>, where <something> is one of the following:

- *
- <property-list>, a comma delimited list of properties to be returned from the query.
- __key__, which returns keys only. (__key__はkeys(primarykey)のみを返す)

と記載してあった。

解答がわかった後にリファレンスをもう一度見直したけど、イマイチわかりづらい。
RDBと同じ感覚でいるとNoSQLでは結構ギャップを感じることが多い。

参照型を作成するmake

goには2つの値の型があります

  • 値型
  • 参照型

値型と参照型

関数に渡されるときにコピーされる。
そのため、呼び出し元で引数に指定した値型の変数は関数に渡されるタイミングでコピーされ、コピーされた値が関数の呼び出しに使用されるので、呼び出し元の値は変化しない。

以下のコードで確認する

func main() {
  a := 1
  b := 1
  sampleFunc(a, &b)
  fmt.Println(a) // 値渡し
  fmt.Println(b) //参照渡し
}

func sampleFunc(a int, b *int) {
  a = a + 1 
  *b = *b + 1
}

メソッド内で使われている a は 呼び出し元のmain関数内の a のコピーであるので、sampleFunc内部で a の値に変更を加えてもコピー先の値が変更されるだけで、main関数内のコピー元のaの値は変更されない。

これを呼び出し先でコピーを渡さずに参照(ポインタ)にするとコピーではなくポインタが渡されて呼び出し先で変更した値は呼び出し元の値にも影響を与えることができるようになる。(サンプルコードでは値b)

なお、値型はメモリ空間の別領域に新しく変数のコピーを作成するので、使いすぎるとメモリ効率が悪くなる。
一方で参照型は変数のアドレスを参照するので、メモリ空間上は同じアドレスを指している。そのため、メモリ効率がいいが、意図せず変数が変更されることがある。(変数の汚染)

参照型でのみ値を持つmap

そもそも、このエントリーを書こうと思ったのは「プログラミング言語Go」の冒頭の方でmapが 参照型 であると明示されていたことによる。

値型参照型 が変数にはそれぞれあることは理解していたが、mapは最初から参照型でのみ値を持つということを理解していなかったので、ここに備忘録として記載。

sampleコード

func main() {
    a := map[string]string{"apple": "りんご"}
    sampleFunc(a)    
    fmt.Printf("%v\n", a)
}

func sampleFunc(a map[string]string){
    a["orange"] = "みかん"
}


// map[orange:みかん apple:りんご] と出力される

sampleFuncに渡している値は参照型でなく通常の値型としてのmap。もし map が値型なら sampleFunc内には map のコピーが渡されて main 内の map は書き変わらないはず。
しかし書き換わっているので、map はデフォルトで参照型のみで値を持つということがわかる。

この他にも参照型のみで値を持つ値に

  • slice
  • channel
  • interface

がある。

参照型を作り出すmake

goで参照型を作成するには make メソッドを使う

// sliceの作成
make([]int, 10)

//mapの作成
make(map[string]string)

値型と参照型。ちょっと迷うことが多いけど一度調べてみてよかった。

一通りスターティング的な内容の書籍は読み終わったので、ガッツリ「プログラミング言語Go」を現在呼んでいる。

shell二刀流を試してみてる

shellを二重で使ってみてます。

使っているshell

用途によって分ける

普段使いのshellはfishです。
もともとzsh使っていたのですが、なんとなく使っていてもっさり感が出てきたのと、いちいちrcファイルでカスタマイズするのがめんどくさくなってきたので fish に乗り換えました。

fishはカスタマイズも専用のパッケージ管理ツールで行ってくれるので非常に楽です。
使っているパッケージ管理ツールは oh-my-fish です。

しかし、fishの悩みは、shellスクリプトの書き方が独特だということ。
例えば $(コマンド) みたいな書き方は使えず、 A && B という書き方も使えません。 (それぞれ (コマンド)A; and B と書きます。)
直感的といえば聞こえはいいですが、普段業務で使うshellスクリプトだったり、ネットで探したときに出てくる参考資料は zsh or bash 向けのものがほとんどでそれ自体をいちいち fish 用に書き換える手間もかかるため、人思いに zsh も使ってます。

zsh もデフォだと見にくいので oh-my-zsh でカラースキーマだけカスタマイズしてます。

結果

めんどくさいかと思いきや、これが意外に使い勝手がいい。

  • zshの用途が制限されたので、今までみたいにzsh向けのカスタマイズが必要なくなったのが心理的にすごく楽
  • fishは今まで通り使っている
  • zsh用のコマンドが合ったら、zshに変更して終われば exit してfishに戻す。

こんな感じで二刀流を最近試してます。

Goのtemplateにおける値の評価の書き方

Golangのtemplateファイルでisやnot equal、and条件、or条件の書き方を調べたので備忘録です。

※ 適宜追加していきます。

is評価

{{ if eq num 1 }} // if num == 1

// 以下同義
{{ if eq ( .num, 1 ) }}

not equel評価

{{ if ne .num 1 }} // if num != 1

//以下同義
{{ if ne (.num, 1) }}

and 評価

{{ if and (eq .str1 "a") (eq .str2 "b") }} // if str1 == 1 && str2 == b

and A B または and (A, B) になります。

or 評価

{{ if or (eq .str1 "a") (eq .str2 "b") }} // if str1 == 1 || str2 == b

or A B または or (A, B) という書き方になります。

nil 評価

objectのpropertyがnilかどうかを判定したいケースを想定してます。

{{ if .Object.SampleProperty }} 
// SamplePropertyが存在する
{{ else }}
// SamplePropertyがnil
{{ end }}

参考: buildin関数

上記の他にもgoのパッケージである text/tempalte パッケージにはtemplateファイルで使えるbuiltin関数があります。

builtin関数は src/text/template/func.go の中を見ると

type FuncMap map[string]interface{}

var builtins = FuncMap{
    "and":      and,
    "call":     call,
    "html":     HTMLEscaper,
    "index":    index,
    "js":       JSEscaper,
    "len":      length,
    "not":      not,
    "or":       or,
    "print":    fmt.Sprint,
    "printf":   fmt.Sprintf,
    "println":  fmt.Sprintln,
    "urlquery": URLQueryEscaper,

    // Comparisons
    "eq": eq, // ==
    "ge": ge, // >=
    "gt": gt, // >
    "le": le, // <=
    "lt": lt, // <
    "ne": ne, // !=
}

と定義されています。
and関数を確認して見てみると、

// and computes the Boolean AND of its arguments, returning
// the first false argument it encounters, or the last argument.
func and(arg0 reflect.Value, args ...reflect.Value) reflect.Value {
    if !truth(arg0) {
        return arg0
    }
    for i := range args {
        arg0 = args[i]
        if !truth(arg0) {
            break
        }
    }
    return arg0
}

and (A, B, C...) という形式を取るときに、Aから評価していって一つでもfalseがあればfalseを返しています。

templateで使える関数を見るにはこの src/text/template/func.go を参照すれば良いと思います。。

Golangでの抽象化について

理解がなんとなく浅いと感じていたGolangの抽象化について簡単にまとめました。

goのサンプル

package main

import "fmt"

type Stringer interface {
    String() string
}

// Stringerを実装する
type Hex int
func (h Hex) String() string {
    return fmt.Sprintf("%x", int(h))
}

func main() {
    var s Stringer
    h := Hex(100)
    s = h // Stringer に Hexを埋め込む
    fmt.Println(h.String()) // Hexに定義されているString()
    fmt.Println(s.String()) // Stringerに定義されているString()
}
  1. Stringer はinterface型で string 型の返り値を持つ String() メソッドを持つ。
  2. ‘Hex’ は int型の新しいtypeとして定義されている。
  3. Hexstring 型の返り値を持つ String() メソッドを持つ。
  4. 変数 sString() の実装の詳細を定義していなくても、 Hex で定義されている String() の実装と同じふるまいをできる。

同じinterfaceを持つtypeで新しく定義した型は同じメソッドを持つ場合、実装の詳細をinterface型で定義している方に埋め込むことで、埋め込まれた側(ここでは Stringer 型)は Hex で定義されている実装の詳細を埋め込まれることで使うことができるようになる。

interface型を使うことで抽象化を実現できる。

というのがいまいち理解できなかったのですが、

type Hex int
func (h Hex) String() string{
    // String()の実装の詳細
}

としている箇所で Hex に対して String() メソッドを定義しているときに

type Hex int {
    String() string
}

と見ることができるので、

type Stringer interface {
    String() string
}

こう考えると埋め込むことでメソッド名と返り値が同じメソッドを持つ構造体の場合、実装の詳細も埋め込むことができる。

ということなのかなと。

「マイクロサービスアーキテクチャ」を読みました

マイクロサービスアーキテクチャを読んだ話

去年くらいに話題になった「マイクロサービスアーキテクチャ」を最近読みました。
仕事でマイクロサービスアーキテクチャを採用しているプロジェクトに関わることがあり、自分自身今までマイクロサービスアーキテクチャを採用しているプロジェクトに関わったことがなかったので、そもそもマイクロサービスってどういった設計で、Monolithicなサービスと何が違うのかをちゃんと理解しようと思って読みました。

本書の構成

  1. マイクロサービス
  2. 進化的アーキテクト
  3. サービスのモデル化方法
  4. 統合
  5. モノリス分割
  6. デプロイ
  7. テスト
  8. 監視
  9. セキュリティ
  10. コンウェイの法則とシステム設計
  11. 大規模なマイクロサービス
  12. まとめ

大まかにまとめると、

  • 1~2が概説
  • 3~ 4が各コンポーネントやDBの設計方法、森シック→マイクロサービスにする流れの説明
  • 6~9が実際にマイクロサービスアーキテクチャで考慮しなればいけない内容を分野を分けてそれぞれ具体例を用いて説明してくれている
  • 10以降はもう少し粒度が荒く、組織的な内容やマイクロサービスアーキテクチャを採用する上で考慮しなければならないこと

という感じ。

3~5、6~9は実際にマイクロサービスアーキテクチャを採用する上でのプラクティスやミドルウェアの説明など粒度が細かい内容が多かったので本エントリでは触れず。

監視ツールやテスト方法、セキュリティに関しては大体設計をする上でMonolithicでもマイクロサービスアーキテクチャでも根本は同じだと感じた。

監視については、マイクロサービスに関わる全コンポーネントを監視できるようにすること。
適切に振る舞っているかを一目でわかるようなメトリックスを用意することなど、Monolithicなサービスを開発しているときは監視対象もほぼ一箇所であった一方で、マイクロサービスは小規模な各コンポーネントが自律して動作しているので、すべて が正常に動作しているかを確認できるように監視しなればならない、ということ。

監視はMonolithicなサービスを開発しているときと比べると、監視対象が増える分、適切な監視体制を構築するのはレベルの高い作業だと感じた。

マイクロサービスアーキテクチャとは?

ではそもそもマイクロサービスアーキテクチャとは何なのか?

1章の言葉を借りると 協調して動作する小規模で自律的なサービス である。

  • 小さく、かつ1つの役割に専念
  • 自律的

どの程度小さければいいのかという問に対してケース・バイ・ケースという答えしか書いてなかったのはちょっと個人的には消化不良。
確かにそうだとは思うのだけれど、そこの指針をもう少し教えてほしかった印象がある。
そのあたりは、実際にアーキテクチャを考える上で議論しながら適切な粒度を決めていくのがいいのであろう。

マイクロサービスの原則

まとめの章から拝借

  • ビジネス概念に沿ったモデル化
  • 自動化の文化
  • 内部実装と詳細の隠蔽
  • すべての分散化
  • 独立したデプロイ
  • 障害の分離
  • 高度な観測

勉強になったところ

  • 「境界づけられたコンテキスト」にそってコンポーネントは分割すること
    • この本を読む前に会社の中でDDD本の輪読会をしていて、DDDについて、ある程度知識を持った状態で読んだので、粒度の粗い、主に設計文脈で説明されている内容を理解するのに非常に役立ちました。
    • ビジネスレベルでのドメイン、技術レベルでのドメイン、目的レベルでのドメインなど、コンテキストを分ける粒度は組織によって異なると思いますが、一定の指針としての 境界づけられたコンテキスト を参考にすべきというのはいい指針だと感じた。
    • コンポーネント間の境界では境界をどう策定するか、どう各コンポーネント間をつなげるかの議論を丁寧に、より厳格にしていくこと。
    • 特定のコンポーネントから見たときにAPIで別のコンポーネントとつなげるので、呼び出し元のコンポーネントは呼び出し先のコンポーネントの詳細は知らなくていいこと。
    • この 知らなくていい 、裏を返せば、何を知っている必要があるのか 、どんなインターフェースを用意するのかを設計段階で合意形成しておくのが大切。
      一方で各コンポーネント内については比較的制限はゆるく、コンポーネント内の規範に従えばいいと。
  • マイクロサービスもコンポーネント単位ではOOP的な思想が入っていること
    • Monolithicなサービス作っているとどうしても、内部で色々密結合してしまうことが多く、いざ大胆な変更をしたいときになかなか動き出せないことがある。
    • 疎結合、詳細の隠蔽というのはマイクロサービスアーキテクチャ全体で見るとオブジェクティブ指向のパラダイムが内部に採用されているように感じました。
  • 最初からマイクロサービスにしない
  • 銀の弾丸にはなりえない
    • マイクロサービスアーキテクチャは課題に対する銀の弾丸ではなく、あくまで求められる要件に対するソリューションの一つであること

読んでみての感想

流行っている、採用実績があるからと行って、プロジェクトにマイクロサービスアーキテクチャを採用していいかというのはやはりすぐには決められないことだと思う。 僕も、キャリアの中でMonolithicなサービスのいち部分を切り出したことがあったのを思い出したけど、Monolithicな状態でも疎結合で切り出しやすい形にしていてよかったと今振り返ってみても思う。
マイクロサービスアーキテクチャ銀の弾丸ではないので、やはり採用した瞬間に管理しなければならない対象は増え、全てを知る人がいなくなるかもしれない。また、ちょっとした変更でも複数コンポーネントの再デプロイが必要になるかもしれず、開発速度もその分食われて遅くなるので、スピード感をもって開発したいスタートアップなどにはむしろ向かない思想だと思う。

ただ、一方でコンポーネントが一つの状態で中にごった煮の如くありとあらゆるドメインのサービスが突っ込まれている状態のツラミももちろんあり、その点から見ると、ある一定程度の規模を超えたり、最初からスケールすることを前提とするサービスであるのであればマイクロサービスアーキテクチャを採用するのはいい選択肢だと感じた。

また、本書とはあまり関係ないけれど、去年話題になった書籍だからか、書籍内で紹介しているツールは主にAWSの内容が多かった。
今だとGCPやMicrosoftAzure等もクラウドサービスが拡張されてきて、フルマネージドな製品も出てきているので、AWSに拘る必要はないのかなーと感じた。