DBFlute-1.1の区分値強制のお話

DBFlute-1.1だとこうなるシリーズ、
すいません、まだあります。

区分値の設定とか微調整的なものですが、大切な話。

1.0.x系だと...

// 1.0.xでは、やろうと思えばデフォルトでこれができちゃう
cb.query().setMemberStatusCode_Equal("FML");
member.setMemberStatusCode("FML");

// 本当はこうしてほしい
cb.query().setMemberStatusCode_Equal_Formalized();
member.setMemberStatusCode_Formalized();
String statusCode = ...; // リクエストなどから飛んで来たら
cb.query().setMemberStatusCode_Equal_AsMemberStatus(CDef.MemberStatus.codeOf(statusCode));

// プロパティで強制できる (littleAdjustmentMap)
// isForceClassificationSetting = true
// すると、このメソッド消える
//cb.query().setMemberStatusCode_Equal("FML");
//member.setMemberStatusCode("FML");

// DBFluteも困る、Entityにマッピングできないじゃん!
// CDef経由のSetterでマッピングするけど、
// 万が一、デタラメコードがDBに存在してて、検索しちゃったら?
//  -> CDef.Xxx.codeOf() の仕様通り、nullになる
String code = member.getMemberStatusCode(); // null

// そんなのデータ不備なんだから例外になってほしい!
// プロパティで例外にできる (littleAdjustmentMap)
// isCheckSelectedClassification = true

// ということで、両方とも true にすると、とっても Strict
という感じでした。

関連ドキュメントは:
 -> ネイティヴ型メソッドの削除

デフォルトで true にしない理由は、
こちらの通り:
 -> デフォルトfalseだが、ほとんどtrueでOKのプロパティ

名付けて「隠れ区分値」
そういったものが万が一あったらと。。。
特に「データの内容次第で検索できない」ってのは、
なかなか厳しいこともあると思うんですよね。

ただ、そんなレアケースに縛られてても、
いや、安心というポリシーからしたら逆に、うーん。

ということで、結局こうしました。

1.1からは...

// 1.1では、まずこれはデフォルトでできないように
//cb.query().setMemberStatusCode_Equal("FML");
//member.setMemberStatusCode("FML");

// みんなこれ使ってね
cb.query().setMemberStatusCode_Equal_Formalized();
member.setMemberStatusCode_Formalized();
String statusCode = ...; // リクエストなどから飛んで来たら
cb.query().setMemberStatusCode_Equal_AsMemberStatus(CDef.MemberStatus.codeOf(statusCode));

// 万が一、デタラメコードがDBに存在してて、検索しちゃったら?
//  -> とりあえず検索はできる!(以前は、nullか例外だった)
String code = member.getMemberStatusCode();

// ただ、デフォルトではログに出てくる
// 落とさないけど「おかしいかもしれないよ」って感じで。
//  -> *Undefined classification code was set...

// 無論、プロパティで例外にできる (littleAdjustmentMap)
// ; classificationUndefinedHandlingType = EXCEPTION
//
// 同様に、何もしないようにすることもできる
// ; classificationUndefinedHandlingType = ALLOWED
//
// そして、区分値ごとにも定義できる
// (classificationDefinitionMap)
// topComment と同じmapにて、
// map:{
//     ; topComment=...
//     ; undefinedHandlingType = LOGGING
// }

// ネイティヴ型メソッドを使いたかったら、区分値ごとに生成できる
// (classificationDefinitionMap)
// topComment と同じmapにて、
// map:{
//     ; topComment=...
//     ; isMakeNativeTypeSetter = true
// }
// ベタうち復活
cb.query().setMemberStatusCode_Equal("FML");
member.setMemberStatusCode("FML");

わざとやっているのか?まちがいなのか?

ということで、
デフォルトの状態でちょっとだけ厳しくなりました。
ただ、実行時の「紛れ」で下手にシステム落ちないように。

迷ったのは...
本番運用で発生する未定義区分値が、
「わざとやっているのか?まちがいなのか?」

究極は自動で判別できないというところでした。
色々とヒアリングをしていると、
やっぱりjfluteの想像を超えるシステム構成や運用を
している話が耳に入りました。
まあ、そういうところで DBFlute が使われるかどうか!?
って話はあるのですが、結論としては「ありえそう」でした。

「一つのDBで、複数のアプリ、複数の組織が運用している」
なんてことがあると、そういうことあるかなーと。
もちろん、変なコードが来たら検知したいってのはありますが、
それを仕組みのデフォルト設定ではちょっとできないなと。

