日々常々

ふつうのプログラマがあたりまえにしたいこと。

SQLアンチパターンを読んだ

SQLアンチパターン

SQLアンチパターン

積読--;

1冊減ったところで誤差でしかない……。ちなみに2年積んでました(懺悔

感想

「知ってることばっかやなー」

……パターン本なんだから当たり前です。だいたいは普通にやってたら踏まない、はずが、なぜか現場ではよく見る。アンチパターンって引力があるからアンチパターンなので、「なぜか」ではないんですが。

SQLアンチパターン」って名前ですけど、「データベス論理設計」「データベース物理設計」「クエリ」「アプリケーション開発」の四部構成です。SQLDMLを想像したなら一部だけで、内容の印象としては前半二部の設計が厚めでした。

でも踏むのもある

はい。引力すごい。

あたりはたまに踏んでるかな……熟考して踏み抜いてるものもありますが、妥協ってのも、はい。これを機に踏みとどまる回数を1回増やしてみることにします。

名前をつける意味

25のパターンが挙げられています。 名前がつけられ、アンチパターンとはっきり言われていると色々やりやすくなります。

パターン名がちょっと結びつきづらいのは残念なところ。名付けの由来も説明してくれてるから、そこ読めば「あーね」とはなるんだけど。

こんな人におすすめ

DB設計する人もSQL書くだけの人も。RDBに関わる開発者ならみんな、かな。

RDBあまり使ったことない人には、知の高速道路としてすごくいいんじゃないかな。背景から具体的なDDLSQL、アプリケーションコード(PHPなので読み飛ばしたけど←)で例示、複数ある解決策の選択基準まで書かれてます。すごい。もちろん「これだけ読めばバッチリ!」な本ではありません。アンチパターンの背景を理解しようとすれば一気に世界は広がると思います。そして積読タワーも高く成長すると思います……。

ある程度経験ある人だったら「知ってることばっかり」になるかなーとは思います。冒頭にも書いた通り、パターンですからね。確認用、話のタネ、他の人に自分の言葉で説明するのが怠い時に書籍参照って言う、とか色々使えますね。

SpringBootでのRestTemplateのタイムアウト設定

  • spring-boot:2.1.7.RELEASE
  • spring-web:5.1.9.RELEASE

Short Answer

RestTemplateBuilder restTemplateBuilder = ...

RestTemplate restTemplate = restTemplateBuilder
                .setConnectTimeout(Duration.ofSeconds(3))
                .setReadTimeout(Duration.ofSeconds(3))
                .build();

タイムアウトが設定できない時は実行時例外が出ます。

以下は中身に興味のある人向け。

登場人物

f:id:irof:20190817195852p:plain

RestTemplateとかRestTemplateBuilderとか

RestTemplateは実際の通信をClientHttpRequestFactoryに任せます。 この子はインタフェースなので実際の通信は実装クラスがすることになります。 そのため通信に関わることは ClientRequestFactoryの実装が使用するHTTPクライアントに依存することになります。 今回のタイムアウトなどはこのHTTPクライアントが担うものになります。

RestTemplateBuilderはSpringBoot1.4で導入されたRestTemplate設定用のクラスです。 RestTemplateBuilderがあるのならRestTemplateへの設定は全部任せたいところですが、タイムアウトは前述のようにRestTemplateに対する設定ではないのです。 これどうやってんだっけ、ってのがこれ書いた動機。

タイムアウトを実際に設定するところ

タイムアウトはHTTP通信に依存したものという判断か、RestTemplateにはタイムアウトを設定するようなメソッドはありません。 そのためClientRequestFactoryの実装か、使用されるHTTPクライアントに設定することになります。

デフォルトで使用されるSimpleClientHttpRequestFactorysetConnectTimeout/setReadTimeoutを持っており、これらの値がHttpURLConnectionに渡されます。他のクライアントでも同様だと思います。

という事で、タイムアウトを設定したClientRequestFactoryを使用するようにRestTemplateに設定します。これにはRestTemplateのコンストラクタかsetRequestFactoryメソッドを使用します。

SpringBootを使用しない場合はこんな感じ。

SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(3_000);
requestFactory.setReadTimeout(3_000);
RestTemplate restTemplate = new RestTemplate(requestFactory);

SpringBootを使用する場合も最終的に前述の設定をする事には変わりありません。なのでRestTemplateBuilderがよくわからなくても次のような方法でいいといえばいいわけです。

  • いままでどおりやる。
  • buildしたRestTemplateに自分で生成したClientHttpRequestFactorysetRequestFactoryする。

でもタイムアウトを設定したいのであってSimpleClientHttpRequestFactoryを使いたいわけではないわけで。

SpringBootでのClientHttpRequestFactoryの決まり方

という事でClientRequestFactoryの実装にタイムアウトを設定したいのですが、まずはClientHttpRequestFactoryがどう決まるかを見ていきましょう。

RestTemplateBuilderRestTemplateに対して次のようにClientHttpRequestFactoryを設定しています。(ソース

  • Supplier<ClientHttpRequestFactory>が指定されていればそれを使用してClientHttpRequestFactoryを生成する。
  • detectRequestFactoryが有効ならClientHttpRequestFactorySupplierを使用してClientHttpRequestFactoryを生成する。
  • どちらも無効なら何もしない。(RestTemplateのデフォルトになる。)

RestTemplateBuilderはクラスパスにあるHTTPクライアントを自動で使用するようになっており、SpringBoot2.1.7では ApacheHttpClient -> OkHttp3 -> JDK となっているようです。これはClientHttpRequestFactorySupplierで実現されます。(ソース

先に「SimpleClientHttpRequestFactoryClientHttpRequestFactoryのデフォルトになる」というような事を書きましたが、SpringBootを使わない場合はRestTemplate(というか基底のHttpAccessor)のデフォルトであり、SpringBootを使う場合はRestTemplateBuilderClientHttpRequestFactorySupplierのデフォルトであります。場合によってはSpringBootを使う場合と使わない場合でClientHttpRequestFactoryのデフォルトが違うなんてことも起こらないとは言わない。

タイムアウトを設定したClientHttpRequestFactoryインスタンスを返すSupplier<ClientHttpRequestFactory>を使うようにすればRestTemplatesetRequestFactoryをしなくてもよくなります。これでもいいですね。

RestTemplateBuilderでのタイムアウト設定

RestTemplateBuilderを使用するならClientRequestFactoryの実装を気にせずタイムアウトを設定したいものです。 具体的にはSimpleClientHttpRequestFactoryのような実装クラスを目にしたくない。いや別に恨みがあるとかではないですが。

ここで冒頭の設定方法にようやく辿りつきます。長かった。

RestTemplateBuildersetConnectTimeout/setReadTimeoutClientRequestFactoryインスタンスに対するリフレクションで実現されています。(ソース) 該当するメソッドがない場合は実行時に例外が出るので安心ですね。 安心じゃねーよ。とはいえ無視されてタイムアウト効かずに動くよりはマシか……

しかしこれなんでClientRequestFactoryにメソッド追加……は他の実装に配慮した(defaultメソッドはJava7以前の互換で使えないし)として、新しいインタフェース作ったりしなかったんだろう。まあインタフェースにしても実行時例外には変わらないけど。

寄り道: MockRestServiceServerの話

spring-testMockRestServiceServerRestTemplateClientHttpRequestFactoryを差し替えることで実現されています。 つまり、ClientHttpRequestFactoryの実装に依存するテストは行えません。 こちらを動作させたい場合はMockServerなどのテストダブルをHTTPの先に置くことになります。

spring-boot-test@AutoConfigureMockRestServiceServer を使うと RestTemplateBuilder で生成された RestTemplate に自動でバインドされます。これは RestTemplateCustomizerの実装であるMockServerRestTemplateCustomizerRestTemplateBuilderに設定されるから。便利なんですが、RestTemplateBuilderで複数のRestTemplateを作ってるとちょっと使いづらくなるのが残念なところ。

長々書いたけど

実際のとこRestTemplateBuilderに対してIDE経由でお伺いをたてたら一瞬で見つかります。

f:id:irof:20190817193315p:plain

あと、最新の master だとここで見たコードも結構変わってたりてて面白かった。

nullが嫌い

消したつももりだった下書きが残ってたっぽいので公開しちゃう。なお2015-05-10に書いたもののようです。当時のブログを見ると、Optionalでなんか盛り上がった頃みたい。

ということで以下は編集せずそのまま投稿ー。

前置き

nullを安易に使わない 派生のポエムです。

nullが嫌いってのは上記に限らずよく書いてる気がするけど、嫌いって感情だけでポエムったらこんなになってました。

なお、Javaの話です。

nullが嫌い

nullが嫌い。気軽に使われるnullが嫌い。思考停止で使われるnullが大嫌い。一時変数を宣言して初期化とか言って使われるnullが嫌い。まともに扱いきれないのに簡単に使えてしまうnullが大嫌い。

NullPointerExceptionが発生した時に「開発者の注意不足で発生した。しっかりチェックして再発防止に努める。」などと言わされたり(やりもしないのに)、責める要因に使われるのを見ると虫酸が走る。「nullの可能性に注意を払えなかったこと」を責めたててどうするのだろう。問題はそこじゃないよね。そりゃ気をつけてて、偶然気づければ防げるかもしれない。でも人間は注意し続けることはできない。チェックリストを作っても、チェックは漏れる。チェックすれば救われる教徒のチェックリストはおもしろい長さになるし、運用もザルになるものだ。「チェックOKが記入されているチェックリストをコピーして一部修正」なんてのも、よくある話。チェックリストが長すぎるのが悪い。チェック漏れが起こったらチェックリストのチェックリストを作りはじめる。冗談のように聞こえるけれど、稀によく見る。「気をつけます」なんてのは、「気をつけ損なったら仕方ない」の宣言に他ならない。なので、漏れる。気をつけたのに漏れた場合は、気をつけてなかった以外に理由は出てこない。気をつけ漏れないように気をつける、無限ループ。

確かに、注意でしか防げないことはある。あるだろうよ。そういうものはチェックを二重化、三重化して漏らす可能性を可能な限り減らす。それでも絶対はない。多重化されたチェック機構はコストに対する効果がどんどん薄れていくものである。そして「nullチェック」にそこまでのコストを割くことはできない。超軽く扱われてる。だったら漏らしていいってことだよね。ダメ?ダメだったら「注意」以外のアプローチが必要だろう。とは言え、今まで「注意する」でやってきたものがいきなり他のうまくいく方法を考え出せるでもないし、完全にうまくいく方法なんて存在するわけでもない。よく言われるように、銀の弾丸なんて無い。他の方法を考えた時に完全性とか言い始めると何もできない。シンプルにメリデメの天秤にかけるしかない。

……と言う話をしても「丁寧にnullチェックすれば良い」と主張されたりする。そうか、ならばNullPointerExceptionが起こりうるすべての場所でチェックする?できる?無理だよね。

nullに注意を払わなきゃいけない状況は、いままで安易にnullを使ってきたツケなんだ。安易にnullを返すメソッドが悪だし、安易にnullに逃げる文化が悪い。その負債の一部を払うのがnullチェック。一部しか払えず、nullチェックを行えば行うほどコードの量が増える。つまり負債が増える。負債を他所で借りてきたもので払うって自転車操業、全体でみると負債は増えてるんじゃないかな。

NullPointerExceptionに遭遇した際、最初にやることはおそらく「nullチェックを追加する」ではない。きっとnullを無視して動くようにすることでもない。これらは問題を先送りしているだけ。そこで例外が起こるということは、おそらく例外的事象なんだ。nullになるべきでない状況でnullになる現実、その根本を断ちに行かなくちゃ。でないと、いつまでたってもnullと戯れ続けることになる。そんなに暇じゃないはず。

勿論、nullチェックが必要な場面もある。一切合切のチェックを排除できるなんて言ってないし、Javaからnullを根絶できるとも言ってない。ここまで書いて、「私が嫌いなのはnullそのものじゃないんだなー」と気づいた。