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

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

E2Eテストのメモ

Selenide

Selenide: concise UI tests in Java

Selenideを試行錯誤しながら実践するブラウザ自動テスト

Selenide~Javaで超簡単・簡潔にUIテストを書く~ #Java - Qiita

Selenide入門 #Java - Qiita

Seleniumで擬似要素を扱うためのTips #Java - Qiita

speakerdeck.com

Spock

https://qiita.com/kimromi/items/2368d6f004666e87b313

JGiven

JGiven で 100% Pure Java BDD(導入編) #Java - Qiita

テストのありかた

Selenium(Java)でブラウザテストをするときにやっておくべき3つのこと - ISOROOT Tech Blog

E2Eテストの導入から学んだこと #Node.js - Qiita

E2Eテストについて考えてみた|デロイト トーマツ ウェブサービス株式会社(DWS)公式ブログ

speakerdeck.com

WebアプリケーションでのE2Eテストの取り組みについて. コイニーでWebアプリケーションを担当している、フロントエンジニアの渡邊です。 | by miyuki | Medium

システムテスト自動化カンファレンス2017-2(2017/12/10)に行ってきました #stac2017 - テストウフ

www.slideshare.net

www.slideshare.net

https://www.slideshare.net/hirokotamagawa/20131201-lt

www.slideshare.net

www.slideshare.net

www.slideshare.net

www.slideshare.net

PageObject

https://javaworld.helpfulness.jp/post-229/

http://softwaretest.jp/labo/tech/labo-292/

Java製SeleniumラッパーのSelenideを使ってみた #Java - Qiita

www.slideshare.net

テスト自動化研究会

テスト自動化研究会 - YouTube

その他、思ったことをツラツラとつぶやいたもの

Java EE(JSF)でDDDのようなことを考えてみるための土台

Formに関して色々と考えている中で、部分的に考えるのも良いけれど、一度、ベタで自分なりにJava EEJSF)で、DDDっぽい感じの実装サンプルをつくってみて もうちょっと全体を見渡せるようなものを作ってみた方が、もう少し具体的に考察出来るのかなぁと思うに至りました。

仕様・参考

仕様というか、要件というか、そのあたりについて 参考にさせていただいたものは

GitHub - system-sekkei/isolating-the-domain: Spring Boot : gradle, Spring MVC, Thymeleaf, MyBatis and Spring Security sample

です。

但書

  • 値検証・例外処理・セキュリティ とか全く実装していません。

  • 目下の目的がFormなので、JPA周りも いったん無しで、オンメモリで疑似的なものです。

  • 単純に画面からの入力を 疑似的にCRUDする ざっくりとしたものです。

  • 性能も当然(?)意識していません。

  • 考察のスタート地点としてlombokなど使わずに、ベタで実装したものです。

  • 考察のための入り口なので、これがJSFの普通の実装では確実にありません。*1

構成

過去に考えた、以下をベースに作成しています。

vermeer.hatenablog.jp

vermeer.hatenablog.jp

それ以外の詳細で、見直したところだけを以下に追記します。

Presentation

個々の部品となるFormはサブフォルダでまとめる

当初は、Domainのmodelパッケージと同様に 1つのフォルダに まとめれば良いだろう、と思っていました。
ですが 部品となるFormと画面ルートFormとActionが全て同じパッケージ配下に存在すると クラスが多すぎて わかりにくく感じました。
とりあえず、画面ルートとなるFormとActionというようなScopeを管理するクラスと 個々の部品は別パッケージにしました。
ちょっと悩んだのは、画面ルートとなるForm内で繰り返しの要素として使用するFormです。
部品と言えば部品ですが、集約と言えば集約です。
いったん、Actionなど 画面仕様に特化したものとして 画面ルートとなるFormと同じフォルダに格納しましたが、この辺りは また考え直す可能性があります。

xhtmlは会話スコープ

ブラウザから参照するxhtmlは同一会話スコープとなるものを同じフォルダで管理します。

といっても、今回はConversationScopedではなくSessionScopedを使って手抜きをしています。

ConversationScopedについては、以前 作ったライブラリを適用する形で 後日リファクタリングするつもりです。

とりあえずは、どういう感じで資産を管理するのか、ということを整理するに留めます。

templateを使う

ざっくりとはいえ、今回はtemplateを使って ある程度 JSFの基本的な作りに近しい感じにしました。

各ページの具体的なコンテンツはWEB-INF配下に整理します。

JSFで どの程度 有効なのか分かりませんが Servlet/JSPのように、直接コンテンツとなるxhtmlを参照できないようにしておいた方が セキュリティ的に良さそうに思っているので そうしました。*2

