システム開発で思うところ

Javaで主にシステム開発をしながら思うところをツラツラを綴る。主に自分向けのメモ。EE関連の情報が少なく自分自身がそういう情報があったら良いなぁということで他の人の参考になれば幸い

【非技術】我が家のカルボナーラ

唐突かつ、全く技術に関係のない話

我が家(というか、私が作る)カルボナーラは、クリームもチーズも使わない。

  1. お湯を沸かす(1.5Lくらい?)
  2. 塩を小さじ(1~1.5くらい?)を入れてパスタを茹でる
  3. 麺を茹で始めてから、フライパンに多めのオリーブオイルとチューブの刻みニンニクを1cmくらい、ベーコンと一緒に炒める(弱火でノンビリ)
    焦げ付きそうなら火を止めるか ごく弱火でベーコンを香ばしく
  4. 茹で上がりの1分前くらいになったら料理酒を大さじ1(作る人数分×大さじかなぁ。まぁ適当)を入れて、アルコールを飛ばして火を止める
  5. 麺をフライパンへ移動(湯切りとかせずに、そのまま適当でOK)
  6. 麺とベーコンを絡める
  7. 卵黄を落として、潰しながら 麺のゆで汁を お玉で1杯より少なめ位を入れて和える
    濃度調整と塩気は味見しつつ ゆで汁を加えて調整(お好みで味の素を入れても良い)
  8. 醤油で塩気を調整(ゆで汁だけで塩気の調整を頑張らない)
  9. 黒コショウを振りかけてオシマイ

オリーブオイル&ベーコンの油 と ゆで汁による乳化で、クリームもチーズも入れなくても十分クリーミー。 ベーコンの塩気が ちょっとチーズっぽくて、別にチーズとか入れなくても特に気にならない。

もともとクリームやチーズ臭の強いカルボナーラが苦手ということで 試しにやってみたら 思った以上に食べられるものだったことから、私の定番になり 今は我が家の定番に。

ポイント

  • 卵黄だけ使う*1
  • フライパンの火を止めてから麺を和える
  • 卵黄とゆで汁と麺を和えるときは手早く素早く
  • ベースの味を濃くしたかったら、ゆで汁の塩を増やして調整か、ベーコンを炒めるときに塩コショウ かな

*1:残った卵白は、冷凍して肉団子などのつなぎに

【考察】Jakarta EEは見捨てられるのだろうか?

への私なりの考えをツラツラと。

はじめに

@suke_masa さんは間違っている!とかいうつもりは全くありません。
確かに、そういう考えもあると思います。
私が EEを使ってみようと思った経緯や、意固地(?)にEEを使っている背景などを自分にとっての整理も兼ねて綴っているだけです。
@suke_masa さんは Springにも Glassfish勉強会にも関わる中で感じた危機感のようなものを表現されたのだと、私は思っています。*1

結論

Java EEJakarta EE)を採用するしないはエンジニアの意向とは別
(今更)焦っても大して変わりはしない、かな?

javaxパッケージの影響

これがきっかけかな?と思います。
つまり javaxパッケージが EEとして拡張できなくなるということで、EE離れが起きるのでは?という問題提起。
パッケージに関しては、

の中の @yamadamnさんと、@khasunumaさんの返信が参考になります。

SpringとEEへの影響ですが、個人的には「Springを使っている方が影響は少ないんじゃないかな?」です。 理由は、javaxパッケージを直接使うのではなくspringパッケージでラップしているから。 EEは標準技術ということで、javaxパッケージを直接使っているという認識。 「ラップしてると影響を局所化できる云々」は、@suke_masa さんとのやり取りの通り。

なので、Java EEの延長としての Jakarta EE への移行については、拡張部分を除けば これまでのJava EE8のアップデートと同じ計画ですすめて行けば良いし、拡張部分を使うのであればベンダー謹製か自社ライブラリでラップしておけば まぁ良いんじゃないですかね?というのが雑な移行計画。もうちょっと真面目に補足すると、EEに限らず 今後のシステム移行というかシステム構築のあり方はクリーンアーキテクチャとかDDDとか、そういう設計思想や知見を活用して ドメイン知識(業務の主たる関心事)をPOJOで実装しましょうってことだと思います。今回はたまたまJava EE側の問題ですが、Spring側で今回のような問題が発生しないとは言い切れませんからね。もっといえばGraalVMまで考えるとドメイン知識の実装はJavaじゃなくても良いかもしれない。むしろStruts1系からの卒業を考える際に、そもそもフレームワーク(を含めた外部ライブラリ)から如何に影響を受けないようにするのかということを考えるべきだったのかもしれません*2。少なくとも SoRなシステムであれば。SoEは当たるか外れるか分からないものを扱うものだと思うので そこまでやるのは YAGNI として良いのですけどね。

どうでも良いけど、オレオレFWを考えてみようと思ったのも外部ライブラリの影響を局所化するためには?という思考実験の延長。 クリーンアーキテクチャとかDDDとか、そのあたりの情報を知ってライブラリをラップするというのではなくアーキテクチャー的なアプローチで解決できそうだというのを知って考え始めました。

私がEEを採用した理由と実感

Springを採用するにせよ、EEを採用するにせよ 実際のところは

言ってしまえば、Struts1系が何故採用されたのか?と実質同じ。
私は標準であるという思考停止だけど、デファクトだからというのも似たり寄ったりじゃないかと。

私は標準であることを理由に使ってみることにしました。
実際に手を動かして比較して、というところまではしませんでしたが @kikutaro_ さんが大体同じプロセスを経て EE6に至っているように思いますので、以下の記事を読んでいただければ雰囲気は伝わると思います。*3

