きょこみのーと

技術に関係ないほうのブログ

毎週テキサス・ホールデムをやってエンジニア力を鍛える弊社の紹介

これはCluster,Inc. Advent Calendar 2017の第10日目の記事です。

弊社の会社紹介しようかなと思います。 弊社VRの会社なんですが、社内では毎週テキサス・ホールデムのポーカーをやっています。(全然VR関係ないです 😇)

IVSのピッチコンテストLaunch Padというイベントで優勝して 本当に叶うAmazonウィッシュリスト というのがAmazonさんより贈呈されて、僕がポーカーやりたいなーと思ってポーカーテーブルをいれたらめっちゃ社内で流行って大満足という感じです 😄

他にもボドゲがいっぱい

これがIVSのイベントの記事です

jp.techcrunch.com

テキサス・ホールデムとは?

テキサス・ホールデム(Texas hold 'em)はポーカーの一種。各プレイヤーごとに配られる2枚の手札と、コミュニティ・カードと呼ばれる全プレイヤー共通のカード(最大5枚)を組み合わせてプレーする。アメリカ合衆国のカジノにおいては最もポピュラーなゲームのひとつである。通常は2人から10人で行われる。

Wikipediaより引用

社内の様子など

f:id:kyokomi:20171130030307j:plainf:id:kyokomi:20171208180933j:plain

  • 大体週1で開催されてる(毎週木曜の20:00くらいから23:00くらいまでやってる)
  • 前職でお世話になった@axrossさんが外部講師(?)として時々きてくれる(結構頻度高)
  • 社外の人も口コミというか、社員の友人的な感じで参加して結構ワイワイやってる
  • ポーカーは論理や確率に基づいたプレイが大事で、ポーカーが上手い人はエンジニアとしての仕事の進め方とか判断力を鍛えられている気がする(そんな気がする)
  • 主に上達目的でリングゲームをやっているがたまに気分転換にトーナメントをやったりしてる
  • ポーカー道あたりで用語を抑えたあとにフィル・ゴードン本(緑)を買って読むメンバーが結構多い(そろそろ会社の本棚に置いてもいいのでは感)あとはひたすら遊んでる
  • 社員13人中9人くらいポーカーやっててVRの会社のはずなんだけど、ポーカーの会社みたいになってる現状

フィル・ゴードンのポーカー攻略法 入門編 カジノブックシリーズ

フィル・ゴードンのポーカー攻略法 入門編 カジノブックシリーズ

弊社でポーカーで遊びたーいという方がいたら気軽にお声がけください!

現場で役立つシステム設計の原則を読んだ

ざっくり説明すると、DDDの文脈にあるドメインモデルやドメインオブジェクトとかを意識してプログラミングとかやっていきましょうという感じ。

エリック・エヴァンスのDDD本は概念的な話やユビキタス言語とかをちゃんと定義してドメインモデルを作っていってチーム全員でやっていき!って感じで、 なんか手っ取り早くいい感じのアーキテクチャとかプログラミングでパワーアップして、いい感じのプロダクトを作りたい!!!! って人には難しい内容となっていたと思います。

(DDD本、自分はとてもいい本だと思ってますので、あとで読むのはおすすめします)

本書はDDD本と違って、プログラミングからのアプローチでDDDの一部を徐々に学んでいく感じがしました。

他にもオブジェクト指向やWebAPI設計やDB設計やリファクタリングなど、システム開発で必要となる基本的な知識を様々な良書から引用して、実際に開発するときに役立つ形でまとまっていました。

正直、 数年前に読みたかった... という一冊です。(引用元の本はほとんど読んでいたので)

システム開発する前にたくさん読む本がある〜という人向けに一冊である程度必要な知識を得ることができてとても良いと感じました。 理解できなかったところやもうちょっと深掘りしたいと思ったものは、書籍に引用元の本がリンクされているのでそれを辿っていくと良さそうです。

いきなりDDDとか言われても... みたいな人にも優しい内容になっていて、とりあえずこの本の内容を理解して開発していればいずれ「DDDやるぞ!」ってなったときに割りとスムーズにやっていけそうだなと思いました。

goa勉強会 in 六本木一丁目でLTしてきましたので内容の補足など

istyle.connpass.com

LTしてきました。色々学びあって楽しかったです。

会場提供のistyleさんありがとうございました。

少しだけ補足説明とexampleコードを書いたので紹介しようと思います。

LTした資料

speakerdeck.com

注意点

  • goとgoaについてすでにある程度理解がある人向けです
  • goaのversionは、v1.2.0の前提で話しています

go generate使ってコード生成して開発する流れについて

–forceをつけることで毎回mainのgenerateは作り直してます。

//go:generate goagen -o gen main --force -d github.com/kyokomi/example_goa_api/_design
//go:generate goagen -o gen app -d github.com/kyokomi/example_goa_api/_design
//go:generate goagen -o gen swagger -d github.com/kyokomi/example_goa_api/_design

_design下を変更したときの作業手順は以下のような感じになります。

  1. _design下を編集する
  2. ルートディレクトリでgo generateする
  3. 生成された gen/main.go から追加したリソースのMountのコードをルートディレクトリの main.go にコピペする
  4. gen/ 下に生成されたリソースのControllerを application/controllers下にコピーしてimport等を直す(リソース追加ではなく編集の場合は、必要なMethodだけコピペする)

swaggerの配布について

werckerのworkflowの設定はこんな感じです。

f:id:kyokomi:20170617235511p:plain

buildのworkflowはexampleのリポジトリをご覧ください(https://github.com/kyokomi/example_goa_api/blob/master/wercker.yml)。 build のworkflowが終わったあとに design/* のブランチ名だったら put-swagger のworkflowが実行される感じになってます。

put-swagger:
  box: golang:1.8
  push-to-ecr:
  steps:
    - script:
        name: set timezone and deploy tag
        code: |
          export VERSION=`cat $WERCKER_ROOT/VERSION`
          echo $VERSION
    - edgecaseadmin/install-aws-cli:
        key:    $AWS_ACCESS_KEY_ID
        secret: $AWS_SECRET_ACCESS_KEY
        region: $AWS_REGION
    // s3にswagger.jsonをコピーする
    - script:
        name: s3 put swagger
        code: |
          aws s3 cp $WERCKER_ROOT/gen/swagger/ s3://swager.example.dev/$WERCKER_GIT_REPOSITORY/$VERSION/ --recursive
    // s3に配置したswaggerのjsonをurlに指定したswagger-uiのリンクをpullRequestコメントする
    - script:
        name: swagger github comment
        code: |
          go get github.com/kyokomi/gh-rice
          gh-rice \
            -t "$GITHUB_TOKEN" \
            -o "$WERCKER_GIT_OWNER" \
            -r "$WERCKER_GIT_REPOSITORY" \
            -b "$WERCKER_GIT_BRANCH" \
            -c "http://swagger.example.dev/?url=http://swagger.example.dev/$WERCKER_GIT_REPOSITORY/$VERSION/swagger.json"

※s3のパスとかは適当です

exampleコード

ヘルスチェックのAPIくらいしか書いてないのであれですが、参考になればと。

github.com

gormのCallbackをつかってexplain結果をログ出力する

はじめに

explainは基本的に実装時に自分で叩いてると思いますが、dev環境とかそこそこデータとか利用頻度が高い環境で雑に垂れ流したいなーと思ってgormのDebug実装を眺めていたらCallbackという仕組みがあったので、それを使ってみました。

作ったもの

github.com

使い方

gormのexampleをにちょこっと追加してみました。

package main

import (
    "fmt"

    _ "github.com/go-sql-driver/mysql" // mysqlを使う
    "github.com/jinzhu/gorm"
    explain "github.com/kyokomi/gorm-explain"
)

type options struct {
    user     string
    password string
    host     string
    port     int
    dbName   string
    location string
}

func buildDataSourceName(opts options) string {
    const format = "%s:%s@tcp(%s:%d)/%s?parseTime=True&loc=%s"
    return fmt.Sprintf(format, opts.user, opts.password, opts.host, opts.port, opts.dbName, opts.location)
}

// Product product table struct
type Product struct {
    gorm.Model
    Code  string
    Price uint
}

func main() {
    opts := options{
        user:     "test-user",
        password: "test-user",
        host:     "127.0.0.1",
        port:     3306,
        dbName:   "test_db",
        location: "UTC",
    }
    db, err := gorm.Open("mysql", buildDataSourceName(opts))
    if err != nil {
        panic("failed to connect database")
    }
    defer db.Close()
    db = db.Debug() // query trace
    db.Callback().Query().Register("explain", explain.Callback) // add explain callback

    // Migrate the schema
    db.AutoMigrate(&Product{})

    // Create
    db.Create(&Product{Code: "L1212", Price: 1000})

    // Read
    var product Product
    db.First(&product, 1)                   // find product with id 1
    db.First(&product, "code = ?", "L1212") // find product with code l1212

    // Update - update product's price to 2000
    db.Model(&product).Update("Price", 2000)

    // Delete - delete product
    db.Delete(&product)
}

実行した結果

(あと、gormのDebug機能をつかうとqueryがログに吐かれるようになるのでこれを合わせて使うと便利です)

(/Users/kyokomi/workspace/go/src/github.com/kyokomi/gorm-explain/example/main.go:53)
[2017-05-05 18:16:38]  [1.08ms]  INSERT INTO `products` (`created_at`,`updated_at`,`deleted_at`,`code`,`price`) VALUES ('2017-05-05 18:16:38','2017-05-05 18:16:38',NULL,'L1212','1000')

(/Users/kyokomi/workspace/go/src/github.com/kyokomi/gorm-explain/example/main.go:57)
[2017-05-05 18:16:38]  [0.95ms]  SELECT * FROM `products`  WHERE `products`.`deleted_at` IS NULL AND ((`products`.`id` = '1')) ORDER BY `products`.`id` ASC LIMIT 1
+-------+----------------+----------+---------------+---------+------------------+--------+------------+--------+---------+-------------+--------------------------------------------------------+
|    id |    select_type |    table |    partitions |    type |    possible_keys |    key |    key_len |    ref |    rows |    filtered |                                                  Extra |
+=======+================+==========+===============+=========+==================+========+============+========+=========+=============+========================================================+
|     1 |         SIMPLE |          |               |         |                  |        |            |        |         |             |    Impossible WHERE noticed after reading const tables |
+-------+----------------+----------+---------------+---------+------------------+--------+------------+--------+---------+-------------+--------------------------------------------------------+



  1 Fix example to add query trace

(/Users/kyokomi/workspace/go/src/github.com/kyokomi/gorm-explain/example/main.go:58)
[2017-05-05 18:16:38]  [1.11ms]  SELECT * FROM `products`  WHERE `products`.`deleted_at` IS NULL AND ((code = 'L1212')) ORDER BY `products`.`id` ASC LIMIT 1
+-------+----------------+-------------+---------------+---------+----------------------------+----------------------------+------------+----------+---------+-------------+---------------------------------------+
|    id |    select_type |       table |    partitions |    type |              possible_keys |                        key |    key_len |      ref |    rows |    filtered |                                 Extra |
+=======+================+=============+===============+=========+============================+============================+============+==========+=========+=============+=======================================+
|     1 |         SIMPLE |    products |               |     ref |    idx_products_deleted_at |    idx_products_deleted_at |          5 |    const |       2 |         100 |    Using index condition; Using where |
+-------+----------------+-------------+---------------+---------+----------------------------+----------------------------+------------+----------+---------+-------------+---------------------------------------+


(/Users/kyokomi/workspace/go/src/github.com/kyokomi/gorm-explain/example/main.go:61)
[2017-05-05 18:16:38]  [0.86ms]  UPDATE `products` SET `price` = '2000', `updated_at` = '2017-05-05 18:16:38'  WHERE `products`.`deleted_at` IS NULL AND `products`.`id` = '7'

(/Users/kyokomi/workspace/go/src/github.com/kyokomi/gorm-explain/example/main.go:64)
[2017-05-05 18:16:38]  [0.88ms]  UPDATE `products` SET `deleted_at`='2017-05-05 18:16:38'  WHERE `products`.`deleted_at` IS NULL AND `products`.`id` = '7'

注意点

さっと書いたので、fmtで出力してます。ちゃんと自前のロガーとかで出力したい場合は、このコード参考に自分で書いてください 🙏🏻 (もしくはPullRequestまってます 😇 )

おわり

explainの結果に filesortが含まれていたら〜みたいなHandlerを設定できるような感じに改修とかすると便利そうだな〜とか思ってます。

esaのAlfred WorkflowをGoで作りました

作ったもの

f:id:kyokomi:20191123150311p:plain

github.com

とりあえずtokenとチーム名を登録して記事を検索できるだけです。

注意点

ちなみに、esaAPIは利用制限があるので検索しすぎにご注意ください

https://docs.esa.io/posts/102#2-3-0

現時点では、ユーザ毎に15分間に75リクエストまで受け付けます。 とのこと

参考にしたもの

使ったライブラリ

github.com

filterで一覧表示するのがこんなに楽に実装できて感謝

github.com

esaのClientすでに作っている方がいてesaAPI仕様はあまり見ないでもサクッと作れました! 感謝

github.com

最初、直接 Open in Finder したフォルダ内にバイナリを放り込んで開発してたんですが、 普通に xxx.alfredworkflow をダブルクリックしてInstallして動作確認したかったので、 alfred-workflow-packager を使うようにしました。(まあ、色々replaceしてzipに固めるだけっぽいですが)

ただ、これはMacでAlfred3がインストールされている前提のコードがあるので、ちょっと書き直してCIで動くようにしようかなと思ってます。

GoでAlfredWorkflowを作る手順(雑メモ)

AlfredWorkflowつくるぞ〜と思ってから最初どうすればいいかわからない具合いが結構すごかったです。

とりあえず自分は、似てるworkflowをinstallして Alfred上で Open in Finder して中身を漁ってつくりを把握してつくってみました。 思ったより単純な構造で、基本的には以下の流れでつくればいいのかなという理解。

  • 普通にCLIツールをGoで作る
    • Script filter での出力は go-alfred 等を使ってxmlをPrintする
    • Run Script して Post Notification に表示する出力は普通に fmt.PrintでOK
  • Alfred上でkeywordsとかをとりあえず作成して ./<バイナリ名> search "{query}" みたいな感じでCLIツールの呼び出しする
  • Open URLとか適当なActionを紐付けて、とりあえず動くところまでいく
  • Open in Finder して icon.pnginfo.plist だけコピーしてGoのProjectにコピーする
  • あとは alfred-workflow-packager を使って xxx.alfredworkflowを作成して、ダブルクリックしてインストールして動作確認していく感じ

まとめ

Alfred便利なのでどんどん作っていくぞ!!!!!!

昔作ったgaego-initをglide対応した

github.com

gaego-initとは以下のように、GAE/Goの開発環境をコマンド一発でgenerateするやつです。

$ gaego-init -app hoge-app
$ tree hoge-app/
hoge-app/
├── Makefile
├── README.md
├── appengine
│   ├── app.yaml
│   └── init.go
└── circle.yml

1 directory, 5 files

以前作ったときは、vendoringの対応をどうするかべきかわからず、結局shellでgoapp getするという荒業をしていたのでそれを修正しました。

# Makefile                                                                                                                                                                                            22:26:56
APP_ROOT=appengine
VENDOR_GOPATH=$(shell pwd)/$(APP_ROOT)/gopath/vendor
APP_GOPATH=$(shell pwd)/$(APP_ROOT)/gopath
RUN_GOPATH=$(APP_GOPATH):$(VENDOR_GOPATH)

install:
    glide init
    glide install
    mkdir -p $(APP_ROOT)/gopath/vendor
    ln -s $(shell pwd)/vendor/ $(VENDOR_GOPATH)/src

run:
    GOPATH=$(RUN_GOPATH) goapp serve $(APP_ROOT)

clean-run:
    GOPATH=$(RUN_GOPATH) goapp serve --clear_datastore $(APP_ROOT)

deploy:
    GOPATH=$(RUN_GOPATH) goapp deploy $(APP_ROOT)

こちらの記事を参考にglideでのvendoringを行えるようにしました。

motemen.hatenablog.com

感謝 🙏 🙏 🙏 🙏 🙏

Arukasを使って無料でGo製のslackbotを運用する

はじめに

SlackbotをHerokuで一日中動かすと課金が発生するので色々ハックが必要となったりするので、なんとかしたくArukasに出会いました。

今回は、自分が作ったgo製のslackbotを元につくったbotgithubにpushしてWercker経由でdockerImageを作ってArukasへのDeployを行う方法を紹介します。

github.com

今回のコードとか設定をexampleとして公開してますので、よろしければ参考にしていただければと。

Arukasとは?

いまのところ無料でDockerをホスティングできるサービスです。

arukas.io

一応CLIツールも公開されていて、結構便利です。

github.com

※注意点としては、CMDで起動したプロセス監視とかは無いので自分でとかgo-server-starterとかを入れてプロセス死んだときに再起動する仕組みを入れる必要があるところです。

流れ

  • 以前作ったgo製のslackbotのdockerImageをWerckerで作成する kyokomi.hatenablog.com kyokomi.hatenablog.com
  • Arukasのコンソールでアプリケーションを作成し、dockerImageを指定する
  • WerckerのworkflowでArukasのrestartを設定する

slackbotのコード自体はDockerとか意識しない形になっているので、wercker.ymlとかを見ていただければと。

example-go-slackbot/wercker.yml at master · kyokomi/example-go-slackbot · GitHub

Werckerの設定

f:id:kyokomi:20170101221031p:plain

README.md に書いてある通り6つの環境変数の登録が必要です。

ポイントはdocker-pushのworkflowで github.com/lestrrat/go-server-starter/cmd/start_serverをinstallしてます。 まあ別にバイナリをdonwloadでもいいんですが面倒だったので...

docker-push:
  box: golang
  steps:
    - glide-install
    - setup-go-workspace
    - script:
        name: install deamontools
        code: |
          go get github.com/lestrrat/go-server-starter/cmd/start_server
    - script:
        name: install application
        code: |
          go install
    - internal/docker-push:
        username:   $DOCKER_HUB_USERNAME
        password:   $DOCKER_HUB_PASSWORD
        tag:        latest
        repository: $DOCKER_HUB_REPOSITORY
        registry:   https://registry.hub.docker.com

arukas-deployのworkflowでは、 arukasのバイナリをdownloadして適当にPATHを通してstop -> startしてます。 sleep 10 を挟んでいるのは、stopする前にstartするとエラーになるので仕方なくです。

arukas-deploy:
  box: golang
  steps:
    - script:
        name: install tools
        code: |
          sudo apt-get update
          sudo apt-get -f install
          sudo apt-get install -y wget unzip curl tree
    - script:
        name: arukas install
        code: |
          mkdir -p $HOME/lib
          export PATH=$PATH:$HOME/lib
          cd $HOME/lib
          wget https://github.com/arukasio/cli/releases/download/v0.1.2/arukas_v0.1.2_linux_amd64.zip
          unzip arukas_v0.1.2_linux_amd64.zip
          rm arukas_v0.1.2_linux_amd64.zip
    - script:
        name: arukas restart
        code: |
          arukas stop ${ARUKAS_CONTAINER_ID}
          sleep 10
          arukas start ${ARUKAS_CONTAINER_ID}

Arukasの設定

f:id:kyokomi:20170102005223p:plain

おまけ

ちゃんとプロセス死んで再起動するかの動作確認ですが、昔ネタでいれたcommandを実行させるプラグインが役に立ちました。(危険)

f:id:kyokomi:20170102010051p:plain

何度でも蘇る。