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