初めてのJava EE 6開発! 最初の壁をどう乗り越えた?──最新Java EE開発“虎の穴” 第1回 菊田洋一氏 - builder by ZDNet Japan

違いとして特徴的なのは JSFJPAだと思うので、EE側の利用者としての感想。

SpringMVCは、Struts1系と同じでアクション指向だから採用されて、JSFは違ったから避けられたのかな?
テンプレート的な使い方であればJSFは使いやすいと思うし、実際、私はEEを金魚本で独学しているときに、SAStrutsの案件に関わって「EEと変わんない」というくらいの違いしか感じず。 アクション指向とコンポーネント指向という違いやライフサイクルの違いは分かるけど、そんなものと思ってしまえば、まぁそんなもの。 リクエストにたいしてレスポンスをページ単位で返却する、というWebの仕組みの範疇。
紙芝居的なWebページだったらJSFに不満ないなぁ。

ただ、JSFをjQueryMoble(とjQueryAjax)と合わせ技で動的にView更新をしたら相性の悪さを感じました。 でも、これは自分の技術力不足だと思っています。*4

率直な感想としては動的ページ(SPA)はJAX-RSを使ってJSFを中途半端に使うのは辞めた方が良いです(笑)

MyBatisのSQLを直接書ける感じが好まれてJPAは避けられたのかな?JPAのステートフルなところやJPQLのクセはあるけど、別にSQLを直接書いてはダメということもないしJDBCの延長くらいの使い方であれば別に困ることは無いんだけどなぁ。 私はそこまでリッチなこともしていないから本当の苦しみは分かっていないだけかもしれません。 MyBatisを使ったことは無いけど置き換えを考えたくなるほどでもないです*5。 正直、MyBatisにするくらいなら、S2JDBCの方が良い。なので実質後継であるDoma2の方が魅力的。 JPAを使わないんだったら、私はDoma2を選ぶなぁ*6

Springがデファクト

※あくまで自分の理解なので、間違っている可能性はあります。整理としても雑なので雰囲気だけ伝わればと思います。

  1. EE(EJB2.x系)重い、WebベースだったらServletで十分
  2. Servletを拡張したStrutsは如何でしょう?(デファクトに)
  3. DIで軽量フレームワークは如何でしょう(Spring,Seasar2*7
  4. SpringMVC、JSF、Velocity、Struts2系(群雄割拠)
  5. SAStrutsが使われる*8
  6. Java EE6(JSF2.x系、JPA の採用)登場
  7. Struts1系がEOL確定
  8. EEもSpringもStrutsからの移行先として広報
  9. 移行するならStruts1系と同じアクション指向が馴染みが良いよね。
    JavaScriptによるページ制御とアクション指向は相性がいいよね。
  10. 金魚本が出るが・・・
  11. SpringBoot1.xリリース(MyBatisも使えるよ)
  12. SAStrutsのEOL確定
  13. Springが採用されデファクト

MSのASPが標準的な扱いで、Javaの標準もそれに近しいJSFが良いのでは?みたいな話をどこかで読んだような気がしますけど曖昧です。
RoRによるEoDの影響はかなり大きかったと思います。当時の私は全く知りませんでしたが、SAStrutsJSF(Java EE6)のことを調べる過程でRoRそのものと、RoRが各言語のFWに与えた影響の大きさを知りました。 Java EE6リリースの時期を確認すると、SAStrutsと同じか ひょっとしたら先だったかもしれませんが、私の当時の認識では SAStrutsが先で EoDとしてRoRなどの影響を受けて Java EE6が出た印象です。 なので、EEを先に勉強はしていましたが、SAStrutsをベースとした開発案件を受けて勉強した際に「SAStrutsを参考にJSF2.x系は作られたんじゃないのか?」と真剣に思ったくらいです。

Java EEデファクトに なれなかったのは

  • EEは重いという印象で去って行ったユーザーが帰ってくることは無かった
  • Java EE6(EoD)にアクション指向フレームワークが標準に無かった
  • 金魚本の日本語版のリリースが遅かった。
    EEの日本語情報が少なく、SpringやSAStrutsの日本語情報は多かった
  • 日本ではMyBatisが好まれた(SQLを覚えたからそれで十分?)
  • SpringBootのインパクトが強烈だった

SpringBootが無かったらSpring自体も群雄割拠の1つであった可能性はあるのでは?と思ったりもします。 そういう意味ではSpringはJavaでありがちな「環境構築の壁」をエンジニアの甘えとせず敷居を下げてユーザーのことを考えたから一気に増えたとすると、それはPivotalの努力だと思います。 それが無ければSpringがデファクトになれたのか?それくらいStrutsSAStrutsも含めて)の影響は長きにわたるものだったという印象です。*9

ちなみに日本語情報の有無については、ちょっと微妙で MyBatisは日本語書籍は ほとんど(もしくは全く?)無いんですよね。
MyBatisが使いやすいことの証左なのかもしれませんが、でも私は使用しているプロジェクトを見る限り、そこまでの嬉しさが分かりませんでした。 あえていえば、Struts1系と同じで「iBatisを使っていて、後継のMyBatisをこれまで通り使っている」というだけじゃないの?と。もちろん使い続けたいと思わせるライブラリだというのは大前提として。

私はSpringがデファクトになった理由はJavaを採用する人にとって採用しやすかったであってSpringをエンジニアが採用したからではないと思っています。
(といいつつ、この後、前言撤回しますが。)

補助資料

Springの歴史

