nakamura244 blog

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

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について理解が進んだり設計的な所を知れる事は有意義ですね
    • 漠然と読むより全然あたまに入る!!