Tsuyoshin blog

所属団体とは関係なく、個人的なblog

社内勉強会でPHPについて少ししゃべった

毎週水曜にやっている社内勉強会

資料

speakerdeck.com

先人の資料を拝借させてもらったのでありがたく思っています。

blogにして書く事があまりなかったので雰囲気の写真を上げておく

f:id:tsuyoshi_nakamura:20190116210517j:plain f:id:tsuyoshi_nakamura:20190116210513j:plain f:id:tsuyoshi_nakamura:20181212191503j:plain


あ、でもnginxは最近なんか日本での活動を活発化させようとしてるっぽい感じがしているのでちょっと気にしてたりします。

初日に暇な人です

このツイートが目にしたので書いておこうかと思います。こんな人もいますよ程度にね。

本日、会社的には仕事始めですけど自分は暇です。

一応理由をつらつら書いておく

1. 仕事を作ったらすぐにやってしまうスタイル

そもそも始業時間が何時で就業時間が何時でみたいな感じで仕事をしていないです。

自分は普通の生活をするように仕事をしていくスタイルです。

taskを作れば、空いた時間で手をつけてしまいます。そしてどんな仕事もある程度のチームでやると思うので、taskが終わったらメンバーに共有なり、レビュー依頼をします。

そのレスポンスが来るまでは当然待ちになってしまう。この待ち状態のtaskが増えるとあまりいい事はないのである程度溜まったらやめておきます

私の場合でいくと勝手にmergeなんて暴走はできないのでどうしても待ちになります。

そうすると、持っているtaskがない状態が普通になってきます。

2. 年末年始はみな休みだから基本レスがない

お休み期間なので仕事をしない事は普通だとは思います。

生活するように仕事もする人にとっては年末年始だからと言って大した差はなくて普通に仕事もします。

何なら休みにして旅行に行っている間も数時間仕事をするときもあります。

周りは稼働をしないので、すぐにレビュー依頼のステータスのtaskになります。

なので

初日に暇なんです。

ちなみに

年末年始はどこも混むので休みを取らないけど、年に1,2回は1wぐらい休んで旅行する時はあります。(ただし、混雑が嫌なのでだいたい平日にとる)

そんな時はだいたいそれまでにタスクをこなしてレビュー依頼(当然丁寧な依頼本文を書きます)にしてから休みに入ります。

そうすると休み明けにはレスが溜まってるのですぐに仕事に取りかかれたりします。

でも一番良いのは

会社的に休みの時は、会社とは関係のないtaskなり、仕事をしていれば良いと自分でも思っている。

だけど、自分の性格的に一つのことにハマると最後まで少しでも早くやりきりたい欲が勝ってしまう。

直した方が良いのかは分からないから現状はこんな感じ

2018年の振り返り

一応12月ももう少しで終わるので振り返りでもしておく。

ざっくり前年との比較

2017年

f:id:tsuyoshi_nakamura:20181228155601p:plain

2018年

f:id:tsuyoshi_nakamura:20181228155629p:plain

たぶんざっくりいうと

  • コードレビューの仕事がだいぶ多くなった
  • issueのハンドリングの仕事がなくたった

という感じなんだと思う。でもgithubのReviewer機能自体のリリースとうちで使い始めた時期がちょっと微妙な感じもするけど...😅

まぁコードレビュー自体はコードを書く事と同じぐらい重要な仕事だと思っているので悪くはない。

自分はわりと障害系のトラブルが発見された時にいち早くトラブルシューティングして復旧させる役を半分強制的に持たされているので

そういう時に為に予備知識を蓄えておきたい意味でもコードレビューはやって損はないと思っている。

気になるPRは勝手にレビューしてみたり、この人はこんなコード書くんだーとか知れるのは良い事

もうちょっと深く振り返ってみる

でもざっくり行く

1月 ~ 5月

主にPHP系にリポジトリへのコミットを繰り返して過ごしていた。大きいプロジェクトはなくて結構リファクタ系もやった感じだった

6月

決済系のプロジェクトが立ち上がり、プロジェクトリードを引き受けたが、結果やりきれず、失敗した。