www.slideshare.net

SpringBootとMicroservice

モノリシックなWebアプリにおいては上述の理由であると思いますが、もう1つの波があります。 みんな大好きMicroserviceです。
色々と端折りますが、UberJarの仕組みを簡単に提供したのが SpringBootだと思っています。 EE陣営もPayaraMicroやKumuluzEEが頑張っていたと思いますが、肝心のOracleは行動が遅かったです。なんだかんだといっても「標準」のオーナーである Oracleの腰が重いと EEの仕様として標準化されません。 たった数年かもしれませんが、この出遅れは結構致命的だったと思います。

この点についてはEEからエンジニアが離れたと言っても仕方ないかなと。

それらに加えて、いずれMicroService化するのでは?Webサービスは12Factorであるべきだよね?と モノリスな設計思想を捨ててもないのに*10MicroServiceで構築する必然のないシステムでも採用していきます。

Springを採用するということ

最新技術へのキャッチアップです。エンジニアとしては嬉しい限りですね。
でも気をつけてください。Pivotalの速度感に皆さんがついていくということですよ? 当然、SpringBootは2系を使っていますよね。1系そろそろEOLですよ? マイナーバージョンアップでも逐一キャッチアップ&デリバリーをするライフサイクルを開発プロセスとして組み込んでいますよね? アジャイル開発してますよね?

嫌味に煽っているわけではなくて、それがSpringを使うことの価値観だと私は思っているということです。
Strutsデファクトだからと採用していた企業*11が、Springを採用するときに そこまで考慮していれば良いのですけれど、という杞憂です*12

EEも下位互換がすべて出来るかというと、まぁそういうことはありませんが標準仕様として 良くも悪くもたくさんの関係者がいる分、それなりに配慮はされていると思います。 本当に求めているのは新規性ですか?それとも安定性ですか?

EEを採用する理由

EEを採用する理由は標準仕様だからということ以外は正直ないと思っています。
そして、私にとって それが大切だと思っています。

ツラツラと冗長に書いてきましたが、@yoshioterada ‏さんと @kawasima さんの対談が、私の言いたいことに近しいです。

「Java EE 8」の注目機能は何か? この先、Java EEはどう進化していくべきか? 寺田佳央氏と川島義隆氏に聞いた - page2 - builder by ZDNet Japan

Jakarta EEとして標準仕様として明確になりそうな(なってる?)MicroProfileにも期待です。

スターターも出てきました。

MicroProfile Starterで選べるMicroProfile Serverを調べてみた - Challenge Java EE !

心強いですね。

EEをラップしてベンダー謹製のFWにするための土台は、改めて整いつつあると言っても良いのではないでしょうか?

改めて結論

さて、EEは見捨てられるのでしょうか?
それに対する私の認識は「EJBをきっかけに離脱したユーザーもエンジニアも ほとんど帰ってきていないんじゃないでしょうか?」です。 つまり今も昔も見捨てられ続けています。身も蓋も無いですね。

でも標準仕様の仕組みはJakarta EEとして生き残りました。
私としては ここが大事だと思っています。
上述でも引用させていただいた @ceto_tさんの

EJBの延長としてのEEへの再評価は今後も無いと思いますが、Javaによるシステム構築における選択肢として EEが選ばれることはあると思います。
個人でも私のように「デファクトStrutsXML地獄はちょっと。。良く分かんないけど、標準というものを選ぼう。」みたいな人もいるでしょう。 もしも見捨てられるとしたら、そもそもJavaでシステム構築をすること自体が見捨てられた時じゃないでしょうか(つまりSpringと競っても意味がない)。

Java SEのオープン化で、ごちゃついてしまったところはあり、それで立ち去ったユーザーもいるようですが、それも私からすれば「エンジニアが採用を見送った」ではなく「ユーザーが ごちゃごちゃしていると判断して敬遠した」だと思っています。 ライセンスやサポートへの意識が高いエンジニアが あの意味を理解できないなんてことないですよね?Linuxディストリビューションがややこしいから やっぱりWindowsServerだね、というのであれば「あっ、はい」という感じですけど*13。もし、エンジニアとして Java SEのオープン化を有償化といって判断したとしたら、うーーん、、なんだろう。。「頑張れ」かな?*14

余談

これは完全に余談ですが、今回のきっかけのtweetの私のリツイート

と @backpaper0 さんに言ってもらえたのは素直に嬉しかったです。
「うらがみさんは、カンファレンスで登壇していたり、Doma2のコミッターだったり、凄い人なんだよ!」という喜びを妻に伝えましたが反応はイマイチでしたが(笑)

なぜ、嬉しいかというと、私のJavaスキルは実質 独学であり我流です。 誰も私のコードをレビューしてくれたこともありませんし、システム構築にあたって誰かに相談することも無い状態で作り込みをしました。SIer時代では開発案件のJava実装者であったことはありません。PLとして方式を検討したり、レビューや部分修正をすることはありましたが1から構築するということはありませんでした。当然、私のコードを誰かがレビューをしてくれて指摘をされるという経験はありません。 SIerを離れる数年前くらいから書籍などで学んだ 良いコードのあり方 とかオブジェクト指向とは?みたいなことを自分で勉強し、SIerを離れてからDDDとかの知識を学びました。Java EEによるシステム構築の際も元SIerのPL経験を踏まえて「もし自分がリーダーだったら、自分のコードにどういうダメ出しをするのか」みたいな禅問答のようなことを陰々とし続けて、自社システムを構築しました。 作り切った後、当然ですが「動くものは作れたけど、なんだか美しくないなぁ」と。設計思想としてアーキテクチャとして、システムとはどうあるべきなんだろう?と考えるようになり、改めてJava EEを使いながら整理をやり直すことにしました。 あわせてEEの情報は少なかったので、独学かつ我流であれ誰かの役に立つかもしれないと思いブログで情報を垂れ流すことにしました。
とまぁ、そういう経緯もあり「実務経験があるかと」と言ってもらえたのは、独学我流ではあったけど そこまで変なことはしていないと初めて実感し、しかも自分が「すごいなぁ」と思っている方に言ってもらえたという意味で非常に嬉しかったわけです。

