パラメータの差分を DB に持つ
本体の標準機能ではありません
このページは、パラメータを差分の JSON だけで持つ の案 A(C# の既定値 + 変更した項目だけの JSON)を前提に、その JSON を DB にも持てるようにする設計メモです。
複数の Web サーバーへの即時反映、画面からの設定変更、テナントごとのパラメータ、DB のバックアップへのパラメータの同梱、変更履歴の追跡を目的にします。
前提にした 1.5.8.1 の実装
- パラメータはファイル(
App_Data/Parameters/*.json)から読み込みます。Rdsの接続文字列だけは、空なら環境変数から補います(パラメータ の「読み込みのしくみ」)。 - 1.5.8.1 にはすでに
Parametersテーブル(ParameterId(IDENTITY の主キー)・Title・Body)があり、EnableScreenManagementを有効にすると、特権ユーザーが画面で変えた項目の差分(op・path・valueの配列)をTitle= パラメータ名で保存します。起動時と再読み込みのときに、ファイルから読んだ値にこの差分を当てます(Initializer.cs#L364-L469)。 - 既存の仕組みは、土台がファイルの全項目であること、テナントの区別が無いこと(
Titleだけで引く)、Rds・Env・ParameterSetting・Migrationなどは対象外であることが、このページの設計と違います。以下は、既存のParametersテーブルを拡張するか、別のテーブルを作るかを含めて検討するためのメモです。
Rds はファイルから読むしかない
Rds.json には DB の接続文字列が入っているため、DB から読むことはできません(DB に接続するための情報を DB に置けない)。読み込み元は次の 2 つに分けます。
| 分類 | パラメータ | 読み込み元 |
|---|---|---|
| ファイルだけ | Rds(と DB に接続する前に要る Env など) | 環境変数 → ファイル → C# の既定値 |
| ファイルと DB | Api・Security・General・Mail など | 環境変数 → DB(テナント)→ DB(全体)→ ファイル → C# の既定値 |
テーブル
差分の JSON を持つテーブル ParameterSources を、プリザンターの標準の形(本体・_deleted・_history の 3 テーブル)で作ります。CodeDefiner の Definition_Column/ に ParameterSources_*.json を足せば、テーブルは CodeDefiner が作ります(CodeDefiner の「対象のテーブル」)。
| 列 | 型 | NULL | 内容 |
|---|---|---|---|
TenantId | int | 不可 | テナント ID。0 は全テナント共通 |
ParameterName | nvarchar(128) | 不可 | パラメータ名(C# のクラス名。Api など) |
Body | nvarchar(max) | 可 | 変更した項目だけの JSON。NULL なら C# の既定値だけ |
Ver | int | 不可 | 版。更新のたびに増やし、楽観的排他に使う |
Comments | nvarchar(max) | 可 | 変更理由などのコメント(JSON の配列) |
Creator / Updator | int | 不可 | 作成者・更新者の UserId |
CreatedTime / UpdatedTime | datetime | 不可 | 作成・更新日時 |
主キーは (TenantId, ParameterName)、_history は (TenantId, ParameterName, Ver) です。
TenantId = 0 を全体の設定に使える理由
Tenants の TenantId は IDENTITY で Seed が 1 です。CodeDefiner は Seed が 0 でも 1 にして IDENTITY を作るため、SQL Server(identity(1, 1))・PostgreSQL(start with 1)・MySQL(auto_increment = 1)のどれでも、通常の運用で TenantId = 0 のテナントはできません(Tenants_TenantId.json、Columns.cs#L156-L159)。現行のファイルはテナントの区別の無い全体の設定なので、TenantId = 0 がそれに当たります。外部キー制約は付けません。
読み込み
図を読み込み中…
Read<T>() に「DB を使うか」の引数を足し、Rds などは DB を使わずに読みます。
private static T Read<T>(bool required = true, bool dbAvailable = true) where T : new()
{
var name = typeof(T).Name;
string json = dbAvailable ? ReadFromDb(name, tenantId: 0) : null;
if (json.IsNullOrEmpty()) json = Files.Read(JsonFilePath(name));
if (!json.IsNullOrEmpty())
{
var data = json.Deserialize<T>();
if (data == null && required)
throw new ParametersIllegalSyntaxException(name + ".json");
return data ?? new T();
}
if (required) throw new ParametersNotFoundException(name + ".json");
return new T();
}
private static string ReadFromDb(string parameterName, int tenantId)
{
try
{
// TenantId が tenantId か 0 の行を、TenantId の大きい順に 1 件
return /* select top 1 "Body" from "ParameterSources" ... */;
}
catch
{
return null; // テーブルが無い(初回)・DB に接続できないときはファイルへ
}
}- DB とファイルの両方にあるときは、DB だけを使います(2 つの差分を重ねない)。どちらが使われたかが分かりやすく、DB に移したあとはファイルを消す運用にします。
- 初回の起動ではテーブルがまだ無いので、
ReadFromDb()は例外を握ってnullを返し、ファイルと既定値で動きます。CodeDefiner でテーブルを作ったあとの起動から DB を読みます。 - 環境変数は従来どおり最優先で、DB やファイルで読んだあとに項目ごとに上書きします。
- 配列の項目は、差分の JSON に書くと配列ごと置き換わります(部分的な追加・削除はできません)。
- 本文が JSON として読めなければ
ParametersIllegalSyntaxExceptionにします。
書き込みと移行
書き込み口は、将来のパラメータ編集画面、管理用の API、CodeDefiner のコマンド、運用者の SQL を想定します。SQL Server なら MERGE で Ver を増やしながら UPSERT します。
移行は段階的に行います。
| 段階 | 内容 | 既存環境への影響 |
|---|---|---|
| 1 | Read<T>() を DB → ファイル → 既定値の順に読む形にする | なし(DB に行が無ければ従来どおり) |
| 2 | param-import コマンド(新規)でファイルの JSON を DB に取り込む(Rds.json は飛ばす) | なし |
| 3 | 取り込んだファイルを消す(任意。残しても DB が優先) | なし |
param-export コマンドで DB の JSON をファイルに書き戻せるようにしておけば、いつでもファイルの運用に戻せます。
テナントごとのパラメータ(将来)
TenantId > 0 の行を足せば、テナントごとに違うパラメータを使えます。リクエストのテナントの行が無ければ TenantId = 0 の行、それも無ければファイル・既定値の順です。
| 区分 | 例 |
|---|---|
| テナントごとに変える意味があるもの | Api(PageSize など)、Security(ロックアウトの回数など)、General(一覧の件数など) |
| テナントごとに変えるべきでないもの | Rds(不可能)、BinaryStorage(保存先)、Service(サービス名)などの基盤の設定 |
テナントごとの値はリクエストのたびに DB から読まず、キャッシュする仕組みが要ります。テナントの設定を読み込む Context.SetTenantProperties と同じタイミングで読み込むことが考えられます。