ただの決済系のプロジェクトだけならよかったのかもしれないが、そこにマイクロサービス文脈を持ち出して一気にやろうとしたから炎上したんだと思う。

自分的には課題は高くされればされるほどありがたいと思うけど、あくまでもチームでやっていかないといけない。その辺でチーム内での足並みを揃えきれず失敗した。

マイクロサービスなので、独立した新しいリポジトリに自由な言語選択、疎結合、DDD....色々風呂敷を広げすぎたのが仇となった。

チーム内でやってやるぞという所まで持っていけなかったのが原因だと思う。

課題が多すぎたというか、一つ一つやって行くべきだったのかもしれない。

自分はプロジェクト自体のリードに向いていないのだとつくづく思い知らせれた月だったと思う。苦い思い出

8月 ~ 9月

色々と仕切り直して自分はプロジェクトリードから外された?外れた?... 結果プロジェクトリードをやめた。他の人にお願いした。

課題設定の難易度も下げられた。選択する言語やアーキテクチャなども既存資産のレールに乗せて取っつきやすいようになった。

エンジニアリング部分で貢献する立場になった。もともとエンジニアリングが好きなので切り替えて頑張った

10月 ~ 12月

引き続き頑張っている。

だけど面白い事にプロジェクトが進むに連れて、このアーキテクチャはこっちのがよくない?みたいなピポットを繰り返すうちに、8月、9月で設計したものからだいぶかけ離れる結果になっている。

マイクロサービスとまではいかないけど、なんか近しい感はちょっとはある。一歩手前まではきている感する。

未知領域へのチャンレンジを日に日に上げるようにすると受け入れやすくなる様子。

人にもよるのだろうが、下記のような違いを感じた

  • 結局は通るだろう壁だから初めに全て受け止めて(愕然するけどね)、どう登ってやろうかと思う人
  • ハードルは少しずつ高くして1つ1つクリアして、達成感を味わいながら先に進むのが合う人

サイバーエージェントの文化ってよく言われるのが、成果からの逆算で全て動けってのがある。上記2つとも逆算からの思考で動けると思うが、まぁアプローチの違いなんでしょうかね。

どっちも正しいと思っている。

ちなみにほぼほぼずっとgolangを書いている

github以外でいうと

技術カンファレンスや勉強会等にちゃんと参加できるようになったのはかなりよかった。前は仕事で技術以外の問題を多く抱えてて、そんな気になれなかったのが正直なところ。

それらの問題が改善されてきたという事なのかもしれない。

色々と外のコミュニティに参加する中で下記の勉強会は結構印象に残っている

"GO GLOBAL" meetup #1 - connpass

また全然レベルの違う感覚があった気がしている。

自分のエンジニアリングのレベルの低さを感じた気がした。

改めてエンジニアとしてもっと高いレベルに視点を持っていきたいなと感じた

グローバルレベルを意識しないとエンジニアとして先がなくなっていく危機感を感じた...

まとめ

こうして振り返ると色々とありましたね。。。

自らの意思を持って行動して得た経験や技術というのが最終的には残るのだなと感じた。

人から言われた事だったり、まぁ仕事だからやるかとか、でやった事は一つも自分の中に残っちゃいない。ある一定期間残っても最後は消える。

なので自分の中での積み上げをするにはやはり自らの意思を大切にしたいなと思っている。

来年はこの辺りを大切にしてエンジニアとして技術の鍛錬をしていきたいと思います。

自分的リモートワークに関して

Makuake Product Team Advent Calendar 2018 の8日目の記事です。

はじめに

今の会社では、所属している開発チーム内でリモートワークを許容するかどうかを取り決めています。なので組んだチームの意思によってリモートワークが採用されるか否かが決まります。

んで今のチームではリモートワークを採用していてここ数ヶ月は基本的にリモートワークで仕事をしています。

多分自分がこの会社でリモートワークの一例目になるかと思います。

数ヶ月過ごしてみての思ってる事、気をつけている事などを書いてみようと思います。

当然、個人の見解ですよ

基本スタンス