HTML Friendly な実装

xhtmlをEEサーバーを介さず、直接ブラウザで閲覧しても問題なくデザインを確認できるようにしています。

テンプレートエンジンは、thymeleaf だけじゃないですよ、というサンプルくらいには なるかな?と思います。

この後 類似部分について 更に 部品化していくと 結局 EEサーバーを介してデザインを確認しないといけなくはなるのですが、まぁ それは仕方ないかな、と。

とりあえず、初期デザイン時に作成したhtmlを ほとんど そのまま使えるというのは良いですね。

Action

validationもしていません。

仕様に合わない入力をしたら 即例外がスローされます。

今回は、あくまで実装の流れを整理する事だけしかしていません。

Application

特筆することは ありません。

単純なCRUDなアプリにアプリケーションサービスでやることって、RepositoryをCallすることだけ(と、Transaction)だけなんですよね。

Infrastructure

今回はDBを使わず、ダミーテーブル的なインスタンスApplicationScopedで設けました。

とりあえず、Repository周りのイメージを整理することだけをゴールとしています。

Code

Bitbucket

考察

仕様

仕様そのものについてですが、注文・発注など一般ユーザーが使う場合はタスクベースの画面構成であるのは正しいと思います。

ちゃんと2重Submitも制御しないといけない要件だと、この構成にしておかないと事故につながります(つまり正しい構成)。

ただ、自分が求めているのは もっと偏ったエクセルをそのままWebにしたみたいな画面です。

ざっくりいうと、一覧を直接編集して更新する、みたいな画面構成です。

同一画面上で、いくらでも好きなだけ更新もするし、2重Submitなんて気にせず 何度でも上書き更新するような雑な画面です。

もっといえば、大量の項目を入力する なんでも画面です。

最終的なオレオレラッパーフレームワークの仕組みとしては「正しい要件」を満たすのはもちろん、「使用者が限定的な業務システム」もカバーしたいという感じです。

冗長さが半端ない

過剰にも見えるクラス群と、右から左へのデータを写すだけのコードが異常に多いというのが 我ながらの感想です。

このあたりについては、AnnotationProcessorとか コード生成をするか、そもそも方式を含めて見直す必要もあるかも、と思っています。

viewActionが動かない?

なぜか、f:viewActionが動かなかったです。

仕方が無いので、とりあえずf:event type="preRenderView"を使いました。

他の お試し実装をするときには問題なく使えたんだけどなぁ、なんでだろ?

さいごに

まずは、なんとなく、こんな感じかなぁを作りました。

今後は、これに対して 色々と小細工を追加するか、「なんでも画面」のサンプルを先に作るか、どちらにしようかな?

*1:そもそも、JSFは画面単位でManagedBeanを作りましょう、というのが王道のようですから、私のような分割自体が なにそれ?と言われる可能性はあります。ただ、私にとっては こちらの構成の方が 分かりやすいのです。

*2:最終的に生成されるxhtmlの構造はクライアントで確認できるので、どのくらい効果的なのか不明です。

JSFのラジオボタン

ラジオボタン(SelectOneRadio)を使うことを個人的に あまり使うことがなくて 特に調べていなかったのですが、今 ちょっと やっていることで たまたま出てきて いざJSFで やろうとしたときに 色々と気になったので まとめてみることにしました。

あくまで、私が試した範囲ですが、正直な感想としては「出来そうで出来ないことが結構あるんだなぁ」ということが分かりました。

やりたいこと

  • JSFラジオボタンを使った画面操作をしたい

  • JSFのタグと、HTML Friendlyの両パターンの実装を比べたい

  • シンプルな選択

  • テーブル列の単一行選択

シンプルな選択

HTML

目標とする表現のHTMLは以下

<div>

    <h2>Pure HTML</h2>

    <div  style="float: left">
        <div>
            <input type="radio" value="MAN" name="gender" id="gender0">
            </input>
            <label for="gender0">男性</label>
        </div>
    </div>
    <div style="float: left">
        <div>
            <input type="radio" value="WOMAN" name="gender" id="gender1" />
            <label for="gender1">女性</label>
        </div>
    </div>
    <div style="float: left">
        <div>
            <input type="radio" value="OTHER" name="gender" id="gender2"/>
            <label for="gender2">不明</label>
        </div>
    </div>

</div>

JSF(Not HTML Friendly)

<h:form>
    <h:selectOneRadio value="#{genderForm.genderValue}">
        <f:selectItems value="#{genderForm.selectItems}"/>
    </h:selectOneRadio>

    <p>genderValue</p>
    <p>#{genderForm.genderValue}</p>

    <h:commandButton action="#{genderAction.update()}" value="更新"/>
