読者です 読者をやめる 読者になる 読者になる

Java の Web アプリで色んなコードを共通化したい

Java Java EE

この記事は Java EE Advent Calendar 2016 5 日目の記事です。
昨日は lbtc_xxx さんの「JPA Builder パターン」 でした。
明日は glory_of さんです。


Java の Web アプリを書く時、共通化したいけどどうすればいいか、 どう書くのが推奨されているのかをまとめてみました。 Java をガッツリやるようになったのは最近なので、間違った事を言ってたら指摘して頂けると助かります。

共通のプロパティが色んなクラスで増えてきた (CDI@Produces)

JAX-RS@Produces (javax.ws.rs.Produces) と混同しないようにしましょう。

基底クラス作って継承するとなると、基底クラスが fat になりがちで、テストする時に対象のメソッド以外にも色々セットアップしなきゃならなくなったりして面倒です。 他の言語だったら mix-in や trait で解決?するのかもしれませんが、Java では DI で解決すると楽です。

Java EEのCDIで定義しておくと便利なプロデューサーとインターセプタ - きしだのはてな

同じ処理をしているメソッドが増えてきた

メソッドをインターセプトして前処理や後処理を行います。 メソッドの中身で行っている中間の処理を共通化したい場合はメソッドの共通化や DI を検討しましょう。

やりすぎるとメンテナンスが辛くなりますが、具体的に使われるのは以下のようなユースケースだと思います。

  • 共通の例外処理 (エラーコードのレスポンスを送る)
  • 共通のロギング処理 (コントローラーやサービスのメソッドが呼び出される前後にロギングする)
  • 共通の認証処理

この手の AOP っぽい処理は大体フレームワークでサポートされていて、 果ては Javassist のような直接バイトコードをいじる裏技もあったりします。 ただどれもライブラリが優れているのでそんなに大変ではありません。

色んな場所でクエリを発行しているメソッドが増えてきた

JPQL と呼ばれる SQL っぽい JPA のクエリを使うと

  • ポータブル (Mongo のような NoSQL もサポート)
  • 高速

なデータアクセス層を実装できます。

まず JPA の機能について説明するのはしんどいので、詳しくはこの記事を参照してください (Spring Data JPA の例も載ってます)。

JPA では主に以下の3つの方法でクエリを実行することができます。

  1. JPQL
  2. ネイティブSQL
  3. Criteria API

この内1と2のクエリには @NamedQuery (JPQL)、@NamedNativeQuery (ネイティブSQL) で名前を付ける事ができます。

@NamedQuery(name="hello.findAll", query="select h from HelloEntity h")

名前を付ける事によって、複雑なクエリ以外は Dao クラスを作る必要もなく、エンティティにクエリを集約できます。

上記のアノテーションだけだと複数指定ができないので、 それぞれ @NamedQueries (JPQL)、@NamedNativeQueries (ネイティブSQL) と組み合わせる事で複数定義ができます。

@NamedQueries({
    @NamedQuery(name="hello.findAll", query="select h from HelloEntity h"),
    @NamedQuery(name="hello.findOne", query="select h from HelloEntity h where h.lang = :lang"),
    @NamedQuery(name="hello.deleteMsg", query="delete from HelloEntity h where h.lang = :lang")
})

ちなみに自分は他のエンティティと衝突しないよう、上記のように「(テーブル名).(クエリ名)」のような名前を付けています。

アノテーションがごちゃごちゃしてきた (CDI@Stereotype)

これまでにアノテーションが沢山出てきましたが、アノテーションがごちゃごちゃしてきたら、 @Stereotype アノテーションでいくつかのアノテーションをまとめて定義できます。

CDIのStereotypes - Challenge Java EE !

XML ファイルが多い

沢山の XML ファイルをコードで集約する、というのも一つの共通化かもしれません。

共通処理をマイクロサービスで行う?

マイクロサービスよく知りませんが、API Gateway Pattern というので上掲したような共通処理を担うサービスを作る、という考えがあるようです (強調は私です)。 Kong なんてミドルウェアもあるんですね…

  • 一箇所見に行けば全てのAPIを見つけられる
  • 細かい権限管理も可能
  • APIで何回も実装しないといけない部分を省略できる
    • Authentication
    • Rate Limiting
    • Aggregation
    • アクセス分析
    • ルーティング
    • データ変換
    • etc...

綺麗なAPI速習会 - Qiita

その他の参考リンク

ミニマリストな Vimmer におすすめしたい履歴管理プラグイン oldfilesearch.vim

Vim

この記事は Vim Advent Calendar 2016 (その2) の 4 日目の記事です。

追記:oldfilesearch.vimリポジトリへのリンクを貼り忘れていたので追記しました。

履歴管理プラグインへの懸念

履歴管理プラグインには様々なものがあります。 例えば MRU.vim であったり、unite.vim と連携する neomru.vim といったものがあります。 しかしこういった履歴管理プラグインは大体次のような処理を行います。

  • autocmd でファイルを開いた時にファイル名を記録する
  • 履歴ファイルを作成してファイル名等の履歴を書き込む

