フラーをやめて、READYFORに入る。
1月末でフラーを退職して、2月頭からREADYFORで働いています。
Ruby / Rails をしっかり書くのは初めてなので、色々触りながら「う〜ん、これはいかんな・・・」とか試行錯誤してる毎日です。
俺はやるぞ!何かを!
— shmokmt / Shoma Okamoto (@shmokmt) 2020年10月9日
転職の決めて
VP of Engineeringの伊藤さん からスカウトをいただき、転職しました。
個人的にはガバナンスが効いており、CTOやVPoE、EMなどの技術経営やマネジメントをするポジションが確立されているというのが大きな決め手となりました。
エンジニアの人数が一年で2倍に!CTOに聞く、サービスの未来を描ける技術組織のつくり方|READYFOR note
想定外のイベントを前向きに楽しむ
READYFORで働くことは紛れもなく偶然なんですが、 前向きにチャレンジすることで自分にとってプラスとなる機会をたくさんつくろうと思います。
思いがけない出来事がキャリアに大きな影響を与えることを計画的偶発性理論(Planned Happenstance)等といったりするらしいですが、 現職ではこれを体験してみたいなと思っています。偶然の出会いやご縁こそ大切にしたいものです。
社会的な問題を解決する
コロナ基金のプロジェクトがREADYFOR史上最大の支援額になったりしており、サービスのインパクトと使命感を感じています。 創業当初のRailsのコードベースが結構でかいんですが、今のシステムにそぐわないものも当然あるわけでそういう箇所に アグレッシブに手を加えていけたらいいなと思っています。
これから
という感じでREADYFORはエンジニアリング組織に対して理解のある会社です。
エンジニアが毎月入社しており、急成長段階で面白いフェーズだと思っています。 一皮剥けるように頑張ります。
皆様READYFORをよろしくお願いいたします。
Go で multipart/form-data を扱ってみる
この記事は フラー Advent Calendar 2020 の24日目の記事です。 23日目は @tmshr06 さんで
Firestore Emulator を使って GitHub Actions でテストを動かしてみる - Qiita
でした。
今回は Multipart について勉強したときのメモを残しておきます。
multipart とは何か
電子メールにASCII文字データ以外の任意のデータを含めるMIMEの拡張仕様の一つです。 具体的にはHTTPヘッダーに以下のような フォーマットの Content-Type で表現します。
Content-Type: multipart/form-data; boundary=c7245ee369df31f524686275eb89381b30581b1ca5557de2453f9f8cf66c
※ boundary は一例
Content-Type は multipart/{subtype} の形式を取ります。 boundary は複数のコンテンツの区切りを示す文字列です。ランダムな英数文字列*1が用いられることが多いです。
それぞれのsubtypeを以下の用途にしたがって使い分けられることが多いです。
multipart/alternative(内容の同じテキストメールとHTMLメールのように、形式は違っても内容は同じもの) multipart/mixed(個々の要素はそれぞれ異なる) multipart/related(関連するものを表現) multipart/form-data(HTMLフォーム、POSTメソッドから送信する)
上記の記事が参考になりました。
multipart/form-data とは何か
先述した multipart のsubtype の一つです。
これはHTTPのPOSTメソッドでmultipartで表現したものを送信したいときに使います。
Go の標準パッケージにも multipart パッケージが存在しますが、 HTTP での利用を想定したものです*2。 APIはそれなりに揃っているので、読み込みや書き込みはこれを使えば大体のことはできると思います。
multipart package (クライアントのサンプルコード)
file, _ := os.Open(filePath) defer file.Close() body := &bytes.Buffer{} writer := multipart.NewWriter(body) part, _ := writer.CreateFormFile("file", filepath.Base(file.Name())) io.Copy(part, file) writer.Close() r, _ := http.NewRequest("POST", "http://example.com", body) r.Header.Add("Content-Type", writer.FormDataContentType()) client := &http.Client{} client.Do(r)
上記のように表現することができます。
NewWriter
は全体のマルチパートメッセージを持つためのWriterで、生成するときにrandomにboundary(base16)が決定します。
パートを追加するときは、CreatePart()
を使って追加することができますが、
より高レベルのAPIとして、CreateFromFile()
や CreateFromField()
が提供されており、これらのメソッドを使うことで各パートのContent-Typeの設定などを省略することが可能です。
送信する前に最後のboundaryを挿入するために writer.Close()
を呼び出す必要があります。
明日は okuzawats さんで 「Androidアプリ開発入門2020」 です。お楽しみに。
合わせて読みたい
RFC 7578 - Returning Values from Forms: multipart/form-data
RFC 2046 - Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types
multipart - The Go Programming Language
Go言語のmime/multipartパッケージでファイルをアップロードしましょう | by James Kirk | Eureka Engineering | Medium
golang multipart file upload example
html - HTMLマルチパートフォーム- “boundary”文字列の最大長?
*1:具体的なフォーマットはここでは割愛させていただきますが、
RFC 2046 - Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types
に記載されています。
*2:FormDataContentType() を呼び出したときに Content-Type は必ず multipart/form-data になります。
Go の context パッケージとは何か
この記事はフラー Advent Calendar 2020 - Adventar の17日目の記事です。 16日目は shmokmt さん で 「MySQL の Generated Columnsについて」でした。
私がGoを勉強し始めたときに独特な概念だなと思ったのがcontextパッケージでした。
今日はそのcontextパッケージについてまとめようと思います。
※ この記事は先日、社内勉強会で発表した資料の簡単な書き直しになります。
context を使うと何が嬉しいのか
context の定義
type Context interface { // コンテキストがキャンセルされたらチャネルをクローズ Done() <- chan struct{} // コンテキストがキャンセルされた理由 Err() error // 時間が設定されている場合は時間を教えてくれる Deadline() (deadline time.Time, ok bool) // Value(key interface{}) interface{} }
- Context はgorotine safeで任意の数のgoroutineにContextを渡すことができます
- Context の親は子をキャンセルできるが、子は親をキャンセルすることができます
タイムアウトの処理(例)
func main() { ctx := context.Background() ctx, cancel := context.WithTimeOut(ctx, 3 * time.Second) defer cancel() go doHeavyProcess(ctx context.Context) { for { fmt.Println(ctx.Deadline()) time.Sleep(1 * time.Second) } }
リクエストスコープなデータへのアクセス
WithValue(parent Context, key, val interface{}) Context Value(key interface{}) interface{}
context はキャンセル/タイムアウト処理だけでなく、リクエストスコープなデータにアクセスする手段も提供します。
ここでいうリクエストスコープなデータというのは、ユーザID, 認証情報(token), トレースID
などのことを指しています。
API Client, DB driver, Loggerなどのリクエストスコープでないことが自明であるものは含めないようにしましょう。
値の取得と設定は色んなで実装してしまうと混乱してしまうので、set/get用の関数を定義して呼ぶことが推奨されています。
type firebaseTokenKeyType struct{} var firebaseTokenKey firebaseTokenKeyType func WithFirebaseToken(ctx context.Context, token string) context.Context { return context.WithValue(ctx, firebaseTokenKey, token) } func GetFirebaseToken(ctx context.Context string { // inteface{} が返ってくるので、型アサーションしてあげる v, ok := ctx.Value(firebaseTokenKey).(string) if !ok { return "" } return v }
所感
- ctx は関数/メソッドの第一引数に書く
- 活用することでgoroutineの開放漏れを防ぐことができる
- タイムアウト処理はtimeパッケージではなく、contextパッケージを活用するとGoっぽさが出る
- ctxに保存する情報は認証情報やユーザーIDなどのcredentialsなど(リクエストスコープなデータに限る)
- ctxのget/set は関数を定義して、それを呼び出して利用するとコードが荒れない
- setする関数を呼ぶ場所は HTTP Middlewareあたりでやると良さそう
合わせて読みたい
- context package - context - pkg.go.dev
Goのcontext.Contextに入れる値をリクエストスコープに限る理由 | Money Forward Engineers' Blog
Junpei Tsuji さんのブログの解説が丁寧で非常に参考になりました。
ありがとうございます!
明日は @yudai_watanabe さんで 「リモートワーク下のスクラム開発で気をつけたこと」です。お楽しみに!
MySQL の Generated Columnsについて
この記事はフラー Advent Calendar 2020 - Adventar の16日目の記事です。 15日目は sohei さん で 「オンボーディングHACKS」でした。
MySQL 5.7.6以降ではGenerated Columns という機能を使うことができます。 これは実カラムの値を計算した結果を格納するためのカラムを作る機能です。
CREATE TABLE `member` ( `id` INT AUTO_INCREMENT NOT NULL, `first_name` VARCHAR(10) NOT NULL, `last_name` VARCHAR(10) NOT NULL, `full_name` VARCHAR(255) AS (CONCAT(last_name, ' ', first_name)), PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4;
Generated Columnsをデフォルトの設定で使う場合は上記のfull_name
の AS expr
で計算したい式を追加するだけで大丈夫です。
INSERT INTO `member` (first_name, last_name) VALUES ('shoma', 'okamoto');
厳密なsyntaxは以下のようになっています
col_name data_type [GENERATED ALWAYS] AS (expr) [VIRTUAL | STORED] [NOT NULL | NULL] [UNIQUE [KEY]] [[PRIMARY] KEY] [COMMENT 'string']
実際にデータを投入してみて、SELECTしてみます。
mysql> SELECT * FROM `member`; +----+------------+-----------+---------------+ | id | first_name | last_name | full_name | +----+------------+-----------+---------------+ | 1 | shoma | okamoto | okamoto shoma | +----+------------+-----------+---------------+ 1 row in set (0.00 sec)
取得できました。簡単! ヤッタネ!
Types
STORE
と VIRTUAL
という格納タイプが存在します。
デフォルトでは VIRTUAL
になります。
それぞれの意味は以下の通りです。
STORED: 挿入/更新時に列の値を計算してストレージに保持します。インデックスを貼ることが可能です。
VIRTUAL: 取得時に都度、仮想列の値を計算します。ストレージには保持されません。
InnoDBのみ仮想列にセカンダリインデックスを貼ることが可能です。
制約
- サブクエリで実行することができない
- 生成列を
AUTO_INCREMENT
で使えない - 計算元の指標に
AUTO_INCREMENT
が使えない - 外部キーが仮想列を参照することができない
等の細かい制約があるため使うときにはしっかりと公式ドキュメントを読んだ方が良いでしょう。 おそらくシンプルな使い方しかできないんじゃないかなと思います。
どんなときに使うのか
複雑な条件文をシンプルにしたいとき
生成カラム(関数インデックス)にインデックスを貼りたいとき
関数を使った条件文は基本的にインデックスが効きませんが、Generated Columnsでは関数に対してインデックスを貼ることができます。 yoku0825 さんのブログに書いてある具体例がわかりやすかったです。
日々の覚書: MySQL 5.7.6のgenerated columnは関数インデックスの夢を見るか
気になった箇所
For stored generated columns, the disadvantage of this approach is that values are stored twice; once as the value of the generated column and once in the index.
STORED
生成列にインデックスを貼る場合は生成列を計算するときとインデックスを貼るときで2回計算が走るようなので、注意が必要です。
When a secondary index is created on a virtual generated column, generated column values are materialized in the records of the index. If the index is a covering index (one that includes all the columns retrieved by a query), generated column values are retrieved from materialized values in the index structure instead of computed “on the fly”.
理由は仮想生成列にセカンダリインデックスが作成されると、生成された列の値をインデックスレコードが持つからだそうです。したがって、インデックスを貼りたい場合はデフォルトの VIRTUAL
を使えば良いのかなと思っています。(間違っていたら、指摘してもらえると嬉しいです)
終わりに
ISUCON10の予選で先輩にGenerated Columnsという機能があるんだよと教えていただき、今回調べてみました。 明日は shmokmtさんで 「Go の context パッケージについて」です。
参考
MySQL :: MySQL 5.7 Reference Manual :: 13.1.18.7 CREATE TABLE and Generated Columns
MySQL :: MySQL 5.7 Reference Manual :: 13.1.18.8 Secondary Indexes and Generated Columns
ISUCON10 の振り返りと MySQL の generated columns - Techtouch Developers Blog
自分なりの集中を継続する方法
実務でどうやったら集中できるかの自分なりの考察とその対策
ADHD傾向があるので、どういう状態のときに作業しやすいか書いてみる。
どうやったら集中を継続できるか
- リズミカルな音楽がある
- 取り組む1つあたりのタスクの量が適切である
多すぎると見積もりの粒度がブレるし、コンスタントにPRをマージできないのは、士気が下がる気がする
- 取り組むタスクの範囲が明確である
エンジニアが決めても大丈夫な問題でもあれば、ステークホルダーに確認を取っちゃう。
- 取り組むタスクの難易度が自分にとって少し難しいレベルである
何がわからないのかわからないというパニックゾーンに陥りそうなものはあらかじめアラートを出す。またはすぐアドバイスがもらえる状態にしておくなどの工夫をする。
- 作業を進めるにあたって何かしらのフィードバックがある
エディタやCIのLinter / レビュワーのコメント / プロダクトオーナーからのフィードバックなど
どんなときに集中が切れるか
- 考察する際に経験がない深い知識を求められるとき
要は興味はあるがわからないことが多すぎて、何がわからないのかわからないみたいなとき。 難しいアルゴリズムやコンピュータサイエンスの専門的な理論や知識を求められるときはすぐに自分のものにできないので、 GitHubで素振りのリポジトリを作る等して分割しながら少しずつ理解を進めていくほうが良さそう。
トイレ(椅子から離れる)
細切れでミーティングが入るとき
いい集中力の切れ方がある
新しい知識を獲得しようとしているときは要は筋トレみたいなもので、いい集中力の切れ方。 でも、切れにくいように対処することはできる気がしていて、大体以下のようなことをしてるっぽい。
- 自分にとってちょっと負荷がかかる程度のブログやスライド、本を漁る
難しすぎるものを読んでもパニックになって認知負荷が上がり、学習効率が悪く、疲れるだけな気がする派です。
- 取り組む範囲を資料ベースで決める
自分に合う資料をベースにそれを理解するようにググって、暗黙知をためていく。
※ 自分でも読破できそうという自信がモチベーションを下げないために大事なので、 公式ドキュメントが難しすぎるようであれば、最初は信頼できる記事を読んでもいいと思う派。 でもそのあと必ず公式ドキュメントと照らし合わせたりするのは大事だと思う。
- 素振りをする
手を動かす
獲得したい知識を使用した小さなコードをリポジトリにアップロードして、限りなくノイズが入っていない状態で動作を確認する。 まだ新しい技術やライブラリであれば、それが本番環境に適用できるレベルのものか確認する。
- 歴史を知る
その技術が使われているのは、それ以前の技術に改善の余地があったから。 ルーツを辿ることで記憶できるし、腹落ちした状態で取り組むことができる。 その技術の本質を見失うことがないと思う。
集中力切れたとき
- 風呂
- ランニング
- 睡眠
大体上記のどれかで解決する
まとめ
定期的に素振りをして、なんでそれを使うのか背景から説明できると、自分の中で意義が見出だせて モチベーションが下がらない気がする。
結果的に過集中モードに入りやすい。
合わせて読みたい
ISUCON10予選参加しました
cli/cli をシェルでちょっといい感じにする
はじめに
GitHub製のCLIツールである GitHub - cli/cli: GitHub’s official command line tool の小ネタです。
ちなみに cli はhttps://github.com/github/hub というツールの後継に当たります。
zsh と peco でシュッっとブラウザに飛ぶをやってみる
チーム開発でよくしたくなるのは、手元でissueやPRの一覧を確認して、シュッっとブラウザに飛ぶことではないでしょうか。
ghコマンドで実際にissueとPRをブラウザで開くコマンドは以下のような形式を取ります。 ですが、issueやPRの番号とかいちいち覚えてないと思います。
gh issue view <number> --web gh pr view <number> --web
なので、
gh issue list gh pr list
などの一覧を取得するコマンドから番号だけ抜き取るようにします。
❯ gh pr list Showing 4 of 4 pull requests in kayac/ddl-maker #23 Support for FULL TEXT index shmokmt:full-text-index #22 add LF to EOF shogo82148:add-lf-to-eof #21 Add new nullable types shogo82148:add-new-nullable-types #20 add go.mod shogo82148:add-go-mod
空白で列を分割できそうなので、awk
で分割してあげます。
gh issue list | peco | awk -F" " '{print $1}' | read num; gh issue view $num --web gh pr list | peco | awk -F" " '{print $1}' | read num; gh pr view $num --web
毎回これらのコマンドを実行するのは面倒なので、function
として ~/.zshrc
にでも登録してあげると良いと思います。
function issue() { line=$(gh issue list | peco) num=$(echo "${line}" | awk -F" " '{print $1}') gh issue view "${num}" --web } function pr() { line=$(gh pr list | peco) num=$(echo "${line}" | awk -F" " '{print $1}') gh pr view "${num}" --web }
これでコマンドを実行するカレントディレクトリがGitHubリポジトリ配下だった場合に対象のページをシュッっと開くことができます。