PostgreSQLのjsonb型の使いどころについて考えてみた

## はじめに

先日、個人開発でjsonb型を使ってみたところなかなか便利だったので、
DB設計のアンチパターンと言わているjsonb型の使いどころについて自分なりに考えてみました。

作ったものはタスク管理ボード（Trello風？）で、
タスクをドラッグ&ドロップで移動できるのが特徴です。
フロントはNuxt.js、バックエンドはSpring Bootを使ってます。
（ドラッグ&ドロップはVue.Draggableというライブラリを使うと簡単に実現できます。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597765/6fa23f8d-e56a-52ec-132c-210632d9efe9.png)

## jsonb型をどう使ったか

まずはjsonb型をどのように使ったかご紹介させていただきます。

**テーブル定義**

```sql:board.sql
CREATE TABLE board (
  id serial,
  category_id integer,
  title varchar(255),
  date date,
  start_time time,
  finish_time time,
  content jsonb,
  supplement text
  -- 削除フラグ とかは割愛
);
```

contentカラムにjsonbを使いました。
中身はこんな感じです。

```json
{
  "groups": [
    {
      "name": "MTG",
      "tasks": [
        {
          "name": "朝会",
          "status": false
        },
        {
          "name": "進捗報告会",
          "status": false
        },
        {
          "name": "実装相談会",
          "status": false
        }
      ]
    },
    {
      "name": "PJ1",
      "tasks": [
        {
          "name": "要望整理",
          "status": true
        },
        {
          "name": "バッチの実装",
          "status": true
        }
      ]
    }
  ]
}
```

タスクに関する情報をすべてこのカラムに格納しました。

### よかった点

よかった点としては、データ受け渡し時の処理がとてもシンプルになったことです。

フロントエンドで利用するオブジェクトはjsonb型のカラムにそのまま格納しておけるので、
バックエンドから受け取ったデータをフロントで利用する際は、`JSON.parse()`するだけでOKになりました。

また、バックエンドにリクエストする際も整形処理を加えずに渡せます。

### 辛かった点

逆に開発していて辛いな。と感じた点は、
データを受け取ったあとのフロントの処理が複雑になることです。

jsonb型はJSON形式にさえなっていれば好き勝手に登録できてしまうので、
たとえば、機能追加でデータ構造を変えたくなった場合でも、良くも悪くもDBのカラム追加が不要で、
フロントでオブジェクトに必要なプロパティを追加するだけで完結できてしまいます。

その結果、既存データとの差異が生まれやすく、`undefined`を考慮する必要が出たり、
画面上でプロパティを追加した際に、再レンダーの監視対象にするための考慮が必要だったり、
フロントの処理が複雑になります。
(Vueの場合、オブジェクトにプロパティを新たに追加する際は、再レンダーが走るように代入ではなく`push()`を使う必要があります)

今回、データ構造を変更していた際に、データの差異による思わぬバグに遭遇したので、
jsonb型で扱うデータはカラム追加以上に、なるべくデータ構造を変えないよう慎重に設計する必要があると感じました。

jsonb型を使った際に感じたメリデメは以上です。

## jsonb型を使わずに実装する

ここからは、jsonb型の使いどころについて考える際の比較材料として、
今回の実装をjsonb型を使わずに実装した場合もまとめてみます。

jsonb型に格納した情報の正規化をします。

**テーブル定義**

```sql:
CREATE TABLE board (
  id serial,
  category_id integer,
  title varchar(255),
  date date,
  start_time time,
  finish_time time,
  supplement text
);

CREATE TABLE task_group (
  id serial,
  board_id integer,
  pos integer,
  name varchar(255)
);

CREATE TABLE task (
  id serial,
  task_group_id integer,
  name varchar(255),
  pos integer,
  isStatus boolean
);
```

ポイントは、タスクのグループやタスクをボードのどこに配置するのか？
わかるように並び順（posカラム）を保存する必要が出てきた点です。

並び順の保存アプローチは色々ありますが、今回は以下の記事を参考に、

[並べ替えできるデータをデータベースに保存する方法](https://zenn.dev/itte/articles/e97002637cd3a6)

> 方法1 レコードに自身の順番を持たせる
> おそらく最も広く用いられている方法かと思います。

定番の方法にしました。

### デメリット

jsonb型を使わずに実装すると、全体的に処理の内容が複雑になります。

フロントエンド側では、ドラッグ&ドロップでタスクの順番を入れ替えたり、
タスクの新規追加・削除時に、並び順(posカラム)の値を更新する必要が出てきます。

バックエンド側では、特に登録・更新・削除の際、複数クエリを発火させる必要が出てきます。

その他、フロント〜バックエンドでのデータ受け渡し時の整形処理も必要になります。

### メリット

一方、jsonb型に比べて実装は複雑になりますが、
データの取り扱いはしやすいため、追加開発時に生じる既存データとの差異は埋めやすく、
思わぬバグに遭遇する機会は減りそうです。

## jsonb型の使いどころ

ここまでで、jsonb型を使った場合の実装と、
比較材料として、jsonb型を使わなかった場合の実装についてまとめてみました。

jsonb型は正規化されたデータに比べて、データの取り扱いが難しくはなりますが、
今回のように並び順を入れ替えるような実装では、
実装工数が大幅に削減できたので、
ここらへんのバランスを見て、jsonb型を導入するか検討するのが良さそうです。

[PostgreSQLのアンチパターン : 何でもかんでもjsonに入れる](https://yakst.com/ja/posts/2445)
> PostgreSQLでデータのモデリングを行う際に最初に検討すべきものではありません。 なぜなら、データを呼び出したり操作したりするのが難しくなってしまうためです。

また、上記の記事にもあるように、まずは正規化されたテーブル設計をしてから、jsonb型を検討するのが良さそうです。
（あとは複数の機能から頻繁に参照されるような情報の場合は、データの抽出が大変な分、正規化した方がよいかもしれません。）

## まとめ
アンチパターンと言われるjsonb型ですが、個人的には要所要所で今後も開発に取り入れていきたいと思いました。

jsonb型は特にJSと相性が良いので、今回のドラッグ&ドロップ機能だったり、
フロントでリッチなUIを作りたくなった場合は特に恩恵を受けることができると思います。

今後の展望としては、NoSQLを試したりもしたいですが、
まずは、GraphQLを使ってjsonb型をよりうまく活用できる方法がないか、模索していきたいと思っています。
（pathで細かく取得したい値を指定できるので、データの扱いが楽になり、実装の幅も広がりそうです。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/597765/68ac8526-8095-5f17-c0a0-237396a93d5c.png)

以上、最後までご覧いただきありがとうございました！

## 参考
[9.15. JSON関数と演算子](https://www.postgresql.jp/document/9.5/html/functions-json.html)
この記事では割愛しましたが、varchar型からjsonb型にカラム変更したりしたので、
JSON関数の情報は大変参考になりました。

[【PostgreSQL】 jsonb型カラムをSQLで検索・集計したい](https://blog.serverworks.co.jp/postgres_jsonb_search)
[PostgreSQL 14からJSONデータを辞書風にアクセスできるようになりました](https://dev.classmethod.jp/articles/postgresql-14-supports-json-subscripting/)
[PostgreSQL の JSONB 型の紹介とメリット](https://www.bokukoko.info/entry/2017/07/02/211449)