</h:form>

ActionForm

私はActionとFormを分けて実装する派です。

xhtmlで参照できるように、SelectItemのListへEnumの情報を設定します。

@Named
@SessionScoped
public class GenderForm implements Serializable {

    private static final long serialVersionUID = 1L;

    private GenderType genderType;

    @PostConstruct
    public void init() {
        this.genderType = GenderType.MAN;
    }

    public List<SelectItem> getSelectItems() {
        List<SelectItem> items = new ArrayList<>();

        for (GenderType _genderType : GenderType.values()) {
            items.add(new SelectItem(String.valueOf(_genderType.getValue()), _genderType.getDisplay()));
        }

        return items;
    }

    public Integer getGenderValue() {
        return this.genderType.getValue();
    }

    public void setGenderValue(Integer genderValue) {
        this.genderType = GenderType.createGenderType(genderValue);
    }
}

Enum

public enum GenderType {

    MAN(0, "男性"),
    WOMAN(1, "女性"),
    OTHER(2, "不明");

    private final Integer value;
    private final String display;

    private GenderType(Integer value, String display) {
        this.value = value;
        this.display = display;
    }

    public Integer getValue() {
        return value;
    }

    public String getDisplay() {
        return display;
    }

    public static GenderType createGenderType(Integer value) {
        for (GenderType _genderType : GenderType.values()) {
            if (_genderType.getValue().equals(value)) {
                return _genderType;
            }
        }
        return GenderType.MAN;
    }

}

Action

リダイレクトもしないし、自画面遷移なので 何もしない空のアクションだけ。

Injectはコンストラクタインジェクションで。

@Named
@RequestScoped
public class GenderAction implements Serializable {
    private static final long serialVersionUID = 1L;

    private GenderForm genderForm;

    public GenderAction() {
    }

    @Inject
    public GenderAction(GenderForm genderForm) {
        this.genderForm = genderForm;
    }

    public void update() {
    }
}

JSF(HTML Friendly)

アクショントリガーとなるボタンについては、今回のメインではないので、JSFのタグで記述しています。

まずは あえて ui:repeatは使わずに、そのままブラウザで表示しても 出力イメージに近い結果になるようにしています。

<h:form>

    <div  style="float: left">
        <div>
            <input type="radio" jsf:id="#{genderForm.targetId(0)}">
                <f:passThroughAttributes value="#{genderForm.checked(0)}"/>
                <f:ajax event="click"  listener="#{genderAction.change}"/>
            </input>
            <label for="#{genderForm.targetFor(component.clientId, 0)}">
                #{genderForm.targetLabel(0)}
            </label>
        </div>
    </div>
    <div style="float: left">
        <div>
            <input type="radio" jsf:id="#{genderForm.targetId(1)}">
                <f:passThroughAttributes value="#{genderForm.checked(1)}"/>
                <f:ajax event="click" listener="#{genderAction.change}"/>
            </input>
            <label for="#{genderForm.targetFor(component.clientId, 1)}">
                #{genderForm.targetLabel(1)}
            </label>
        </div>
    </div>
    <div style="float: left">
        <div>
            <input type="radio" jsf:id="#{genderForm.targetId(2)}">
                <f:passThroughAttributes value="#{genderForm.checked(2)}" />
                <f:ajax event="click" listener="#{genderAction.change}"/>
            </input>
            <label for="#{genderForm.targetFor(component.clientId, 2)}">
                #{genderForm.targetLabel(2)}
            </label>
        </div>
    </div>

    <br />
    <p>genderValue</p>
    <p>#{genderForm.genderValue}</p>

    <br />
    <h:commandButton action="#{genderAction.updateRadio()}" value="更新"/>
    <br />
    <h:commandButton action="#{genderAction.confirm()}" value="確認"/>

</h:form>

f:passThroughAttributes

checkedvalueで値を設定できません。

仕方が無いので、ロジックで動的に出力します。

f:ajax event

ラジオボタンの選択がcheckedでしか表現できないために ボタンをチェックする都度、サーバー側のBeanを更新します。

checkedvalueで値を設定できないのと同様に、取得もできないためです。

仕方が無いパート2です。。

別にAjax操作の後に画面表記を変える必要は無いのでrenderは不要です。

シンプルな選択(ui:repeat)

繰り返しを使って動的にラジオボタンを出力します。