そもそもチームを組んでディスカッションから始めないといけないとか、大枠の仕様の議論が存在する場合は同じ空間に集まって仕事した方が絶対に効率的です。

そのフェーズを過ぎて(or 元々必要ない場合もあるかも)からリモートワークが可能というのが基本スタンスです。その前提で気をつけている事などをあげていきます。

特に気をつけている事

自立

基本的に自走できないと無理です。仕様決めしかり、技術的にもそうです。

もちろんチームなので助言を求めたりはサポートを受ける事は問題ないですが、いわゆるおんぶに抱っこ状態では機能しません。

技術的に未知の領域にぶつかるときも当然ありますが、調べ尽くして絶対に習得してやると腹をくくって取り組みます。

レスポンス

slack,githubでのやりとりがメインになります。いつも姿が見えない分、いち早く反応して仕事してる感をアピールするように心がけています。

移動中であってもスマホからレスポンスしてたりします。

どうしても集中モードにしたい場合とかは当然レスポンスが遅くなりますが、その旨をslackのチームのチャンネルに一言伝えてから行います。

パフォーマンス

一番これが重要かも。とにかくパフォーマンスが落ちればダメだねとなって、そもそも会社としてNGになりかねません。

会社も一例だけをみて良し悪しを判断しないと思いますが、リモートワークを導入して一発目からコケると風向きは悪くなります。

初回の事例が成功事例になるとその後、話がしやすくなります。そういう傾向はあると思っています。

1日1日の成果をより意識して取り組む必要があります。むしろ出社して作業する成果よりも上回った成果を意識します。(そのぐらいの意気込みでという意味)

そうすると、着地を必ず意識することになるのでアウトプット=成果に繋がってきます。

ハンディキャップ

たぶん人間の心理としてだと思うんですが、何かの課題に取り組んで結果的にうまく成果を出せなかった場合、

  • 出社してチャレンジした場合は努力過程が見える(毎日遅くまで取り組んでたとか)ので一生懸命頑張った結果だからしょうがないと思われがち。
  • リモートでチャレンジした場合は努力過程が基本的に見えない(出社してやるのと比べて)のでダメじゃんって思われがち。

となる事も自分は受け入れてやっています。

今の会社の人はちゃんと理解して評価を与えてくれると思いますが、一般的にはこーなんだろうなと思っています。

いつでも出社準備してる

業務時間内であれば、いつでも電話やslackで呼ばれても出社できる準備をしている。家が遠方なので呼ばれてから会社着くまでに1時間はかかりますけど。

あくまでもチームで成果をだすことが重要なので、会って話すべきだとなればいつでも出社します。その準備はいつもしています。

会話OKのコワーキング、会話NGのコワーキング

だいたいコワーキングスペースを利用していますが、会話OKとNGのところがあります。

1日の予定を考えて、今日はモクモクな日だなと思えば会話NGのコワーキングを選択しますし、オンラインMTGが何件かあったり、slackでの議論がありそうな日は直接話せる?ってなる確率が上がるので会話OKのコワーキングを選択しています。

taskを部類わけする

そんなに細かくわけないですが、ある程度雑音があってもこなせるtaskがっつり集中しないとこなせないtaskを分けています。

家で仕事する事も多いですが、家だと子供の声や家事の音などは避けられないのでそういう時はある程度雑音があってもこなせるtaskをしたりして効率的にできるようにしています

slackガイドライン

自分は最初思いつかなったですが、これがあると助かります。

基本的に非同期の仕事スタイルなのでその事を理解した上で必要最小限のslackガイドラインがあるとお互いにストレスなくコミュニケーションが取りやすくなります。

  • slackだけど同期的に会話したい場合のルールとか
  • いつまでに回答が欲しいと締め切りを設けたりとか
  • メンションは曜日・時間帯で送ってok or ng とか
    • @here,@channel,@個人名の使い分けとか
  • DMを基本的に禁止とか
  • リアクションは必ずするとか

...

テキストだけの情報に注意

一度自分は失敗しました。基本的にテキストだけのやりとりになるので、気軽にいった言葉が相手にはそう受け取られておらず、怒っているかのように受け取られたりしてしまう場合もあります。