さいごに

余談が長くなってしまいました(笑)
EEにおけるベンダーサポート云々の話も書こうと思いましたが、ベンダーとしてSpringであれ EEであれ それ以外であれ、有償サポートを受けつつフレームワークを採用するのであれば言及する意味は無いかな?と思ってやめました。*15


*1:愛のある故の危機感的なものだと思っています

*2:とはいえ、あの時期にDDDとか、そのあたりの知見は出たばかりだったような気がしますが

*3:このプロセスで @yoshioterada ‏さんを知るのは EEあるあるな気がします

*4:独学というか我流というか相談できる人がいない辛さを感じつつ力技で対処しましたが、もっと良い対処方法はあったはずだと思っています。

*5:あくまで個人的な意見

*6:ドキュメントを読む限りの理解ですが

*7:Springをラップして自社FW(TERASOLUNA)として活用する

*8:ベンダーがラップして自社FW(https://cloud.watch.impress.co.jp/docs/release/396408.html)として活用する

*9:ちなみに、まだ生き残っているという話も最近聞きました。

*10:口が滑りました

*11:エンジニアとはあえて言いません

*12:あっ、それを嫌味というのか

*13:それはそれできちんと理解して選定しています

*14:様子を見るというのであれば理解できますけど

*15:そのレベルなら Javaそのものの採用からの話になってしまう

【雑記】新紙幣発行で思うところ

新紙幣が発行されることが発表されました。
さて、これにともなって どのような論が出てくるでしょうか?
陰謀論的な発想も含めた頭の体操です。

パワーシフト論(的 陰謀論

福沢諭吉から渋沢栄一へ、ということは 経済界における慶応閥の力が弱まってきているという論。
三菱は岩崎弥太郎で、彼と福沢諭吉との 関連を鑑みるに、国としては みずほ銀行(旧第一勧銀) 推し? 三菱UFJはマイナス金利の際に国債を買わないという宣言をしたり、仮想通貨(暗号通貨?)の発行をメガバンクとして早々に打ち出したりと「反日本」と見られて 紙幣の顔から福沢諭吉が外された的な。

バブル期の総決算

バブル期(1984年)からの「顔」が、今 役目を終えたという見方。
でも新しい顔が東京証券取引所の創設者って、結局(ry。 とはいえ、渋沢栄一は 多数の企業設立に関わっているから、それを言ったら何かしらに関係してしまうので「新しい時代に どんどんビジネスを起こしていこう」と考えた方が前向きですね。

タンス預金対策(ロンダリング対策)

新しい紙幣に「電子透かし」的なものを採用することによって、古い紙幣が使えなくなる という論。
電子透かしの無い紙幣は、ある一定期間を経て 市中で使えなくなるという話。
銀行にもっていって新しい紙幣に交換しないといけない、または預金(貯金)としてしないといけない。
タンス預金(地下経済)を把握しようという流れの一環。

キャッシュレスも同じ理屈ですね(インドとか中国とか)。

日本は タンス預金率が高いと言われていますし、どうにかして把握するためには「古い紙幣は 電子透かしがないと市中では使えなくなります」という論理で あぶりだすんじゃないかなぁ、なんて。
そこまでするか分かりませんが、そこまでしたら 結構なインパクトになることは必至でしょう。

紙幣のブロックチェーン

先の電子透かしの延長。
紙幣につける電子透かしは、透かしと言うよりも ID。
IDをもって、それをブロックチェーンとして把握するという論。
預金(貯金)はマイナンバーで把握、紙幣はブロックチェーンで把握。 お釣りとして、小額紙幣は使われるけど、1万円の高額紙幣は お釣りとして出ていくことは無いので それなりにブロックチェーンの効果はあるんじゃないですかね。

仮想通貨が上がる?

ロンダリング対策の影響を受けて、グローバルな仮想通貨へのシフトがより強まるかも。
そうすると値段が上がるかもしれませんね。
(しらんけど)

新紙幣特需

紙幣読み取り関連の銘柄が上がるんじゃないですかね。
特に電子透かしという特殊性があるし、これまでとは違う気がするんですけど どうかなぁ。 (しらんけど)

無人販売機関連のキャッシュレス化

新紙幣対応として、自動販売機の入れ替えをするコストアップに対応はできないということで、自動販売機から 紙幣投入口が無くなるという話。
結果として、硬貨と電子マネー(QR決裁含めて)しか使えなくなる。 これは、割とありそうな気がします。
特に地方の自動販売機だと、紙幣のところに蓋をして使えなくなる可能性が大きいんじゃないかなぁ。
今更、新しい自動販売機に置き換えるコストをかけ無さそうな気がします。

預金封鎖

キャッシュレスとか、タンス預金の是正の延長で ありがち(?)な話として、預金封鎖もあるかなぁ。
正しくは、預金封鎖がやりやすくなるための土台になっていますよね?という感じ。
戦後 日本国債が紙切れになったようなことを 恣意的にやるためには、できるだけ 現金を預金にしておいた方が効果は大きいのは 言わずもがな、です。

円が無くなる?

(さらに ぶっ飛んだ発想)

円が無くなって ドルとか元とか 何かしらの通貨に 一括置き換えされる未来。
日本が無くなるのか、日本以外も含めて国家が無くなるのか、経緯は ともかくとして、円が無くなってしまう。
貨幣変換は 電子化されたら ボタン ポチッでおしまい。
そんなに単純なものでは無いというのは分かっていますが、まぁ、ぶっとんでいくと そういう世界もありますねという論。

その他

とりあえず、インドみたいに高額通貨(1万円)が無くしてまで キャッシュレス化を進めることはしないみたいですね。
逆に10万円札も無いということでもあるようですね。

さいごに

信じるか信じないか(ry

アクターとユースケース(権限)の実装

vermeer.hatenablog.jp

を元に アクターとユースケース(権限)の実装をしてみました。
まだサービスやコントローラーへの適用はしていません。

想定する要件

アクターが複数あって、それぞれに権限がありそうなものということで「申請フロー」を題材にしました。

状態遷移図

f:id:vermeer-1977:20190302104539p:plain

ユースケース

f:id:vermeer-1977:20190302104506p:plain

「編集」については、状態変更を伴わないということもあり 除外して、それ以外の 状態遷移のトリガーとなる振る舞いを 基本的に そのままユースケースとして採用しました。

ツッコミどころや補足など

申請者と承認者を継承関係にしていますが、正直 自分自身「うーん」と思っています。 要件としての役割だから継承関係にしない方が正しい整理だと思うんですよね。 継承すべきものがあるとしたら「社員」みたいなアクターのような気もするんですけど、今回はアクターの継承を含んだ実装例を試したかったということで目をつぶってください。

状態が終了することは無いの?という状態遷移になっていますが、これは一応、意図的なものです。 イミュータブルデータモデル的なことを想定していて、すべての状態を履歴として管理する、という意図です。

実装(インターフェース)

アクター

public interface ActorType {

    public Set<UsecaseType> usecases();

    public default ActorType parentActor() {
        return null;
    }

    public default Set<UsecaseType> recursiveScan(ActorType actor, Set<UsecaseType> usecases) {
        if (actor == null) {
            return usecases;
        }
        usecases.addAll(actor.usecases());
        return recursiveScan(actor.parentActor(), usecases);
    }

    public default boolean hasUsecase(UsecaseType usecase) {
        return this.usecases().contains(usecase);
    }

    public default boolean anyMatch(Set<UsecaseType> usecases) {
        return this.usecases().stream().anyMatch(usecase -> usecases.contains(usecase));
    }

}

ユースケース

ユースケースは、アクターと違って 永続層などの値から ユースケースEnumへの変換が必要だというところです。 ということで、変換用のユーティリティとしても 本インターフェースを使います*1

public interface UsecaseType {

    public Object code();

    public static <E extends Enum<E> & UsecaseType> Optional<E> codeOf(Class<E> enumType, Object code) {
        return Arrays.stream(enumType.getEnumConstants())
                .filter(e -> e.code().equals(code))
                .findFirst();
    }

}

実装(具象クラス)

実装例ということで、分かりやすさ重視で 上述の ユースケース図や状態遷移図の日本語表現を そのまま使っています。 実際の作成の順番としては、ユースケースEnumをつくって、アクターのEnumを実装します。

アクター

public enum Actor implements ActorType {
    起票者(起票, 申請, 取下),
    承認者(差戻, 承認) {
        @Override
        public Actor parentActor() {
            return 起票者;
        }
    };

    private final Set<UsecaseType> usecaces;

    @Override
    public Set<UsecaseType> usecases() {
        return this.usecaces;
    }

    private Actor(UsecaseType... usecases) {
        Set<UsecaseType> _usecases = (usecases == null || usecases.length == 0)
                                     ? Collections.emptySet()
                                     : new HashSet<>(Arrays.asList(usecases));
        this.usecaces = this.recursiveScan(this.parentActor(), _usecases);
    }

}

承認者は、起票者を拡張したアクターであることを parentActorで表現しています。
あとは各アクターに関連するユースケースを記述するだけです。

再帰的にアクターに紐づくユースケースを取得するためにコンストラクタでthis参照をしているところが、ちょっと気にはなっています。
インスタンスフィールドを参照しているわけではないので、大丈夫かなぁと思っていますが、もし問題があるようなら、アクターに関連付けされているユースーケースを取得するメソッド(hasUsecase)で都度 階層情報をさかのぼって ユースケース情報を参照するようにしないといけないかもしれません。一応、ユニットテストでは 想定通りの挙動をしています。

ユースケース

public enum Usecase implements UsecaseType {
    起票, 申請, 取下, 差戻, 承認;

    @Override
    public Object code() {
        return this.name();
    }

    public static Optional<Usecase> codeOf(Object code) {
        return UsecaseType.codeOf(Usecase.class, code);
    }

}

永続層などで保持しているコード値はUsecaseのEnum列挙子と同値という前提です*2
Enumクラスの valueOfの コード値版として、static な codeOfメソッドを準備します*3

使い方の例(テストコード)

public class ActorTest {

    @Test
    public void hasUsecasesAt起票者() {
        Set<UsecaseType> usecases = Actor.起票者.usecases();
        Set<UsecaseType> expects = new HashSet<>(Arrays.asList(起票, 申請, 取下));
        Assert.assertTrue(usecases.containsAll(expects));
    }

    @Test
    public void hasUsecasesAt承認者() {
        Set<UsecaseType> usecases = Actor.承認者.usecases();
        Set<UsecaseType> expects = new HashSet<>(Arrays.asList(起票, 申請, 取下, 差戻, 承認));
        Assert.assertTrue(usecases.containsAll(expects));
    }

    @Test
    public void hasUsecase() {

        Assert.assertTrue(Actor.起票者.hasUsecase(取下));
        Assert.assertTrue(Actor.承認者.hasUsecase(取下));

        Assert.assertFalse(Actor.起票者.hasUsecase(差戻));
    }

    @Test
    public void anyMatch() {
        Set<UsecaseType> items = new HashSet<>(Arrays.asList(差戻, 承認));
        Assert.assertFalse(Actor.起票者.anyMatch(items));

        Assert.assertTrue(Actor.承認者.anyMatch(Actor.起票者.usecases()));

    }

    @Test
    public void convertUsecaseEnum() {
        Assert.assertEquals(Usecase.codeOf("取下").get(), Usecase.取下);
    }

}

さいごに

Actorとユースケースの実装については、だいたいこんな感じで良いかな と思います。
次は これらのEnumを実際に サービスやコントローラーで使ってみて、使い勝手を検証していきたいと思います。


*1:EnumのInterfaceに共通処理を実装する - システム開発で思うところ に 少しStreamでアレンジを加えて実装しました。

*2:手抜きですが意図は伝わると思います

*3:実際の実装に近づけたいので こちらは手抜きをしません

【考察】アクター/ロールについて考える

vermeer.hatenablog.jp

vermeer.hatenablog.jp

への継続考察みたいな感じです。

ログインユーザーの持つ役割(権限)を どうやって 振る舞い(ユースケース・サービス)で表現したら良いのかな?という考察です。

権限と役割の文脈だと、アクター、ロール、パーミッション など、いくつかの表現で示されているように思います。

今回は、実装を踏まえつつ、自分なりに整理をすることで、実装(設計)において迷いを持たないようにすることを目的とした考察とも言えます。

アクター(辞書的な(UML的な)ところ)

- UML超入門_第2章

機能を利用するユーザや、システムが使用するハードウェア、外部システムがアクターとして表現されます。 アクターはシステムの一部ではありません。

ドメインそのものにならないようにする、というのが大事なところという理解です。
やりがちなところとしては、アクターとしての「利用者」があったとして、それを ドメインとして採用すると 全ての集約のルートが 「利用者」になってしまい、巨大な集約をモデリングしてしまうという感じと言えば良いでしょうか?

アクターの権限により起こり得るユースケースから導出される動詞とかコトをモデリングするようにしたら、多分大丈夫です。

ロールと権限

業務システムにおけるロールベースアクセス制御 - Qiita

色々と参考にする中で、やっぱり いつも この記事に戻ってきます。
ザックリいうと、アクセス制御は 権限で指定しましょう、ということになるという理解です。 じゃぁ、ロールは? というと、これもザックリいうと ラベルとかグループとか分類とか、そういうものなので アクセス制限とは 直接的には関係を持たせない方が良い、ということになります。

アクターとロール/権限

印象としては、アクターとロールは 意図しているものは同じように思います。
ちなみに、私は、同じだと考えていました。
そして整理が出来ず悩んでしまいました。*1

Serviceの実装を ユースケース記述のように、アクターとユースケース(振る舞い)によって実装すると、意図の伝わるドキュメントのような実装になると考えていたからです。
アクターとロールが同じという整理では、ロールによるアクセス制限で起こり得る課題を避けられないことになります。

ということで、もう少し 考えてみる必要がありそうです。

先のブログにも 書かれていたことを参考に あれこれ考えてみます。

別途ロールを設計し、組織とマッピングするようにしましょう

ロールという業務的権限の集合

アクターはユースケースの主語なので、業務的権限ではありません。
この文脈では ロールという表現がしっくりします。

権限制御したいオペレーションをグルーピングして、権限として設計するようにしましょう

ここでグルーピングしたものは ユースケースに近しいように考えます。
この文脈では アクターという表現がしっくりします。

ふむ。。。

  • ロールはラベル(システムにおけるアクセス権限に使わない)
  • 権限はユースケース(もしくはグルーピングしたオペレーション)
  • アクターは主語(ロールは システムにおける主語にはならない)

というので、どうでしょうか?

もう少し考えてみます。

ロールはラベル

ラベルはシステムに柔軟さを与えてくれます。

ラベルによる柔軟さ とは、例えば ユーザー登録をする際に 付与する区分として使って、また権限を割り充てた新たに追加も出来るということです。

ロールはラベルとして任意に編集できます(柔軟さの提供)。

権限はユースケース

ユースケースのサイズはアクターから見て、1つのユースケースを終了すれば目的が達成されており、かつ1つのユースケースの中では中断が行われないことを目安にします

とあります。

権限制御したいオペレーションをグルーピングとして、ユースケースは良さそうです。
また、今回の整理とは直接関係は無いのですが、サービスとユースケースを明確に分けて整理できるところも良さそうです。

ユースケースはシステムが提供する価値なので、ラベルのように編集するものではなく、システムの価値として定義(設計)します。

アクターは主語

アクターはシステムの利用者(外部)として ユースケースの主語として表現します。

アクターもユースケースと同様に 編集するものではなく、定義(設計)するものです。 こちらも、機能に対する要件の厳密さを表現します。

さらなる考察

自分なりに 納得ができる整理が出来てきつつあります。
では、もう少し 具体的に実装を踏まえた考察を進めていこうと思います。

コードによる表現対象

ロールはラベルなので、コードによる静的な表現対象から外します。
ユースケース(という名前の権限のグルーピング)と、アクター(という名前のユースケースの関連)が実装(つまり定義)の対象になります。

レイヤー(パッケージ)

結論:アプリケーション層

当初、アクターはシステムの外を定義したものなので、全てのレイヤーから参照できうる層として、ドメイン層が良いかな?と考えました。 ですが、アクターは、

  • ドメイン知識そのものではないのではない
  • 振る舞い(ユースケース)と関連付けされる(依存させる)ものである

ということで、アプリケーション層配下に配置するのが妥当だろうと考えました。
またアプリケーション層であれば、プレゼンテーション層から参照できる、ドメイン層から参照できない という制約も 目的に合っていると思います。

アクター

アクターの種別は、Enumで実装したいです。

ただ、Enumそのものは 継承が使えません。
UMLの表現としてアクターは 汎化や 包含を 用います。
インターフェースを使ったら、表現できうる実装になりそうな気もしますが、そうすると アノテーションで指定しづらくなるような気もするし。。

とはいえ、権限が主たる関心事であると割り切れば、あとは差分の表現さえできれば十分な気もします。 実際、UMLによる汎化と包含について「これが正しい」という厳密な基準について言及されている記事を見つけることも出来ませんでした。 また、そこの厳密さの基準を設けても、自分自身 その基準が妥当なのか 数日経てば変わるような感覚もあり、とりあえず 何かしらの方法で差分表現ができるような実装が出来ればよいだろう、という結論に至りました。

ユースケース(権限)

こちらも Enumで実装したいです。

権限のグルーピングとしてユースケースという表現を考えていますが、ユースケースもアクターと同じく 差分の表現(汎化や包含)があります。
ですが、今回の主たる関心事は ユースケースを実現するフローの差分ではなく、アクターとユースケースの関連のなので、アクターであったような課題は 考える必要は無さそうに思います。

実装

アクター、ユースケース(権限) どちらとも Enumで実装します。
クラス名を、Actor、Usecase として 詳細を列挙子として実装し、アプリケーション層のルートパッケージ直下に配置します。

アクターとユースケース(権限)

関連を示すクラスを別途設けるのではなく、アクターの要素として定義します。

サービス

クラスアノテーションとして、サービスの主語として アクターを宣言します。
アクターは複数指定することも可能です。
こうすることで、ユースケース記述に近しいものを サービスの実装で表現します。

ログインユーザーの権限と アクターに関連付けられた ユースケース(権限)を 用いて、当該サービスの実行可否を判定します。

アクセス制限の指定として、サービスはユースケースを構築する振る舞い(つまり部品)ということで ユースケースを使うことも想定できます。 もちろん「あるサービスは、あるユースケースの部分である」という実装表現もあるとは思いますが、アクターを主語として サービスを述語とする 実装と比べてみて、 実装から理解できる文脈において 優位性を感じることが出来ませんでした。

したがって サービスにおけるアクセス制限は、アクターだけにします。

コントローラーでのアクセス制限

アクセス制限は、システムの中と外の境界となる コントローラーにも設けることも想定されます。 実際、これまで私もアクセス制限を設けるとしたら、コントローラーに実装していました。

ですが、サービスをユースケース記述のように実装する、という考えから、アクセス制限に相当するものは サービスにおけるアクターで表現した方が分かりやすいという考えに変わりました。
とすると、コントローラーで行うアクセス制限は どうするのが良いでしょうか?
結論としては、コントローラーのアクセス制限は ユースケース(権限)で行うです。

理由は、操作は ユースケースの部分である、という文脈で実装をしたいからです。
呼び出すサービスを振り分ける場合も権限(ユースケース)を用いて 分岐をします。*2
また、アクターの使用を避ける理由として、アクターは条件分岐に使えない、ということもあります。

アクターは分岐条件に使えない

少し、この点についての考察を追記しておきたいと思います。
ログインしたユーザー情報から、把握できるのは ロールおよび、それに紐づく権限(ユースケース)だけです。

アクターの代替としてロールは使えません。 ロールは、アクターと異なり、業務的権限の集合のような 全く違う基準で決めるもので あくまでラベル(分類)です。

では、権限(ユースケース)からアクターを導出するというのはどうでしょうか。
例えば、ロールに関連付けられた 権限(ユースケース)が、アクターのユースケース(権限)と完全一致していたら、目的のアクターであると見做せるでしょうか?
それは無理があります。
述語の集合が一致しているからといって、主語は特定できません。犬は走りますが、走れたら犬である とはなりません。

ということで、導出できないアクターを、コントローラーで 呼び出すサービスを分岐させるような処理をしたい場合の条件として使うことは出来ない、ということになります。

アクセス制限が無かったら?

使用用途としては、アクセス制限のための定義なので、特に そのような制限が無ければ、アクターとユースケース(権限)の実装は不要かもしれません。
ただ、私としては 要件として定義した時点で、コードに落としておいて 制限が必要になったら使用するというのでも良いかな?と思っています。

まとめ

参考資料

Bounded Context パターンの実践方法は? | システム設計日記

https://qiita.com/suke_masa/items/1473450a8b59eea5f3cd

アクターとは - たまゆら雑記

さいごに

うーん、、もったいぶった割に当たり前のことを書いただけになった気がしなくもないですが、私としては スッキリしました。

アクターって、システム境界を決めるために大切である、ということは 周知の事実だと思うけど、それをコードとしてどうやって表現するのか?っていうのは あんまり見ないなぁ、と思ったり思わなかったり。
そもそもシステムの外部なのだから コードで実装するものではない、ということなのかなぁ?
でも、何を外部とするのか、外部をどう定義して どう捌くのか、っていうのは システムの内側で それなりに判別しないといけないと思うんですけど、どうなんですかね?

さて、次は この整理を踏まえて実装例を まとめようかな。

*1:知り合いに、このことを話したら「言葉通り、全く違う印象しかないんだけど・・」と言われてしまいました。。

*2:具体的には「ブログ:業務システムにおけるロールベースアクセス制御」で例示されている通りです。

ログインユーザーの管理

ログインしたユーザー情報を管理するにあたって必要そうなことの整理。

順不同で、あれこれ。

各種部品を作ったりするための整理。

EEとかSpringの認証ライブラリは?

とりあえず、各種ライブラリを 使わないで 自前のForm認証から試してみようかと考えています。
今回のテーマは、認証認可をするための仕組み というよりも、ログイン後の権限に対する仕組み を考えてみようと思っているからです。

イントラネット(プライベート)だったら 自前のものや、Firewallの後ろだからBasic認証でも良いかなと思うし、インターネット(パブリック)だったら、自前でログインによる認証・認可を管理するよりも Googleとかのサービスを使うことになると思っています。
つまり、仕組みとして注力するのは ライフサイクルや アクセス権限のことを考える方を優先した方が良いかな?と。

名前付け

ログインユーザーとか、セッションユーザーとか、そんな感じ。
操作者の情報という意味を鑑みて「ClientAgent」にしようと思っています。

ライフサイクル

Scopedアノテーションを付与したクラスと 振る舞いを行うクラスは別にして、ライフサイクルだけを管理するものにしておく。

今回はセッションスコープ

リクエストスコープにして、都度 永続化情報やキャッシュへ問い合わせるというやり方もあると思います。
(可用性の高い仕組みを設けたい場合は、こっちかな?)
アクセスキーは、ログインIDか、そこから生成したハッシュ値を使うイメージ。

ログアウト

もしくは、セッション破棄。
これはクライアントが関係するので、JavaScript による close をトリガーとして サーバーサイドの操作をします。

セッションハイジャック対策

ログインしたところで、セッションIDを書き換えます。
自前のForm認証じゃない場合でも、使えるような仕組みにしたいところですが、そのためには Basic認証とか コンテナによる認証機構を使うとか、そのあたりも調べないといけないので 後回しにするかも。

パスワードのハッシュ化

生パスワードでチェックをするのではなく、ハッシュ化したもので判定をする。
SALTとか使って云々をするところとか、そのあたりもかな。
一応、自前のForm認証用のUtilityとして作るところを最低限と言う感じかなぁ。
この機能は ユーザー登録みたいなところでも使えるような可搬性も踏まえた作りにしておきたいところ。

認証と認可を分ける

若干YAGNIな気もするけど、OAuth2を使った仕組みへの連携とか、そのあたりを ある程度 考慮した仕組みも考えておきたいところ

権限判定

アクションに対するロールとか、そのあたりの仕組み。
悩んでいるところとしては、アクションに対するロールという整理が良いのか、アクターとユースケースとする整理が良いのか、というところ。

これについては、デブサミで 偶然 良い視座をいただきました。

リダイレクト

ログイン画面へのリダイレクトさせるケース

未ログイン

操作に対して、そもそもログインしているか、していないか、というレベルの話。
権限が付与されていない状態での操作、というものも これに含まれるかな。
OAuth2の発想が参考になりそうな気がします。

タイムアウト

セッションタイムアウトしていたら、再ログインを促します。
未ログインとの違いは、長時間の操作をしていなかったことを ユーザーに通知してからログイン画面に遷移させるか、いきなりログイン画面に遷移させるか、というところ。
自分の観測範囲では、遷移パターンはサービスによって異なるように思います。

  • ログイン画面へタイムアウトメッセージを添えて遷移する
  • タイムアウトした旨を伝える画面に遷移して、そこからログイン画面へ遷移する

と思ったけど、機能としては タイムアウトを検知時に 遷移する先の遷移先を指定するだけだから、実質一緒かな?

権限の異なる操作

異なる権限のユーザーで、URLを直接実行するケース。
悪意が無ければ普通は無さそうなケースだけど、だからこそ外せないところ。
通常なら URL Filterで制御するようなところです。
なので、Serviceで権限制御をしようとすると、この点については 対処できないケースがあるような気がしています。 ただ、適切に権限指定すれば 大丈夫な気もしています。

ログイン前後の情報喪失を防ぐ

具体的には、ログイン前にカートに入れた商品を いざ購入しようとしたら ログインを促されて ログインしたらカートの中身が空になるみたいなケース。
なんだけど、これは そもそも可用性の話であって権限とは話が違うので除外。

考えようとしたことのメモとしては

  • カートに商品を入れる という操作は 商品購入というユースケースとして 購入者権限が必要(=ログインしないといけない)として、ログイン画面を介在させる
  • 購入する という操作前後で カートの情報が喪失しないように引き継ぐ

セッションハイジャックのことを考えると*1、そもそも後者って どうやってやっているんだろう?という感じです。
セッションではなく、別のトークンみたいなものをもたせて、セッションとは異なる キャッシュで管理している?
クライアントに保持しておいて 毎回 全部引き回す?

参考情報

以前の自分の認証認可のメモのリンクは、一通り 改めて 読み直しておきたいところ

vermeer.hatenablog.jp

*1:セッションIDを書き換えるだけだから使えるのかな?