`Node-Postgres` を使ったときに、試行錯誤してできたPostgresクラス

## はじめに
　この記事は自分が小規模アプリ開発でバックエンドを１から担当したときに、失敗を経て最終的に出来上がった`Node-Postgres`のクラスを紹介するものです。一応`Node-Postgres`は2019年8月の時のものを使用しています。改善点などありましたら是非是非教えてください！

## `Node-Postgres`のベストプラクティスが中々見つからない
　どのサイトもチュートリアル的なものだったり、`Pool`を使わず`Client`をそのままインスタンス化して使用している古いものばかりで、ベストプラクティスを探すのに時間がかかりました。(最近はORMが主流だからなんですかねやっぱり)
　結局見つからなかったのでドキュメント、GitHubであがっていた質問、テストで出てきたバグ等を参考にして更新していきました。

## まずはコード
長々と説明するのもあれなのでまずはソースコード出します。こんな感じになりました。

``` js:postgres.js
const { Pool } = require('pg');
const connectionString = 'postgresql://postgres:postgres@localhost:5432/hogehoge_db';
const pool = new Pool({ connectionString });

/**
 * Postgresクラス
 */
class Postgres {

    /**
     * Poolからclientを取得
     * @return {Promise<void>}
     */
    async init() {
        this.client = await pool.connect();
    }

    /**
     * SQLを実行
     * @param query
     * @param params
     * @return {Promise<*>}
     */
    async execute(query, params = []) {
        return (await this.client.query(query, params)).rows;
    }

    /**
     * 取得したクライアントを解放してPoolに戻す
     * @return {Promise<void>}
     */
    async release() {
        await this.client.release(true);
    }

    /**
     * Transaction Begin
     * @return {Promise<void>}
     */
    async begin() {
        await this.client.query('BEGIN');
    }

    /**
     * Transaction Commit
     * @return {Promise<void>}
     */
    async commit() {
        await this.client.query('COMMIT');
    }

    /**
     * Transaction Rollback
     * @return {Promise<void>}
     */
    async rollback() {
        await this.client.query('ROLLBACK');
    }
}

/**
 * Postgresのインスタンスを返却
 * @return {Promise<Postgres>}
 */
const getClient = async () => {
    const postgres = new Postgres();
    await postgres.init();
    return postgres;
};

module.exports.getPostgresClient = getClient;
```

## `Pool`は一つ生成する
``` js:postgres.js
const pool = new Pool({ connectionString });
```
はクラスの外でインスタンス化しました。理由は再度使う`Client`を保存する(？)コネクションプールなので、複数も必要ないからです。

## `Client`は`Pool`経由で生成する
``` js:postgres.js
async init() {
    this.client = await pool.connect();
}
```
前の`Node-Postgres`では`Client`を直接インスタンス化するようになっていましたが、今は`Pool.connect()`を使って生成することを公式でも推奨しています。
[Node-Postgres Pooling](https://node-postgres.com/features/pooling)
ちなみに`constructor`ではない理由は、`async await`が使えなかったからです。これについては後述します。

## 使用するときは直接このクラスを`new`しない
``` js:postgres.js
/**
 * Postgresのインスタンスを返却
 * @return {Promise<Postgres>}
 */
const getClient = async () => {
    const postgres = new Postgres();
    await postgres.init();
    return postgres;
};

module.exports.getPostgresClient = getClient;
```
ここで`getClient`メソッドを、(この名前が適切かは別として…)`getPostgresClient`と言う名前で`exports`しています。
`constructor`では`async await`が使えないため、メソッド内で`new`して`init()`メソッドを呼んでもらうことで確実に`Client`インスタンスを生成するようにしています。

## 使う時
``` js:SomeFunc.js
const { getPostgresClient } = require('postgres');

async function someFunc() {
    const db = await getPostgresClient();
    try {
        const sql = `UPDATE ~`;
        const params = ['1', 'name'];
        
        await db.begin();
        await db.execute(sql, params);
        await db.commit();
        
    } catch (e) {
        await db.rollback();
        throw e;
    } finally {
        await db.release();
    }
}
```

## 失敗履歴
- `Postgres class`のインスタンス生成時の度に、`new Pool()`を行っていた。
    - つまり`constructor`の中で`new Pool()`呼んでました。
    - コネクションプールのメリットを完全に潰してました。当時の自分は何を考えていたのだろう…。
- シングルトンパターンでPoolを管理していた。
    - 短時間で10リクエスト送ると`client.release()`でエラーが多発し処理が止まりました。
    - 表示されたエラーは`Release called on client which has already been released to the pool.`
        - 他で既に`release`したclientを再度リリースしていたらしいです。`Client`オブジェクトまで全てのリクエストで共有されてたんですね。
２つめの失敗のコードの一部がこちら

``` js:postgres.js
/**
 * Poolから接続
 * @return {Promise<void>}
 */
static async connect() {
    try {
        // poolが無いなら生成
        if (typeof this.pool === 'undefined') {
            this.pool = new Pool({
                connectionString: process.env.DATABASE_URL || 'postgresql://postgres:postgres@localhost:5432/hogehoge_db'
            });
            Log.info('Initial Postgres instantiation');
        }

        // poolから接続できるクライアントを取得
        this.client = await this.pool.connect();
    } catch (e) {
        throw e;
    }
}
```
ちなみにその他のメソッドは全て`static`メソッドでした。
(ちなみにこの時はまだ`getClient`メソッドはありませんでした。)
なぜあのエラーが出てしまうのかは、イベントループを勉強すればわかりそうな気がしているのでそのあたり探ってみます。

## 参考サイト
- [Node-Postgres](https://node-postgres.com/)

他にもあったんですが保存するのを忘れてました