この方法だと

  • autocmd を使うため Vim の動作が重くなる
  • ファイルへの書込み処理にバグがあった場合、(ファイルの一覧を表示したり autocmd が実行された時点で) 動作が重くなったり、履歴が消えたりといった事が考えられる

しかし今回紹介する oldfilesearch.vim はこれらの方法は取りません。 oldfilesearch.vimviminfo に記載の情報からファイルの一覧を取ってくるので、ファイルが壊れたり動作が重くなったりする心配がまずありません。

github.com

操作方法

oldfilesearch.vim:OldFileSearch コマンド1つだけを提供します。

:OldFileSearch ファイル名のパターン1 [ファイル名のパターン2 ...]

以下は vimrc を含むファイルを開く例です。

:OldFileSearch vimrc

マッチする候補が複数あると以下のようなプロンプトが出て、 一番左の番号を入力してエンターを押すとファイルを開く事ができます (以下は bash を含むファイルを開いた時の例です)。

:OldFileSearch bash
Select old file:
1) <10 ~/.bash_profile
2) <56 ~/.bashrc
番号と<Enter>を入力するかマウスでクリックしてください (空でキャンセル):

<Tab> で補完もできます。

:OldFileSearch plugin/ .vim<Tab>

v:oldfiles

先程、viminfo に記載の情報からファイルの一覧を取ってくると言いました。 詳しく説明すると、v:oldfiles という Vim 組込みの変数からファイルの一覧を取得します (このリストは :oldfiles コマンドでも確認できます)。 ではこの :oldfilesv:oldfiles とはどのような物なのでしょうか。

:help :oldfiles からの引用です。

:ol[dfiles]      viminfo ファイルにマークが記録されているファイルのリス
            トを表示する。起動時にこのリストが読み込まれ、
            ":rviminfo!" を行った後でのみ変更される。|v:oldfiles|
            も参照。このリストで表示される番号は |c_#<| で使うこと
            ができる。

:help v:oldfiles からの引用です。

v:oldfiles   起動時に |viminfo| から読み込まれたファイルの名前のリスト。
        これらはマークを記憶しているファイルである。リストの長さの上限
        はオプション 'viminfo' の引数 ' によって決まる(既定では 100)。
        |viminfo| ファイルが使われていない時、リストは空となる。
        |:oldfiles| と |c_#<| を参照。
        このリストは変更可能であるが、後で |viminfo| ファイルに書き込ま
        れるものには影響しない。文字列以外の値を使うと問題を引き起こす
        だろう。

つまり、viminfo ファイルに保存されているファイルのリストだという事が分かります。

…とは言ったものの、正直今これを書いている自分が (viminfo ファイルのどの情報から取得するのか等) 詳細な挙動を把握できなくて、 実際に :oldfiles の結果と viminfo ファイルを見比べながら調べたので、 詳細な仕組みを解説してくれる人がいたらうれしいです… 見比べた結果、大体以下の項目からファイルリストを取ってくるようだということが分かりました。

  • ジャンプリスト
  • ファイルマーク
  • ファイル内マークの履歴

一つずつ解説していきます。

ジャンプリスト (:help jumplist)

viminfo ファイルでは「# ジャンプリスト (新しいものが先):」または「# Jumplist (newest first):」という行の下にあるエントリです。

<C-o><C-i> で移動できるファイルの事です。 エントリ数の上限は50個となっています。

ファイルマーク (:help viminfo-file-marks)

viminfo ファイルでは「# ファイルマーク:」または「# File marks」という行の下にあるエントリです。

