ERD Toolの紹介

# 目的
ERD ToolはVSコード拡張でjsonでERDを設計するツールです。以前は有償のERD作図ツールを使っていたのですが、コロナ下でリモートとなりツールを使える人が限られて困る状況になったので、誰でも使えるように作成しました。作図や図示の機能はあきらめました。

```json:sample.erd.json
{
  "meta": {
    "version": "1.0.0"
  },
  "includes": [
    "simple_kbn.erd.json"
  ],
  "templates": {
    "kbn": [
      {
        "template": "kbn_template1.ejs",
        "file": "aaaa.txt"
      }
    ],
    "table": [
      {
        "template": "table_template.ejs",
        "file": "tables_1/sample_${table}.txt"
      },
      {
        "template": "table_template.ejs",
        "file": "tables_3/${index}_sample_${table}.txt"
      },
      {
        "template": "table_template.ejs",
        "dir": "tables_2",
        "extension": "txt"
      }
    ],
    "error": [
      {
        "template": "error_template.ejs",
        "file": "ccc.txt"
      }
    ]
  },
  "kbns": [
    {
      "lname": "バッチ",
      "pname": "batch",
      "code": "001",
      "values": [
        {
          "lname": "日次バッチ",
          "pname": "daily_batch",
          "code": "01"
        }
      ]
    }
  ],
  "errors": [
    {
      "lname": "共通",
      "pname": "common",
      "code": "001",
      "values": [
        {
          "lname": "成功",
          "pname": "success",
          "code": "01",
          "db_code": "U0000",
          "message": "正常に終了しました。",
          "warning": false
        }
      ]
    }
  ],
  "domains": [
    {
      "lname": "ID",
      "pname": "id",
      "type": "BIGINT",
      "default": "0"
    },
    {
      "lname": "名前",
      "as": "名",
      "pname": "nm",
      "type": "TEXT",
      "default": "''"
    },
    {
      "lname": "日時",
      "pname": "at",
      "type": "TIMESTAMPTZ",
      "default": "NOW()"
    },
    {
      "lname": "区分",
      "pname": "kbn",
      "type": "TEXT",
      "default": "''"
    }
  ],
  "tables": [
    {
      "lname": "ユーザー",
      "pname": "users",
      "columns": [
        {
          "domain": "ID",
          "pk": true
        },
        {
          "domain": "区分",
          "kbn": "バッチ"
        },
        {
          "domain": "区分",
          "kbn": "リポート"
        }
      ],
      "metadata": {
        "useGarbage": false
      }
    },
    {
      "lname": "キャンペーン",
      "pname": "campaigns",
      "columns": [
        {
          "domain": "ID",
          "pk": true
        },
        {
          "domain": "ID",
          "fk": [
            {
              "table": "ユーザー"
            }
          ]
        },
        {
          "lname": "アカウント",
          "pname": "account",
          "domain": "名前"
        },
        {
          "lname": "アカウント最近ログイン",
          "pname": "account_last_login",
          "domain": "日時"
        },
        {
          "lname": "使用期間",
          "pname": "usage_period",
          "domain": "日時",
          "type": "TIMETZ"
        }
      ]
    },
    {
      "lname": "返信候補",
      "pname": "response_candidate",
      "columns": [
        {
          "domain": "ID",
          "pk": true
        },
        {
          "domain": "ID",
          "fk": {
            "table": "ユーザー"
          },
          "lname": "ユーザー"
        }
      ],
      "fks": [
        {
          "columns": [ "ユーザーID" ],
          "table": "ユーザー",
          "foreignColumns": [ "ID" ]
        }
      ]
    }
  ]
}
```

# 機能
* ドメインサポート
* コード生成
* 簡易プレビュー
* 区分値管理
* エラーコード管理
* metadata

## ドメインサポート
ドメインとはカラムの接尾語に一定のルールを付けて型を固定する機能です。例えば「at」は時間を表していて型はTIMESTAMPTZ型になります。カラムの例としては「created_at」で作成時間を表します。

