定数をコードでなくデータベースに置く団体差を吸収する仕組み
団体ごとに変わる設定値を定数としてDBに持ち、起動時にdefineする設計。その利点と、型・存在をコード側で守る契約について。
grep しても見つからない define
NGWの保守を引き継いだエンジニアが、最初に戸惑うことがある。コードのあちこちで _SOME_FLAG_ のような定数が参照されているのに、その定数を define している箇所をリポジトリ全体で grep しても、どこにもヒットしないのだ。
種を明かせば単純である。この定数は、コードのどこにも書かれていない。値の出所はデータベースの設定テーブルであり、アプリケーションの起動時(接続確立時)に、そのテーブルの全行を読み込んで、実行時に一括で define している。つまり値の実体は、ソースではなくデータの側にある。
なぜこんな回りくどいことをするのか。答えは、同じひとつのコードベースを、設定の異なる複数の団体で同時に運用したいからだ。
変わるものはデータに追い出す
自治体向けのシステムでは、団体ごとに「振る舞い」が微妙に違う。1日の標準勤務時間、ある機能を使うか使わないか、何らかの閾値。こうした値をコードに直書きすると、団体が増えるたびにコードが枝分かれし、やがて分岐の森で身動きが取れなくなる。
そこで、変わる可能性のある値を設定テーブルの1行として持たせる。起動時の初期化は、概念的にはこれだけだ。
// 設定テーブルの全行を読んで定数として展開する
$rows = $pdo->query('SELECT name, value FROM app_settings')->fetchAll();
foreach ($rows as $r) {
define($r['name'], $r['value']); // 例: define('_WORK_HOURS_', '7.75')
}
この設計の効き目は運用面で大きい。新しい設定を増やしたいとき、やることは「テーブルに行を1つ足す」だけで、コードには一切手を入れない。団体ごとの挙動の切り替えも、その団体のDBの値を変えるだけで済む。再デプロイなしに振る舞いを変えられる ── これは要するに feature flag、設定の外部化の一種である。コードは団体差を知らないまま、すべての団体で同じように動く。
データ化した値の型と存在は、コードが守る
美しい仕組みには対価がある。この方式には、意識しておくべきトレードオフが3つある。
- 追跡性が落ちる。値がソースに現れないので、「この定数はどこで何のために使うのか」は、定義側ではなく参照側を辿って理解するしかない。grep で定義に飛べない不便は、設計の裏返しとして受け入れることになる。
- 型の問題。DB由来の値は文字列として
defineされることが多い。すると数値のつもりの定数が実は'7.75'という文字列で、厳格比較(===)や型に敏感な分岐で足をすくわれる。近年の言語バージョンほど型に厳しくなっているので、ここは意図的に型を寄せておくべきだ。 - 存在の問題。必要な行がDBに登録されていなければ、その定数は未定義のまま参照され、エラーになる。データ側の不備が、そのままコードの実行時エラーに化ける。
だから参照側に、存在チェックと既定値のフォールバックという「契約」を用意しておく。
function cfg(string $name, $default) {
return defined($name) ? constant($name) : $default;
}
$hours = (float) cfg('_WORK_HOURS_', 8.0); // 未登録でも落ちない・型も寄せる
たった数行だが、これがあるとないとで堅牢性がまるで違う。定数がなくても既定値で動き、文字列を数値に寄せることで比較の事故も防げる。
0 と 1 にも、名前を与える
この「定数化」の発想は、団体差のような可変値だけに向けられているわけではない。もっと素朴な値 ── 0 と 1 にも及んでいる。このシステムでは、0 を _NO_、1 を _YES_ として定数化している(これらも先の設定テーブル由来の定数の仲間だ)。
一見どうでもいい工夫に見えるかもしれない。だが、これでロジックの読みやすさが格段に変わる。
// 生の数値: この 1 は何を意味する?
if ($privacyFlag === 1) { ... }
// 名前を与える: 意図がそのまま読める
if ($privacyFlag === _YES_) { ... }
0 と 1 は、プログラムの中で最も基本的で、最も多用される値だ。だがそれゆえに厄介でもある。素の 0 や 1 がそこに書かれていても、「この 0 は何を表す 0 なのか」を読み手が頭の中で補わなければ、ロジックの意味は取れない。フラグが立っていないのか、件数がゼロなのか、既定値なのか ── 同じ 0 でも文脈で意味はまるで違う。
_NO_ / _YES_ と名付けておけば、その一語が意図を語る。比較式を目で追った瞬間に「これは"はい"かどうかを見ている」と分かる。コメントを足さずとも、コード自身が意味を持つ。値は同じでも、名前があるかないかで、読み解く速さがまるで違うのだ。
AIとの協働作業での学び
変わるものはコードから追い出して、データにする。団体差のように「同じ振る舞いの、値だけが違う」ものは、分岐ではなく設定として外に出すのが素直だ。再デプロイせずに挙動を変えられる自由は、運用の現場で確かな価値になる。
ただし、データに出した瞬間、その値の型と存在の保証はデータ側から失われる。だからこそ、参照側のコードで「なければ既定値・使う前に型を寄せる」という契約を先に決めておく。外部化の自由と引き換えに手放した安全を、コードの側で拾い直す。この一手間を惜しまないことが、設定をデータに委ねる設計を成立させる条件である。