<div style="float: left" jsfc="ui:repeat" value="#{genderTypeItemsForm.items}" var="genderTypeForm" varStatus="stat">
    <div jsf:rendered="#{genderTypeItemsForm.renderChecked(stat.index)}">
        <input type="radio" jsf:id="radioOn" pt:name="radioJSF" value="#{genderTypeForm.genderTypeValue}" checked="checked">
            <f:ajax event="click"  listener="#{genderRepeatAction.change(stat.index)}"/>
        </input>
        <label for="#{genderTypeItemsForm.forTargetRadioOn(component)}" >
            #{genderTypeForm.display}
        </label>
    </div>

    <div jsf:rendered="#{genderTypeItemsForm.renderChecked(stat.index)==false}" class="designOnly">
        <input type="radio" jsf:id="radioOff" pt:name="radioJSF" value="#{genderTypeForm.genderTypeValue}">
            <f:ajax event="click"  listener="#{genderRepeatAction.change(stat.index)}"/>
        </input>
        <label for="#{genderTypeItemsForm.forTargetRadioOff(component)}" >
            #{genderTypeForm.display}
        </label>
    </div>
</div>
@Named
@SessionScoped
public class GenderTypeItemsForm implements Serializable {

    private static final long serialVersionUID = 1L;

    private List<GenderTypeForm> items;

    private GenderType genderType;

    @PostConstruct
    public void init() {
        List<GenderTypeForm> _items = new ArrayList<>();
        _items.add(new GenderTypeForm(GenderType.MAN));
        _items.add(new GenderTypeForm(GenderType.WOMAN));
        _items.add(new GenderTypeForm(GenderType.OTHER));
        this.items = _items;
        this.genderType = GenderType.MAN;
    }

    public List<GenderTypeForm> getItems() {
        return items;
    }

    public void setItems(List<GenderTypeForm> items) {
        this.items = items;
    }

    public Map<String, String> checked() {
        Map<String, String> map = new HashMap<>();
        map.put("checked", "checked");
        return map;
    }

    public boolean renderChecked(Integer itemIndex) {
        return Objects.equals(this.genderType.getValue(), this.items.get(itemIndex).getGenderTypeValue());
    }

    public String getDisplay() {
        return this.genderType.getDisplay();
    }

    public void updateGenderType(Integer index) {
        this.genderType = this.items.get(index).getGenderType();
    }

    public String forTargetRadioOn(UIComponent component) {
        return component.getParent().getClientId() + "-radioOn";
    }

    public String forTargetRadioOff(UIComponent component) {
        return component.getParent().getClientId() + "-radioOff";
    }

}

チェックのついたラジオボタンと ついていないラジオボタンを2つ準備しておき、出力するタグを分けます。

先と同じように checkedvalueで扱えないための苦肉の策です。

Label の for 指定をIDと同じ値にするためのメソッドforTargetRadioOnforTargetRadioOff も 引数を2つ以上指定出来なかったための苦肉の策です。

でも、別の実装お試しをしている時には出来たようなケースもあったので、多分 何か対応が不十分なだけな気もします。

テーブル列の単一行の選択

HTML

目標とする表現のHTMLは以下

<table>
    <thead>
        <tr>
            <th></th>
            <th>商品</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td><input type="radio" value="AAAA" name="item" id="item0" checked="" /></td>
            <td>AA AA</td>
        </tr>

        <tr>
            <td><input type="radio" value="BBBB" name="item" id="item1"/></td>
            <td>BB BB</td>
        </tr>


        <tr>
            <td><input type="radio" value="CCCC" name="item" id="item2"/></td>
            <td>CC CC</td>
        </tr>
    </tbody>
</table>

JSF(Not HTML Friendly)

dataTableでの 良いやり方が見つけられませんでした。

pass-through でやろうとしたけど、selectOneRadio配下全てのname属性が全て同じになってしまった htmlコードをみて、たとえ うまく動いたとしても絶対に後々事故の元になると思ったので そっと諦めました。

ということで、実装したいなら input type="radio"だと思って試しましたが、今度は indexの取得で挫けました。

DataModel型を使えば出来るんでしょうけど、そのための実装量が多すぎるし 分かりにくいし。。

とにかく登場する要素が多すぎて これならdataTableを使わない方が良いや、となりました。

実際、私自身は、JSFを使う際、可能な限り HTML Friendly にするという指針なので dataTable自体を使っていませんでした。なので 諦めてもダメージが少ないというのも本音です。

JSF(HTML Friendly)