有償ツールから移行するときにどうしても外せなかった機能がこれです。無償ツールも探したんですが見つからずに自作したのがこの機能のためです。

この機能のおかげで名前を見れば、型や使い方が直感的にわかって開発者がカラム名を見て混乱しないようになっています。

## コード生成
[ejsというテンプレートエンジン](https://www.npmjs.com/package/ejs)を使って、任意のコードが生成できます。例えば以下のようなものを生成しています。
* CREATE TABLEのsql
* 一覧を取得するストアードプロシージャ
* 登録・更新を行うストアードプロシージャ
* ストアードプロシージャを呼び出すモデルクラス

テーブルに対する一通りの操作は、呼び出す以外はほとんどコーディングが完了しています。

```sql:table_sql.ejs
<%
  const getType = (column) => {
    if (column.type == 'BIGINT' && column.pk && column.pname == 'id') {
      return 'BIGSERIAL';
    }
    return column.type;
  };
  const columns = table.columns.map((column) => {
    let col_text = `${column.pname} ${getType(column)}`;
    if (column.notNull) {
      col_text += ' NOT NULL';
    }
    if (column.default && (!column.pk || column.pname != 'id')) {
      col_text += ` DEFAULT ${column.default}`
    }
    col_text += ` -- ${column.lname}`;
    return col_text;
  }).join('\n  ,');
  const pkeys = table.columns.filter((column) => {
    return column.pk;
  }).map((column) => {
    return column.pname;
  }).join(', ');
  const fks = (table.fks ? table.fks : []).map((fk) => {
    let columns = fk.columns.map((column) => column.pname).join(', ');
    let foreignColumns = fk.foreignColumns.map((column) => column.pname).join(', ');
    let res = `\n  ,FOREIGN KEY (${columns}) REFERENCES ${fk.table.pname}(${foreignColumns})`;
    return res;
  }).join('');
%>DROP TABLE IF EXISTS source.<%= table.pname %> CASCADE;

CREATE TABLE source.<%= table.pname %> (
  <%- columns %>
  ,created_uuid UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'
  ,updated_uuid UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'
  ,deleted_uuid UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'
  ,created_at TIMESTAMPTZ NOT NULL
  ,updated_at TIMESTAMPTZ NOT NULL
  ,deleted_at TIMESTAMPTZ
  ,created_pg TEXT NOT NULL DEFAULT ''
  ,updated_pg TEXT NOT NULL DEFAULT ''
  ,deleted_pg TEXT NOT NULL DEFAULT ''
  ,bk TEXT
  ,PRIMARY KEY(<%- pkeys %>)
);

CREATE TABLE public.<%= table.pname %> (
  LIKE source.<%= table.pname %> INCLUDING ALL
  <%- fks %>
) INHERITS (source.<%= table.pname %>);


CREATE TABLE garbage.<%= table.pname %> (
  LIKE source.<%= table.pname %> INCLUDING ALL
) INHERITS (source.<%= table.pname %>);
```

## 簡易プレビュー
![スクリーンショット 2022-09-08 133233.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148247/969a6e40-3768-36f6-c443-0334f5590250.png)

本来ならここでERDを出さないと名前と合っていないツールなので、いつかはだしたいです。

## 区分値管理
ERDの本来の趣旨から離れますが、区分値も管理しています。カラムに出てきますし、ストアードプロシージャでも参照したいです。一元管理になるので、コードの重複なのが発生しないのが良いです。

## エラーコード管理
上記と同様

## metadata
生成するストアードプロシージャやモデルクラスのカスタマイズにmetadataという属性で任意にjsonが与えられます。ejsで活用することで色々コードが生成できます。order byやon conflictなどを調整したことがあります。

# まとめ
テーブルに近い処理はこのツールにより、開発が楽になりました。
簡単な管理画面程度ならフロントとバックエンドを同時に自動生成して動かすことも可能になっています。
複雑な処理でも、生成された一覧や登録・更新のストアードプロシージャを使うことで省力化できています。