番号マーク ('0 〜 '9) ですね。 適切なタグがないですが、:help mark-motions してちょっと下にいった所 (/^'0 で飛べる場所) にある文章を引用します。

直接セットすることはできず、viminfo ファイル |viminfo-file| を使っている場合にのみ存在します。
基本的に '0 は最後に Vim を終了したときのカーソル位置であり、'1 は最後から1個前の
位置、などなどです。特定のファイルを番号マークに保存しないようにするには
'viminfo' の "r" フラグを使ってください。参照: |viminfo-file-marks|

豆知識ですが、つまり「0」のマークは最後に開いたファイルとその位置を記憶しているので、 Vim を起動して「'0」(シングルクォート + 0) または「`0」(バッククォート + 0) と入力すればそのファイルを開けます。

ファイル内マークの履歴

viminfo ファイルでは「# ファイル内マークの履歴 (新しいものから古いもの):」または「# History of marks within files (newest to oldest):」という行の下にあるエントリです。

ファイル単位の jumplist のようにも見えましたが…すみませんよく分かりませんでした。

viminfo に保存するファイル数の上限を設定したい (:help viminfo-')

viminfo の ' (シングルクォート) フラグを使います。

 '   マークが復元されるファイル履歴の最大値。オプション 'viminfo'
        が空でないときは、常にこれを設定しなければならない。
        また、このオプションを設定するとジャンプリスト |jumplist|と
        |changelist| も viminfo ファイルに蓄えられることになる。

viminfo に保存したくないファイルを指定したい (:help viminfo-r)

viminfo の r フラグを使います。 :help viminfo-r の説明が分かりやすいので Vim の日本語訳 help から引用します。

 r   リムーバブルメディア {訳注: USBメモリ、CD-ROM等の取り外せる記
        憶装置。この中身は取り替えてしまえば全く変わるので、ファイル履
        歴の意味がない} の指定。引数は文字列 (次の ',' まで) である。
        これは複数個指定できる。それぞれがマーク履歴の対象外になるパス
        の先頭部を指定する。これはリムーバブルメディアを避けるためであ
        る。 MS-DOSでは "ra:,rb:", Amigaでは "rdf0:,rdf1:,rdf2:" とす
        るとよい。ここに一時ファイルを指定することもできる
        (Unixの例: "r/tmp")。大文字と小文字の区別はない。それぞれの
        'r' の引数の最大長は 50 文字である。

ただ、.git/COMMIT_MSG のように中間のパスでの無視はできないようなので、諦めるしかないかもしれません。 ただそもそも oldfilesearch.vim ではファイル名を入力して目的のファイルを開くという操作方法なので、 <Tab> 補完を使わなければ開かないファイルが目に入る事はありません。

…しかしそれでも <Tab> をよく使うのでどうしてもそういったファイルを除外したい!という方もいるかもしれません。 そういった方のためにやり方だけ紹介します (俗に言う「読者の課題」です)。

冒頭で挙げた :help v:oldfiles の文章で目敏く見つけた方もいるかもしれません。

     このリストは変更可能であるが、後で |viminfo| ファイルに書き込ま
        れるものには影響しない。文字列以外の値を使うと問題を引き起こす
        だろう。

つまり Vim が起動した後なら v:oldfiles の内容をいくらでも変えられるという事です。 つまり v:oldfiles からいらないファイルを除外してしまえばいいのです (現に let v:oldfiles = [] としてリストを空にすると :OldFileSearch コマンドは過去に開いたどのファイルも開けなくなります)。 しかし viminfo ファイルには変更した v:oldfiles の内容は反映されないので、 Vim の起動時やプラグインの起動時等のタイミングで毎回除外する必要があります。

感想

このプラグインを見つけた経緯としては、実は自分が v:oldfiles の存在を知って 「これで履歴プラグインを作れないか?」と思い誰かすでに作ってるかと思って検索したら 案の定 oldfilesearch.vim があった…という経緯です。 自分は飽きっぽいので注力するプラグインはなるべく減らしたいという気持ちがあり、 誰かが作ってくれてるならプルリクエストを送ってメンテナンスを押しつけ改善していった方が皆がハッピーになれるでしょう。

何度かプルリクエストを送って分かった事としては、 作者はおそらくミニマリズムで、おそらくこれ以上大きな変更は加えられないと思います (PR を見てもらえば分かると思いますが、私も何度も PR を送りましたが、ほぼリジェクトされています :p)。 ファイルを履歴から開くという操作は (人にもよりますが) おそらく日常的に使われる操作であると思うので、 そういったプラグインが安定しているというのは安心して使っていられますね。

あと、oldfilesearch.vim では最小限の UI しか提供していませんが、 「oldfiles を使う履歴管理プラグイン」というアイデアは他のプラグインでも応用できると思います。 例えば neomru.vim などのプラグインが一時ファイルではなく oldfiles からファイルリストを取得し、 一時ファイルへの書込みはオプションなんかで無効にできるようにする、という作りにする事も可能でしょう。

おまけ

  • 「'」(シングルクォート) と「`」(バッククォート)、この2つのコマンドは 実は違う意味のコマンドだというのをこの記事を書いてて初めて知りました… 詳しくは :help mark-motions を参照してください。

  • あと、最近*1 vim_dev によくパッチ出してる Yegappan さんって MRU.vim とか taglist.vim の作者さんだったんですね。気付きませんでした。

*1:目に付いたのは最近ですが、昔はどうだったか調べてません…

自転車関連の本が Kindle Unlimited で大量に読み放題になってる

著者名「BiCYCLE CLUB 編集部」でググると大量に見つかる。

それ以外でも Kindle ストア → Kindle Unlimited 読み放題ストア で絞り込むと結構自転車関連の本が読み放題になってて助かる。 特に「自転車"道交法"BOOK」はオススメ。読んでおいた方がいい。

自転車“道交法

自転車“道交法"BOOK (エイムック 2789)

ロードバイク「規格」便利帳 改訂版 (エイムック 2811)

ロードバイク「規格」便利帳 改訂版 (エイムック 2811)

これも大変非常に参考になる。

自転車少女 TOブックス写真集

自転車少女 TOブックス写真集

ロードバイク始めたばかりの自分にとってはかなり助かる。 Kindle Unlimited、完全に解約のタイミングを失った気がする。