受け取り方は様々あるので、誤解を与えない言葉をチョイスしたり、日本人得意のすいませんがという枕詞を活用すべきかもしれません。

ここからは半分冗談なんですがとかもありかもです。HRTを再度思い出しても良いかもしれません。

見える化

よく言われる事ですが、slackには最低限下記のように残しています

  1. 始業するときにslackにpost
  2. 休憩や食事でオフラインになる時に一言slackにpost
  3. 休憩から戻って仕事を再開するときに一言slackにpost
  4. 1日の仕事を終わるときに一言slackにpost

でよく見える化をすべきだよねってなってリモートワーカは見える化に務めるんですが、リモートワーカー側からすると社内の様子はわからない(ブーメラン)

リモートワークを許容するならば、社内も見える化に努めないといけないですよね。

リモートワークとかの文脈でなくても見える化は重要ですけどね。

同じ空間にいるからこそ伝わる情報がない事を理解する

リモートワークしてみてから気づきました。なんて言い表せば良いかわからないのですが、人間そういうのありますよね。

情報は取りに行くことが基本で、その取りに行く場所にもない情報というのがあったりします。単に情報を残すことを怠ったわけではなくて、、、でも存在する情報です。

この事を理解できれば、知らない社内情報に触れたときに消化しやすくなります。

どう消化するかは個人次第ですけど

ここ数ヶ月やってみての自分のパターン

だいたい下記のようにパターン化してきましたね。

  • 朝、子供を幼稚園に送り、9時から3,4時間はそのまま家で仕事
  • 雨が降ってたりすると家から徒歩で行けるコワーキングへいく
  • 天気がよければ、横浜あたりのコワーキングへいく
  • だいたいマンネリしてきたら新しいコワーキングを発掘する
  • 夜帰ってくるとだいたい子供が寝る時間になり静かになるので家で仕事再開する

定期的に場所を変えることで気分転換と集中できるように仕向けています

これはだけはやらないと決めてること

たまに見かけるのですが、体調が悪いので本日は自宅作業します。的なやつです。

自分的価値観はなしですね。出社もできないぐらい体調が悪いのならば、たとえ家で仕事ができたとしても質の高い仕事なんてできっこないです。

そんな状況下で仕事しても体調が回復した時に見たらダメなコードのパターンが多いからです。

そうすると無駄な時間を過ごしていただけということになります。

気持ちはわかりますよ。そんなにパワーを必要としない仕事とかは、確かにこなすことはできるとは思います。しかし、無しと決めています。

基本的には回復に努めて休むべきで思い切って休む or 早退にして復活してから巻き返した方が絶対に良いです。

仮にどうしてもやりたいのであれば体調が悪いのでという事など言わないでやればいい。

私には言い訳にしか聞こえない言葉なので。

ちょっと脱線

最終的にリモートワークを導入してみての成果という話になり、ひとくくりで評価され始めるとつらい。

体調が悪いので本日は自宅作業します=パフォーマンスが下がった状態の成果が混じってくるとノイズでしかない。

なにか新しい仕組みを導入しようとしての初回成功は重要というのは既に述べましたが、そこに関わってきます。

ひとくくりでなく、ちゃんと状況を把握しての評価であれば問題はありませんが、人間忙しくなると簡単な解を無意識に求めるものですからね...

最後

まぁつらつらと書いて来ましたが、職種やその人の性格的なものだったりも大いに関係するので一概に良いとは言えませんが、自分には合っているという評価です。

良い意味で自分へのプレッシャーにもなって頑張らねばという気持ちにもなりますし、隙間で家族の時間が取れたりできるのはありがたい。

golangのjson.Marshal/Unmarshalでちょっとハマった話

はじめに

Goのjson.Marshal/Unmarshalを使っててちょっと気になったところを残しておこうと思ったので書こうと思う

json - The Go Programming Language

出くわしたエラー

Unmarshalの方を使ってて下記のエラーに出くわした

json: cannot unmarshal number  NullInt64

まぁ対応している型ではないのでエラーなんですけど...