<h:form>
    <table>
        <thead>
            <tr>
                <th></th>
                <th>商品</th>
            </tr>
        </thead>
        <tbody>

            <tr jsfc="ui:repeat" value="#{itemsForm.items}" var="item" varStatus="state">
                <td>
                    <input type="radio" value="#{item.itemValue}" pt:name="itemJSF" jsf:id="radioOn"
                           rendered="#{itemsForm.renderChecked(state.index)}">
                        <f:passThroughAttributes value="#{itemsForm.checked}"/>
                        <f:ajax event="click"  listener="#{itemAction.change(state.index)}"/>
                    </input>
                    <input type="radio" value="#{item.itemValue}" pt:name="itemJSF" jsf:id="radioOff" class="designOnly"
                           rendered="#{itemsForm.renderChecked(state.index)==false}">
                        <f:ajax event="click"  listener="#{itemAction.change(state.index)}"/>
                    </input>
                </td>
                <td>#{item.itemValue}</td>
            </tr>
        </tbody>
    </table>

    <p>item = #{itemsForm.checkedItem}</p>
    <h:commandButton action="#{itemAction.update()}" value="更新"/>

</h:form>

「 シンプルな選択(ui:repeat)」の Label が無い版というところです。

その他

idとLabelのforの同値編集については、他にも色々なやり方があると思います。

例えば、idを直接指定するイメージで 以下のような書き方とかもあるでしょう。

<label>性別</label>
<div class="field">
    <div class="ui radio checkbox">
        <input type="radio" jsf:value="MAN" pt:name="gender" jsf:id="gender0">
            <f:passThroughAttributes value="#{userRegistrationForm.checked(0)}"/>
        </input>
        <label for="#{userRegistrationForm.targetFor(component,'gender0')}">男性</label>
    </div>
</div>
<div class="field">
    <div class="ui radio checkbox">
        <input type="radio" jsf:value="WOMAN" pt:name="gender" jsf:id="gender1">
            <f:passThroughAttributes value="#{userRegistrationForm.checked(1)}"/>
        </input>
        <label for="#{userRegistrationForm.targetFor(component,'gender1')}">女性</label>
    </div>
</div>
<div class="field">
    <div class="ui radio checkbox">
        <input type="radio" value="OTHER" pt:name="gender" jsf:id="gender2">
            <f:passThroughAttributes value="#{userRegistrationForm.checked(2)}"/>
        </input>
        <label for="#{userRegistrationForm.targetFor(component,'gender2')}">その他</label>
    </div>
</div>
public String targetFor(UIComponent component, String targetName) {
    return component.getClientId() + "-" + targetName;
}

参考

name attribute overriden when specifying input type="radio" as JSF passthrough element - Stack Overflow

JSF - JSF 2.2 selectOneRadioをテーブル一覧に出力させるには(43834)|teratail

「JSF selectOneRadio の同じneme属性のItemをテーブルの縦に割り振りたい。」(1) Java Solution − @IT

How to display the row index in a JSF datatable - Stack Overflow

Jsf datatable get row index program | JSF example code