やっぱりデフォルトで落とすのは、やめようと。
かといって、何かしら警告はしたいので、ログという形にしました。
(ユーザーの方からアドバイス頂きました、ありがとう!)

ネイティヴ型メソッドは独立オプションに

一方で、ネイティヴ型メソッドは、ほとんどの場合で不要なので、
遠慮なくデフォルトで削除。

リフレクションとかで落ちるにしても、
それは基本的に開発中であることが想定されるので、
必要であれば、isMake...してしまえばいい。

ポイントは、そこですね、厳しいチェックロジックが、
本番で発生するのか、開発中に発生するのか?
本番で発生する可能性があるものは、
100%まちがい!と言い切れないとチェックしづらい。
そういうジレンマがあります。

もともと、ネイティヴ型メソッドを削除すると、
CDefのcodeOf()を使うから未定義区分値の検索どうする?
っていう話につながっていたのですが、
そのつながりをやめました。

別にネイティヴ型メソッドがなくなっても、
未定義区分値を検索することはできる、という風に。
なので、チェックの話と切り離せて考えられるようになりました。

似たような話で、NotNull制約などがあります。
アプリを再リリースせずに、NotNull制約だけ付けたり外したりって、
ありえるだろうと思って、DBFluteではドキュメント要素という
位置づけでしか使っていません。DBスキーマDBFluteが
ズレてもある程度は動くようにしておかないとというところで。

それはそうと、DBFluteマッピングはどうやってんの?
というところですが、実はメソッド削除といっても、
たいした話ではないのです...

暗黙の区分値のチェックは統合

暗黙の区分値のチェック、isCheckImplicitSet ですが、
これは先ほど紹介した undefinedHandlingTypeに統合されました。

どちらかというと、先ほどの「検索時チェック」が、
「EntityのSetter呼び出し時チェック」に統合されたと
言えるかもしれません。

別に検索時だけチェックである必要はないし、
ほとんど似たような機能だったので、
わかりやすさも考慮して一緒にしました。

暗黙の区分値のチェックは統合

暗黙の区分値のチェック、isCheckImplicitSet ですが、
これは先ほど紹介した undefinedHandlingTypeに統合されました。

どちらかというと、先ほどの「検索時チェック」が、
「EntityのSetter呼び出し時チェック」に統合されたと
言えるかもしれません。

別に検索時だけチェックである必要はないし、
ほとんど似たような機能だったので、
わかりやすさも考慮して一緒にしました。

互換性大魔神

1.0.x系から1.1に移行される猛者ユーザーの方々のために、
今までと同じプロパティがそのまま利用できます。

o isCheckSelectedClassification (littleAdjustmentMap)
o isForceClassificationSetting (littleAdjustmentMap)
o isCheckImplicitSet (classificationDefinitionMap)

これらは、1.1からはドキュメント上からも削除されますが、
しばらく動き続けるプロパティです。
isCompabileBeforeJava8 オプションと合わせると、
基本的に同じ挙動になるはずです。

万が一、挙動の違いはあっても、
「厳しくなるのではなく緩くなる、もしくは、ログが出るだけ」
というポリシーで実装しています(そのはず)。

まあ、よければ、
(littleAdjustmentMap)
classificationUndefinedHandlingType = EXCEPTION
にして、

isCompabileBeforeJava8 = false
なら、それだけでOK。
(その他のプロパティは適宜調整する必要はありますが)

isCompabileBeforeJava8 = true
なら、
(littleAdjustmentMap)
isMakeClassificationNativeTypeSetter = false
にすると、
古いプロパティを削除しても同じ動きになるかと。

ヒアリングってほんと大事

jfluteは、いまはWebサービス開発の現場にずっといて、
受注開発やってた頃も BtoC のサービスものばかりで、
実は、いわゆる「大規模がっちり業務システム」
みたいな開発の経験は実は少なかったり。。。

DBFluteは、変化の激しいインクリメンタルな世界を
主なターゲットではありますが、様々な応用も効かせたい。

昔からヒアリングはたくさんしているので、
色々な話は聞いて疑似体験として積み重ねていますが、
やはり感覚としては薄れてしまうもの。

なにを心配するか?なにを安心と思うか?
なにが効率的なのか?

これら、現場でいろいろ違うわけで、
バランスよく考えて良いものを提供するために、
コミュニケーションマニアにならないとぅ。