なんでNullInt64にマッピングしたかったのかというと

  • 対象のjsonデータをUnmarshalして他のtableにinsertしたかった。
  • そのinsert先のtableの該当のカラムの型が、sql.NullInt64の構造体フィールドだった為、NullInt64マッピングしたら楽じゃんと思った。

どう対処したか

json.RawMessageを使ってとかjson.Numberを使ってとか、mapでいったん受け取ってループでごにょごにょとか色々考えたんですが、 最終的にシンプルにjson.Marshal/Unmarshalになるべく任せて処理したかったのでnull.IntUnmarshalして sql.NullInt64のフィールドに詰め込み直すようにした

Unmarshalってそんな高速に動くイメージがなくて(本来はちゃんとベンチ取るべき)、何度も使うと性能のボトルネックになり得るだろうなと思ってあまり使い回す事をやめようと思ってた

おまけ

null.Intって中身を見たら

null.Int{
    NullInt64:sql.NullInt64{
        Int64:0, 
        Valid:false
    }
}

sql.NullInt64がネストされているだけっぽかった。

最終的に詰め込みたい先の構造体フィールド(sql.NullInt64)に詰め込みやすいなnull.Intはという事でこれを使ってる

golang1.9系から1.11.2にあげてみての感想

はじめに

業務で1.9系で使っていて1.11.2にあげた。

まぁリリースノートを見ればどの辺りがどう変わったかは大体わかるが、ある程度稼働しているコードのリポジトリでバージョンをあげる事で何となく得られる知見や理解が深まった部分があったので残しておく

Go 1.9 Release Notes - The Go Programming Language

Go 1.10 Release Notes - The Go Programming Language

Go 1.11 Release Notes - The Go Programming Language

test関連

vet

今まではcircleciのjobにvetやlintだけを流す静的チェック系のテストを定義していたが、test実行のまえにvetが-vet=off.にしない限り自動で動く。

なのでcircleciのjobを減して良いなと感じた

https://golang.org/doc/go1.10#test

test結果のキャッシュ

修正を加えていない部分に関しては毎回テストが走る事がなく、キャッシュからresultを返してくれる。

testは早ければ早いほど自分は嬉しいのですごくありがたい。

もちろん、キャッシュをさせずに一通り実施したい場合はオプションが存在する -count=1

ちなみにどこにキャッシュを作ってるのだろうかと気になって調べて見たら書きに書いてあった

https://github.com/golang/go/blob/master/src/os/file.go#L346-L382

macであれば$HOME/Library/Caches配下ですね

へーっと思ったら、その辺りを解説されているエンジニアさんがいらっしゃいました

How Go cache tests - Speaker Deck

先にこのスライドを目にしていれば...

bug fix系かな

1.

loggingにlogrusを使っているが、今ままで下記のようなコードがあった

GitHub - sirupsen/logrus: Structured, pluggable logging for Go.

if err != nil {
  Logger.Errorf("failed to xxx err:%#v, p_id:%d, u_id:%d", err, iv.PxxxID, iv.UxxxID)
}

iv.PxxxIDintだったが、iv.UxxxIDsql.NullInt64 でした。がエラーにはなっていませんでしたが、1.11.2にあげてからは怒られるようになりました。

正しく、iv.UxxxID.Int64としてあげなきゃいけないなと気づけてありがたい。

なので下記のように修正

if err != nil {
  Logger.Errorf("failed to xxx err:%#v, p_id:%d, u_id:%d", err, iv.PxxxID, iv.UxxxID.Int64)
}

2.

これまだ要調査という感じですが、下記のようなコードがあったとします。

main.go

func sample() string {
    var i float64
    i = 444
    fmt.Printf("%d",i)
    return "aaa"
}

func main() {
    fmt.Println(sample())
}

main_test.go

func TestMain_sampe(t *testing.T) {
    actual := sample()
    expected := "aaa"
    if actual != expected {
        t.Errorf("got: %v\nwant: %v", actual, expected)
    }
}

1.9.7の場合

  • go run main.goすると私の期待値通り%!d(float64=444)aaaと返ってくる
  • go test -run TestMain_sampeすると問題なくPASSする