[ H e p o n ' s T r i c k C u b e J a v a ]

Code

Bitbucket

さいごに

図らずも、JSFのBean参照のタイミングというか、出来る出来ないという範囲が 自分の直感的な理解とは異なるケースがあるというのが分かってよかった気がします。

「理屈としては、ここで参照できるはず」としないで、細かく確認しながら実装することが大事だと改めて思いました。

これはJSFが悪いとか そういう話ではなくて DDDでいうところの Domain以外のところは 実現性可否の調査をちゃんとしましょう、というだけの話だと思っています。

【考察】FormとEntityの類似点

Formに関して、アレコレ考えていると、Entityとの類似点が多いように思えてきた。

つらつらと思いついたことをメモ。

Identification

画面のどこの何、という意味で「ここのこのフィールド」という一意な場所が必要。

姓と名が同じ値だった場合(そんな名前の人はいないとは思うけど)、入れ替えても良いかというと それはダメ、みたいな話。

Mutable

identificationとほぼ同じこと。

画面項目として重要なのは同値性ではなくて一意性。

値については書き換えが可能。

実装面においても、JSFの場合、Formに相当するフィールドはmutable(プロパティにset)。

Persistent

厳密な意味でのPersistent(永続化)とは違うけど、クライアント(ブラウザ)上のメモリに保持されているという意味では 永続化しているともいえる。

DBはストレージに存在しつづける限りは生存するのと同じように、Formもブラウザを閉じるまでは生存しているという見方もできなくはない。

Aggregate

Formの AggregateのRoot Entityに相当するものが、画面。

考察(1)

考えるきっかけは、Formにおけるエラーツールチップの実装を考えている中で、対象セル(フォーム)の位置を特定するための情報を持っておかないといけないなぁ、と考えたところから。

フォームにおけるidentificationをどうしたものかな、と考えていて なんとなく 類似点を感じた。

ただ、全く同じかというと それは違って IDの採番はFormとEntityでは異なる。そして、それが現状悩み中のところ。

EntityはTable単位でIDが一意であれば問題ないけど、Formの場合は集約(画面)単位で一意じゃないといけない。 *1

なので、全く同じというわけでもない。

検証は、クライアント側だけではなく、ビジネスロジックによる検証(既存データの存在確認など)もあって、その結果をイイ感じに集約して 最終的にクライアントの入力フォームへ反映させる という仕組みが見いだせず。

クライアントだけならUIComponent使ったら 出来る方法が無いわけではないみたいだけれど、そのためにはxhtmlに記述がいるみたいで(ComponentIdとValidationの対象を明示するため)、個人的には そういうxhtmlに何かを記述するという やり方も出来れば避けたい。

単項目単位でDBへアクセスして検証する、というのもありといえば ありかもしれないけれど、やはり N+1問題が絶対に発生する方式というのは 個人的に許容したくない。

以前、それっぽいものを自作はしたけれど、結局 Formの情報をビジネスロジックまで蜜結合させた実現方式であったため(フォームのプロパティ名をEntity(またはDxo)と一致させる)、今から振り返ると かなり良くない。

単純なCRUDだったら、それでも良いかもしれないけれど、単純なCRUDにのみ限定されない仕組みを考えたい。

また、単項目の関連制御だけではなくて、一覧のような繰り返しも 適用範囲にしたい*2

現状、落としどころが見いだせておらず悩みは尽きず。

なーんか、パチッとくるアイディアが閃かないものか。。

考察(2)

クライアントサイドのモデルとは何か 後編 ~ 単方向データフローと参照透過性 - mizchi's blog

正直なところ、ちゃんと理解はできていないけど 何かヒントになりそうな気がする。

部分更新をするんじゃなくて、FormのRootへのCRUDとなるactionを通じてのみ 詳細への更新をすれば 良いのかも?

つまり常にRootから作り直すイメージ。

これくらい割り切ったら、最悪 FormIDも毎回 採番しなおしても問題なさそうかも。

変に「前回のIDとの関連付け」を やろうとしているから悩みが深くなっていたような気がする。

ただ、そうするとクライアント側でJacaScriptで表示順を変えたり、動的に要素を追加したりしたら どうなるかな?

JSFだと、そのあたりのズレが地味に困ったりするんだよなぁ。

ただそれもSubmitでAggregate全体を更新すれば大丈夫かな?

手を動かしつつ、更に色々考えてみよう。

*1:Entityもシステム全体でIDをUUIDにするとか、すれば それは一意になるかもしれないけど、普通 そういうことはしないように思うので、できる と やる は別で考えるべき

*2:これも過去の実装では、なんとか実現していたけれど、結局 方式は上述の「プロパティ名を一致させる」という方式なのでダメ

環境変数で設定したい情報のメモ

環境変数で設定したい情報

開発とプロダクトで異なる値になるものは環境変数(もしくは それに準ずる仕組み)で設定したいところです。

まずは、何を対象にするのか整理。

JSF

  • javax.faces.PROJECT_STAGE

  • javax.faces.FACELETS_SKIP_COMMENTS

  • javax.faces.FACELETS_REFRESH_PERIOD

パラメータ

Standard context parameters

設定のやり方

JSF 2.0のPROJECT_STAGEをアプリケーションの外部から指定する - penultimate diary

公式は、このやり方みたいだけど 個人的には もっと汎用的というか環境変数から値を取得するような仕組みが良いかぁ。

この手のものは、あんまり独自実装をしない方が良さそうに思ってはいるけれど どうせ環境変数から値を取得する仕組みについては 何かしら準備が*1必要だと思うので、その時に考えよう。 *2

DB

接続先毎に指定

  • 接続先URL

  • ログイン名

  • パスワード

  • SQLログの出力の有無

ログ

  • 出力ログレベル

  • 出力先

*1:ありものを使うにしても

*2:良く知らないけど、Config 1.0 とか そういうものを使うとか

JSFのIDあれこれ

JSFで生成されるidあれこれ - Challenge Java EE !

に加えて pass throughを実際に使ってみた実装サンプル

目的は JSFが生成するidnameについて確認する事。

定義

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:jsf="http://xmlns.jcp.org/jsf"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:pt="http://xmlns.jcp.org/jsf/passthrough">
    <head>
        <title>JSF ID</title>
    </head>
    <body>
        <h1>Id</h1>

        <h:form>
            <div jsf:id="jsfId" pt:id="ptId10" id="divId">
                jsfIdあり、passthroughIdあり、Idあり=passthroughIdが適用
            </div>
        </h:form>

        <h:form>
            <div jsf:id="jsfId" id="divId">
                jsfIdあり、passthroughIdなし、Idあり=divIdが適用
            </div>
        </h:form>

        <h:form>
            <div jsf:id="jsfId">
                jsfIdあり、passthroughIdなし、Idなし=jsfIdが適用(ルートは自動生成)
            </div>
        </h:form>



        <p>input type="text"</p>

        <h:form>
            <input type="text" jsf:id="jsfId" pt:id="ptId10" id="divId" value="jsfIdあり、passthroughIdあり、Idあり=passthroughIdが適用"  style="width:400px" />
        </h:form>

        <h:form>
            <input type="text" jsf:id="jsfId" id="divId" value="jsfIdあり、passthroughIdなし、Idあり=divIdが適用"  style="width:400px" />
        </h:form>

        <h:form>
            <input type="text" jsf:id="jsfId" value="jsfIdあり、passthroughIdなし、Idなし=jsfIdが適用(ルートは自動生成)"  style="width:500px" />
        </h:form>



        <p>h:inputText</p>

        <h:form>
            <h:inputText id="jsfId" pt:id="ptId10" value="jsfIdあり、passthroughIdあり=passthroughIdが適用" style="width:400px"/>
        </h:form>

        <h:form>
            <h:inputText id="jsfId"  value="jsfIdあり、passthroughIdなし=jsfIdが適用(ルートは自動生成)"  style="width:400px" />
        </h:form>



    </body>
</html>

生成後のxhtmlのコード

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>JSF ID</title>
    </head>
    <body>
        <h1>Id</h1>
        <form id="j_idt2" name="j_idt2" method="post" action="/jsf-id/index.xhtml" enctype="application/x-www-form-urlencoded">
            <input type="hidden" name="j_idt2" value="j_idt2" />
            <div id="ptId10">
                jsfIdあり、passthroughIdあり、Idあり=passthroughIdが適用
            </div>
            <input type="hidden" name="javax.faces.ViewState" id="j_id1-javax.faces.ViewState-0" value="-6059247821844920063:3663652348388936230" autocomplete="off" />
        </form>
        <form id="j_idt4" name="j_idt4" method="post" action="/jsf-id/index.xhtml" enctype="application/x-www-form-urlencoded">
            <input type="hidden" name="j_idt4" value="j_idt4" />
            <div id="divId">
                jsfIdあり、passthroughIdなし、Idあり=divIdが適用
            </div>
            <input type="hidden" name="javax.faces.ViewState" id="j_id1-javax.faces.ViewState-1" value="-6059247821844920063:3663652348388936230" autocomplete="off" />
        </form>
        <form id="j_idt6" name="j_idt6" method="post" action="/jsf-id/index.xhtml" enctype="application/x-www-form-urlencoded">
            <input type="hidden" name="j_idt6" value="j_idt6" />
            <div id="j_idt6-jsfId">
                jsfIdあり、passthroughIdなし、Idなし=jsfIdが適用(ルートは自動生成)
            </div>
            <input type="hidden" name="javax.faces.ViewState" id="j_id1-javax.faces.ViewState-2" value="-6059247821844920063:3663652348388936230" autocomplete="off" />
        </form>



        <p>input type="text"</p>
        <form id="j_idt9" name="j_idt9" method="post" action="/jsf-id/index.xhtml" enctype="application/x-www-form-urlencoded">
            <input type="hidden" name="j_idt9" value="j_idt9" />
            <input name="j_idt9-jsfId" style="width:400px" id="ptId10" type="text" value="jsfIdあり、passthroughIdあり、Idあり=passthroughIdが適用" />
            <input type="hidden" name="javax.faces.ViewState" id="j_id1-javax.faces.ViewState-3" value="-6059247821844920063:3663652348388936230" autocomplete="off" />
        </form>
        <form id="j_idt10" name="j_idt10" method="post" action="/jsf-id/index.xhtml" enctype="application/x-www-form-urlencoded">
            <input type="hidden" name="j_idt10" value="j_idt10" />
            <input name="j_idt10-jsfId" style="width:400px" id="divId" type="text" value="jsfIdあり、passthroughIdなし、Idあり=divIdが適用" />
            <input type="hidden" name="javax.faces.ViewState" id="j_id1-javax.faces.ViewState-4" value="-6059247821844920063:3663652348388936230" autocomplete="off" />
        </form>
        <form id="j_idt11" name="j_idt11" method="post" action="/jsf-id/index.xhtml" enctype="application/x-www-form-urlencoded">
            <input type="hidden" name="j_idt11" value="j_idt11" />
            <input id="j_idt11-jsfId" name="j_idt11-jsfId" style="width:500px" type="text" value="jsfIdあり、passthroughIdなし、Idなし=jsfIdが適用(ルートは自動生成)" />
            <input type="hidden" name="javax.faces.ViewState" id="j_id1-javax.faces.ViewState-5" value="-6059247821844920063:3663652348388936230" autocomplete="off" />
        </form>



        <p>h:inputText</p>
        <form id="j_idt13" name="j_idt13" method="post" action="/jsf-id/index.xhtml" enctype="application/x-www-form-urlencoded">
            <input type="hidden" name="j_idt13" value="j_idt13" />
            <input type="text" name="j_idt13-jsfId" value="jsfIdあり、passthroughIdあり=passthroughIdが適用" style="width:400px" id="ptId10" />
            <input type="hidden" name="javax.faces.ViewState" id="j_id1-javax.faces.ViewState-6" value="-6059247821844920063:3663652348388936230" autocomplete="off" />
        </form>
        <form id="j_idt14" name="j_idt14" method="post" action="/jsf-id/index.xhtml" enctype="application/x-www-form-urlencoded">
            <input type="hidden" name="j_idt14" value="j_idt14" />
            <input id="j_idt14-jsfId" type="text" name="j_idt14-jsfId" value="jsfIdあり、passthroughIdなし=jsfIdが適用(ルートは自動生成)" style="width:400px" />
            <input type="hidden" name="javax.faces.ViewState" id="j_id1-javax.faces.ViewState-7" value="-6059247821844920063:3663652348388936230" autocomplete="off" />
        </form>



    </body>
</html>

プレゼンテーション層で例外を扱う(私の例)

への私なりの回答です。

Javaの王道ではないと思いますし、過去のオレオレライブラリでは、こういう風にしたよってだけの話です。*1

正直、現時点では「うーん、発想は悪くなさそうだけど、見直ししたいな」と思っています。

とりあえず

「HTML返す場合で一律のエラーページに飛ばさずに画面の表示をエラー文言にしたい、とかの場合はtry-catchするしかないのか?」

への、私なりの回答です。

ちなみに、元々はトランザクションの話だったと思いますが、最終的なところはプレゼンテーション層との連携、ということとして回答します。

細かい前提は抜きにして、Interceptor(イメージ)を示します。

実際のコードは、もっと変なことを色々しているので、過去のコードを横目に それっぽい感じにしたものです。

私はJSFを使っているので、SpringMVCなどフレームワークだと の処理画面の情報の取得のやり方について、参考にならないかもしれませんが。。

public class ActionInterceptor {

    @Inject
    MessageItem messageItem;

    @AroundInvoke
    public Object invoke(InvocationContext ic) throws Exception {

        // 処理実行開始時点の画面IDを取得する。
        Object result = FacesContext.getCurrentInstance().getViewRoot().getViewId();

        try {
            result = ic.proceed();
            return result;
        } catch (CustomException ex) {
            /*
            実行時例外に独自の情報(メッセージIDとか、その他の付加情報)を持たせておいて、
            それを元に画面のメッセージを設定する
             */
            this.messageItem.setMessage(ex);

            /*
            例外があった場合は、実行時の画面IDを返却する。
            (もし、CustomException で保持している内容次第で、遷移先を変えたかったらここで何か実装する。)
             */
            return result;
        }
    }
}

なお、ExceptionHandler でシステム全体で共通してやっても良いように思います。

この実装を やったときは Interceptorで実装する方式しか イメージできなかったけれど、今なら、、どうかなぁ ちょっと分かりません。

ExceptionHandlerにするかもしれないです。

遷移前の情報など、その他の参照したい情報や適用されるタイミング次第だと思います。

ちなみに、当初は Controller で try-catch してメッセージ用のインスタンスに値を設定するUtilをCallする方式にしていました。

そのときは「レイヤー間を跨る例外は、検査例外にして 明示化するべきではないか?」と思っていたからです。

ですが、実際にやってみて 独自例外はメッセージIDが違うだけで型は1つ、いちいち try-catch を書くのが面倒くさくなって、上述の実行時例外での方式に編集しました。

おかげ様で、Controllerのメソッドに例外ハンドリングの記述が無くなった分だけ、スッキリしました。

*1:逃げ腰