PrismaORMで複数のデータベーススキーマを動的に管理する

# なにこれ
とあるチーム開発でNext.jsを採用し、ORMとして初めてPrismaを触ってみました。そこで、RDB特有のデータベーススキーマの構造を利用して、アカウントごとに同じテーブル構造のスキーマを作成し、テーブル群をpublicスキーマと分割して扱うという設計に走りました。
要するにアプリケーションの方でアカウントが登録されると、スキーマのテンプレートをもとにアカウントidと同じ名前のデータベーススキーマが作成されるような流れ。

![スクリーンショット 2023-12-13 3.48.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3092163/fa246b34-e13a-a5ee-240d-73f2065ca1a7.png)

しかし、PrismaORMは基本的に`prisma/schema.prisma`に定義されているmodel群に対してのみ扱えるClientインスタンスができる仕組みになっています。特に指定がない場合、定義されたmodelはすべてpublicスキーマに作成されます。
```javascript:client.js
import { PrismaClient } from '@prisma/client'

// 普通にimportすると、schema.prismaに定義したmodelだけ扱えるclientになる
const prisma = new PrismaClient()

// prismaインスタンスに任意の.tablename変数でtableにアクセスする
await prisma.tablename.create({
    data: {
        ...
    },
})
```

一応、事前に指定されたデータベーススキーマにmodelを定義したい場合、[multiple database schemas](https://www.prisma.io/docs/orm/prisma-schema/data-model/multi-schema)という手法で定義することはできます。

```diff_javascript:schema.prisma
generator client {
    provider        = "prisma-client-js"
+   previewFeatures = ["multiSchema"]
}

datasource db {
    provider = "postgresql"
    url      = env("DATABASE_URL")
+   schemas  = ["base", "transactional"] //　あらかじめ作っておくことを想定
}
```
しかしこれはテーブルやスキーマが新しく作成される場合では不適です。`schema.prisma`を動的に変更して更新する必要がありますし、できたとしても作成されるスキーマにテーブルが5個もあったら、それだけでmodelが5つも追記されることになるので、`schema.prisma`が肥大化していきます。やはりclientごとに`.prisma`ファイルは分割するべきでしょう。

### まとめると、以下の問題を解決したい
* プログラムでデータベーススキーマを作成したい
* アクセスするデータベーススキーマを動的に切り替えたい
* 複数のclientを扱いたい


# 複数のclientを扱う
これを前提としないと残り２つがうまく説明できないので、こちらから見ていきます。調べてみると、優秀な著者様がいらっしゃったので、紹介と引用をさせていただきます。

https://zenn.dev/frog/articles/073af49880452f

https://px-wing.hatenablog.com/entry/2022/03/04/074543

こちらはどちらもデータベーススキーマではなく、異なるデータベースごとにClientを作成する手順を紹介しています。しかし、`schema.prisma`をデータベースごとに作ってそれぞれにインスタンスを作る手法はとても参考になりました。本当にありがとうございます。（届かぬ声）

### .prismaの定義
例えば、userAとuserBというスキーマを作り、それぞれにClientインスタンスを作成したい場合、`.prisma`ファイルの先頭を次のように定義します。

```diff_javascript:userA.prisma
generator client {
  provider = "prisma-client-js"
+  output   = "../generated/userA"
}

datasource db {
  provider = "postgresql"
+  url      = "postgres://...?schema=userA"
}
```
```diff_javascript:userB.prisma
generator client {
  provider = "prisma-client-js"
+  output   = "../generated/userB"
}

datasource db {
  provider = "postgresql"
+  url      = "postgres://...?schema=userB"
}
```

ポイントとなるのは、`output`パラメータと、`url`の末尾です。
* `output`：Clientインスタンスのimport先となるディレクトリーを設定できます。
* `url`：databaseのurlの末尾にクエリパラメータとして`?schema=`を入れることで、指定したデータベーススキーマ内を指定します。

### prismaコマンド
`.prisma`を書き終えたあと、次のコマンドを実行します。
```terminal:terminal
$ npx prisma migrate dev --name userA --schema prisma/userA.prisma
$ npx prisma generate --schema prisma/userA.prisma

$ npx prisma migrate dev --name userB --schema prisma/userB.prisma
$ npx prisma generate --schema prisma/userB.prisma
```
* `migrate dev`はデータベースに`.prisma`で定義したmodelを適応させます。
* `generate`は、`.prisma`で定義したモデルのインスタンスを`output`のディレクトリに作成します。

ここでえらいのは、コマンドに`--schema`パラメータを設定できる点です。実はこれ、なくても動くのですが、デフォルトは`prisma/schema.prisma`になっています。つまりこのパラメータを使うことで、任意のファイル名の`.prisma`を`generate`できるわけです。大感謝。

### Clientのexport
`migrations`ディレクトリなどが作成されますが、大体見るとこんな感じになっているのではないでしょうか。

```terminal:terminal
$ tree prisma
prisma
├── generated
│   ├── userA
│   └── userB
├── userA.prisma
└── userB.prisma
```

ここで、`client`ディレクトリを作って、`userA.js`、`userB.js`を作ります。
```terminal:terminal
$ tree prisma
prisma
├── client
│   ├── userA.js
│   └── userB.js
├── generated
│   ├── userA
│   └── userB
├── userA.prisma
└── userB.prisma
```

この`.js`で、`generated`からそれぞれのデータベーススキーマに対してclientインスタンスを作成します。

```javascript:userA.js
import {PrismaClient as UserA} from '../generated/userA';

const user_a = new UserA({
    datasources:{
        db:{
            url: "postgres://...?schema=userA"
            }
    }});

export default user_a;
```

```javascript:userB.js
import {PrismaClient as UserB} from '../generated/userA';

const user_b = new UserB({
    datasources:{
        db:{
            url: "postgres://...?schema=userB"
            }
    }});

export default user_b;
```
これらひとつのjsにまとめたかったのですが、ひとつの`.js`にある複数の`export`を動的に切り替えることができなかったので、`.js`のパスによって動的にimport先を切り替えるようにします。

# データベーススキーマを動的に切り替える
`require`でimportします。promiseが返されるので、非同期で解きます。`.default`で`export default`されているオブジェクトを参照しています。この場合、`importClient()`関数の引数によって動的にimport先を切り替えることが可能になっています。

```javascript:crud.js
async function importClient(user){
    const client = require(`prisma/clients/${user}.js`);
    return client;
};

const userSchema = await importClient("userA");
const tableInst = userSchema.default.tablename;

await tableInst.create({
    data: {
        ...
    },
})
```
これで複数のclientを扱う流れを理解できました！

# プログラムでデータベーススキーマを作成する
~~実はここが本題でした。~~ 今回の要件では、アカウントが作られるたびに同じテーブルがセットされている新しいスキーマが作成される設計になっています。ここまで期待をさせていてとても申し訳ない回答になるかと思いますが、ゴリ押しでやります。というのも、上記のclientを作成する流れをそのままプログラムでやるだけです。

### .prismaを生成する
まずスキーマ構造のテンプレートとなる`template.prisma`を作っておきます。（[.prismaの定義](/#.prismaの定義)）

```javascript:template.prisma
generator client {
  provider = "prisma-client-js"
  output   = "../generated/ACCOUNTID"
}

datasource db {
  provider = "postgresql"
  url      = "postgres://...?schema=ACCOUNTID"
}

model Table1 {
...
```
これを`Fs`を使って書き換えて複製します。
```javascript:create_schema.js
import Fs from 'fs';

const createSchema = (accountId) => { 
    Fs.readFile("./prisma/template.prisma", 'utf8', (err, data) => {
        // ACCOUNTIDをaccountId引数に書き換える
        const modifiedData = data.replace(/ACCOUNTID/g, accountId);
        
        // 変更した内容で.prismaを複製する
        Fs.writeFile(`./prisma/${accountId}.prisma`, modifiedData, 'utf8', (err) => {
            if (err) {
                console.error(err);
            } else {
                console.log(`${accountId}.prisma generated`);
            }
        });
    });
};
```

### prismaコマンドを実行する
`exec`を使ってコマンドを実行させます。（[prismaコマンド](/#prismaコマンド)）
```javascript:create_schema.js
import { exec } from 'child_process';

...
// データベースを更新
exec(`npx prisma migrate dev --name ${accountId} --schema prisma/${accountId}.prisma`, 
        (err, stdout, stderr) => {
    if (err) {
        console.error(err)
    }
        console.log("migrate done")
    }
);

// clientを作成する
exec(`npx prisma generate --schema prisma/${accountId}.prisma`, 
        (err, stdout, stderr) => {
    if (err) {
        console.error(err)
    }
        console.log("client generate done")
    }
);
```

### Clientのexportを定義する
`.js`ファイルを作って、そこにexport元となるclientを定義します。これも`Fs`を使って`.js`ファイルを作ります。（[Clientのexport](/#Clientのexport)）
```javascript:create_schema.js
...
// clientを作成する
const clientcontent =  `import {PrismaClient as ${accountId}} from '../generated/${accountId}';\n
                        const ${accountId} = new ${accountId}({datasources:{db:{url:"postgres://...?schema="+'${accountId}'}}});\n
                        export default ${accountId};\n`;

Fs.writeFile(`./prisma/client/${accountId}.js`, clientcontent, 'utf8', (err) => {
    if (err) {
        console.error(err);
    } else {
        console.log('clients created');
    }
});
```

これで以下の要件を実現することができました。
* 動的なデータベーススキーマの作成
* 動的なclientインスタンスをの定義
* 動的なclientインスタンスの切り替え