1.11.2の場合

  • go run main.goすると私の期待値通り%!d(float64=444)aaaと返ってくる
  • go test -run TestMain_sampeすると./main.go:13: Printf format %d has arg i of wrong type float64という感じで怒られる

ちなみにbuildはどちらでも何もエラーを出力する事なくできました。

go rungo testをもうちょっと詳しくならないと理由がわからないですね。次の持ち越し課題にしておく。

goimports

indentがちゃんと揃うようになった

before

   i := &models.Ixxxx{
        PxxxxxxID:                i.PxxxxxxID,   
        TotalxxxxxxxxxPrice:      sql.NullInt64{Int64: i.TotalxxxxxxxPrice, Valid: true},  
        UxxxID:       sql.NullInt64{Int64: i.UxxxID, Valid: true}, 
        Type:         sql.NullInt64{Int64: i.Type, Valid: true},   
    }    

after

   i := &models.Ixxxx{
        PxxxxxxID:                i.PxxxxxxID,   
        TotalxxxxxxxxxPrice:      sql.NullInt64{Int64: i.TotalxxxxxxxPrice, Valid: true},  
        UxxxID:                   sql.NullInt64{Int64: i.UxxxID, Valid: true},
        Type:                     sql.NullInt64{Int64: i.Type, Valid: true},   
    }    

vetをかけた際に出ていたcannot load packageが解決された

以前はgo vet -v ./...をした時によく(can't find import: ~と出てたが、そのloadが解決されてちゃんと動くようになった

最後にちょっと気になっているところ

1.11でWebAssemblyvgoといったところが出ていた。まだ全然試せていないが、vendorも時期に消し去る時がくるようだなーと。

golangのxoを導入して困った点、どう対応しているかを書いておく

はじめに

下記の記事でxoを導入してどうやって管理運用しているかについて書いたが、すべてうまくいっているわけではない。

すんなりハマらず、色々と困った点はあった。

このすんなりハマらなかった点やそれに対してどうやって対応しているか等を書いておく。他の人も参考になったら嬉しく思います。

tsuyoshi-nakamura.hatenablog.com

前提的な所

既存に関係なく、まったくの新規の開発ではなく、DB(mysql)は既存で運用されているtableを参照するという制約があった。

まぁこれが原因でうまくハマらない点が出てきたのだが、マイクロサービスへ移行する過渡期などは致し方ない所があると個人的には思います。

困った点

困った点1 : indexの貼り方がちょっと変な場合

歴史的な背景はおいておくとして下記のようなindexが貼られてた場合です

+----------+------------+------------------------+--------------+-----------------+
| Table    | Non_unique | Key_name               | Seq_in_index | Column_name     |
+----------+------------+------------------------+--------------+-----------------+
| table_a  |          1 | idx_aaa                |            1 | column_a        |
| table_a  |          1 | idx_bbb                |            1 | column_a        |

あるテーブルにおいて、同じカラムに対して別名のindexを張っていた場合、xoからmodelを生成すると下記のfunctionが2つ生成されます

func TableAByColumnA(db XODB,~~

まぁindexをkeyにしてgetするfunctionを生成しているので当たり前といえばそうですが、goのファイルとしてはduplicate functionとして当然怒られます。

どう対応したか

xxx.xo.goに対して手を入れたく無いが、入れざるを得ない。1つfunctionを消した。そしてREADMEファイルに例外項目を追記

一番良いのは該当のtableのindexの見直しである事は間違い無いが、他のリポジトリと共有であるtableという前提があるのですぐには改善されないと言う事でこの対応をした

困った点2 : カラムに一意制約が無いindexだとreturnの値は配列で返ってくる

単一のレコードが取得できる条件がない場合は基本的に下記のように配列でreturnされます

func TablesByStatus(db XODB, status string) ([]*Tables, error) {

[]*Tablesの部分です

逆に単一のreturn値になる場合は色々とパターンはありそうですが、下記あたりがあります。

  • unique indexが設定された場合、一意性が保証されるので単一で戻ってくる
  • foreign key設定されていてその先のtableのkeyがuniqueなものになっている場合は単一で戻ってくる
  • AUTO_INCREMENT設定されているPrimary keyであれば、配列のreturnにはならずに単一で戻ってくる

サービスによってinsert、updateコストがネックになる場合もあるので共有テーブルであるとなおさらindex周りは変更しにくい...

どう対応したか

下記の記事でファイル構成について触れていますが、xxx.xo.goの同ディレクトリにxxx.goを作って自動生成メソッドでは事足りないメソッドを生やしています。

golangのxoを導入を決めてファイルの運用方法がいい感じになってきたので書いておく - Tsuyoshin blog

そこに単一で取得できるメソッドを生やしています。ただし、間違って同じキーで複数レコードが存在する様になると色々とエラーになるので気おつけた方がいいです。

カラムに一意制約を付けれるのがベストだとは思います

困った点3 : 複合indexが存在する場合

下記のような複合indexが貼られていた場合

CREATE TABLE `tables` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `xxx_id` int(11) NOT NULL,
...
...
...

  PRIMARY KEY (`id`,`xxx_id`),

xxx_idを元にfuncは作られますが、idを元にしたfuncが生成されない

これは作られる

func TablesByXxxID(db XODB, ~~

これは作られない

func TablesByID(db XODB, ~~

どう対応したか

下記の記事でファイル構成について触れていますが、xxx.xo.goの同ディレクトリにxxx.goを作って自動生成メソッドでは事足りないメソッドを生やしています。

golangのxoを導入を決めてファイルの運用方法がいい感じになってきたので書いておく - Tsuyoshin blog

そのxxx.gofunc TablesByID(db XODB, ~~を生やして対応している

困った点4 : mysql予約語がカラムにある場合

具体的にはorderというカラムがあった。その事で何が困るかで言うと

insert functionを作られるtemplateを見ると下記のようになっている

https://github.com/xo/xo/blob/master/templates/mysql.type.go.tpl#L40-L81

{{ if .Table.ManualPk  }}
    // sql insert query, primary key must be provided
    const sqlstr = `INSERT INTO {{ $table }} (` +
        `{{ colnames .Fields }}` +
        `) VALUES (` +
        `{{ colvals .Fields }}` +
        `)`

...

これで作られるものは下記

   const sqlstr = `INSERT INTO table (` +
        `カラム名, ~~~` +
        `) VALUES (` +
        `?, ~~~` +
        `)`

Golang上、バッククォートが入っているが、SQL自体にはバッククォートが含まれていない...

なのでmysql予約語が入ってくるとSQLエラーが出力されてしまう

どう対応したか

tableを他のシステムと共有の前提でのカラム名変更はindexの変更よりはるかに難易度が高い。。。

なのでtemplateを修正して出力されるSQLの形式を変えた。カラムにバッククォートを入れる事がtemplate修正だけではできなかったのでtable名.column名となるように変更をした

xo自体のコードに変更を加えればできるが、今後のメンテを考えて独自に変更を加える事を回避した

before

const sqlstr = `INSERT INTO {{ $table }} (` +
`{{ colnames .Fields .PrimaryKey.Name }}` +

after

const sqlstr = `INSERT INTO {{ $table }} (` +
`{{ colprefixnames .Fields $table .PrimaryKey.Name}}` +

変更にあたり、templateで使えるfunctionを見ながらやると理解が早かった

https://github.com/xo/xo/blob/master/internal/funcs.go

mysql8では予約語が増えているらしいのでさらに注意が必要ですね

困った点では無いけどおまけ

困ってはいないが、mysql-viewテーブルが存在していた場合、funcは一切生成されず、mysql-viewテーブルのカラムのstructだけが生成された。まぁこれはこれで良い。ここにinsertとかupdateとかあると逆にややこしくなるし

まとめ

  • DBも新規で切り出せて開発ができるなら本来的な対応ができると思うが、そういう案件ばかりでは無いので個人的には良いTipsになったかと思う。
  • 問題が発生triggerでxoの中身を読むキッカケになってくれてxoについて理解が進んだり設計的な所を知れる事は有意義ですね
    • 漠然と読むより全然あたまに入る!!