SAStruts のフォワードとリダイレクトについて

Seasar/Struts チョットデキルマンのみなさんには常識かと思われますが、@Execute の属性に redirect=true を指定してるのに実際はフォワードで遷移することがあったり、そのあたりの挙動がイマイチ理解できてないので少し調べてみます。

デフォルトはフォワード

Super Agile Struts - Annotation Reference

redirect要素(オプション) 遷移先にリダイレクトで遷移するかどうかを指定します。 デフォルトはfalseで、リダイレクトはしません。

redirect=true を指定するとリダイレクトになる。

エラーメッセージがあるときはフォワード

sa-struts/ActionWrapper.java at master · seasarorg/sa-struts · GitHub

if (redirect && ActionMessagesUtil.hasErrors(request)) {
    redirect = false;
}

ActionWrapper の execute の中でエラーメッセージがあるかチェックしてフラグを上書きしている。この場合、@Executeredirect=true を指定してもフォワードになる。

Action メソッドで返却するパスの末尾に redirect=true を付けるとリダイレクト

Super Agile Struts - Feature Reference

リダイレクトで遷移したい場合は、 パスの最後にredirect=trueを追加します。 =の前後に余分な空白は含めないでください。 redirect=trueの部分は、実際に実行されるときには、消去されます。

sa-struts/S2ActionMapping.java at master · seasarorg/sa-struts · GitHub

if (path.endsWith(REDIRECT)) {
    redirect = true;
    path = path.substring(0, path.length() - REDIRECT.length() - 1);
}

S2ActionMapping の createForward の中で redirect=true があるかチェックしてフラグを上書きしている。この場合、エラーメッセージがあってもなくても強制的にリダイレクトになる。

現場からは以上です。

JSUG勉強会 2018年その2 SpringとAPI時代の動く仕様への取り組み に行ってきた #jsug

JSUG勉強会 2018年その2 に行ってきました。簡単に所感をまとめます。
(2018/03/12) スライドのリンクを追記しました。

jsug.doorkeeper.jp

www.slideshare.net

  • システム設計の謎を解く
    • "設計とは考えること、アウトプットはその一側面である"
  • テストできないテストケース
    • 観点は書かれている
    • テストするための事前条件が書かれていない
  • テンプレート、フレームワーク
    • given: 事前条件、状態
    • when: 対象に対する操作
    • then: どうなるか
  • Spock: テストフレームワーク

    • ↑のテンプレート形式で書ける
  • 自動テストは動く仕様である

    • プログラムで表現できれば繰り返し検証できる
    • 動かして確認できる仕様という位置付け
  • テストメソッドの名前を given - when - then の形式で書く

  • テストで確認したい部分が仕様

    • プログラムレベルや操作レベルの内容はテストコードから隠蔽してよい
    • 仕様とは直接関係ないもの?
  • 状態中心のテスト

    • テスト対象の処理前後で状態の変化を検証する
  • 相互作用中心のテスト

    • テスト対象のオブジェクトのやり取り(どのメソッドが何回呼ばれたかなど)を検証する
  • 仕様はビジネスに使う言葉で表す

  • 仕様の粒度に気を付ける

漠然と思ったことなのですが、単体テストコードって仕様に対する品質保証より、プログラムに対する品質保証というかプログラマ心理的な安心を得るための役割の方が大きいのかなという気がしています。変更やリファクタリングするときとか。

なので、自動テストとは言っても、E2E のテストはさておき、JUnit などでやるようなメソッド単位のテストは、仕様を確認するというには少し粒度が細かすぎるのではないかという印象。

www.slideshare.net

後半は、Swagger, SpringFox, Spring REST Docs の話。なんとなくツールを使うモチベーションがズレてる気がして、ちょっと話を聞いててモヤモヤが...。

Vue.js + Vuex + axios

某案件で Vue.js + Vuex + axios を採用しました。備忘録としてまとめます。

前提

それぞれのバージョンは以下の通り。

"dependencies": {
  "axios": "^0.17.1",
  "es6-promise": "^4.1.1",
  "vue": "^2.5.2",
  "vue-router": "^3.0.1",
  "vuex": "^3.0.1"
}

開発には Visual Studio Code を利用。以下のプラグインを入れると便利。

  • HTML Snippets
  • vetur
  • VueHelper

vue-cli

vue-cli の webpack テンプレートを利用。当初、イチからプロジェクトを準備しようと思っていたが、vue-cli で作成した方がいろいろと確実と思われたので。ハマりどころも多いかもしれないが...。

webpack / source maps

source maps については、webpack の 公式ドキュメント と以下が参考になりました。

t-hiroyoshi.github.io

devtool の指定によってマッピングファイルの生成有無や中身、js ファイルのサイズが変わってくる模様。試したところ以下のような感じ。

デバッグ△は変数名が minify される程度、js サイズ△は kByte 程度。(分かり辛い...)

devtool デバッグ js サイズ source maps
source-map あり
eval-source-map なし
inline-source-map なし
cheap-eval-source-map なし
eval なし
minify なし

結局、開発環境向けには eval-source-map を指定、プロダクション環境向けには source-map を指定。

その他、webpack のちょっとした小細工は以下を参照。

vue-cli の webpack テンプレートでテスト環境向けにビルドしたい - kntmr-blog

プロジェクト構成

├─build
├─config         ... 設定ファイル
├─dist
├─node_modules
├─src            ... エントリーポイントの js を配置
│  ├─api         ... API アクセス
│  ├─components  ... 共通コンポーネント / 基底コンポーネント
│  ├─pages       ... ページコンポーネント
│  ├─router      ... ルーティング (今回は利用しない予定)
│  └─store
│      └─modules ... 状態管理
├─static
└─test           ... テストコード

このあたりは Vuex の examples などを参考にしました。shopping-cart とか。

github.com

ページコンポーネントはアプリケーション内で単一となるコンポーネント。共通コンポーネントや基底コンポーネントはアプリケーション内で使い回されるコンポーネント。共通コンポーネントは複数の基底コンポーネントで構成される。

共通コンポーネントや基底コンポーネントに id 属性を付けると、ページ内で id が重複する可能性があるため注意。ページコンポーネントには id 属性、共通コンポーネントと基底コンポーネントには class 属性を指定するといいかもしれない。

スタイルガイド

Vue.js の スタイルガイド は必読。

store の namespace

複数の store に分割するときは、namespace を指定してモジュールを管理する。

モジュール - Vuex

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}

モジュール間でデータを受け渡す場合は dispatch する。(この方法がベストかどうかはちょっとあやしい...)

API

axios に依存するコードをまとめて抽象化する。ここには業務的なロジックは実装しない。ここで export した関数を store の actions から呼び出す。コンポーネント内には API 呼び出しの処理は書かない。

gist.github.com

mixins / filters

mixins は Vue コンポーネントの実装を共通化する用途で使う。今回はローディングやエラー処理など、各画面共通で使う computedmethods の実装を mixins にまとめた。filters は日付やテキストをフォーマットする用途で使う。

反省点など

コンポーネント命名規則がイマイチ

このコンポーネントがどういう役割を持っているのか名前から判断できるとよい。そのためには単一責務のコンポーネントとして切り出すようにするのが理想と思われる。

再掲: スタイルガイド

actions / mutations / state

今回、1つの action の中では基本的に1つの mutation を呼び出すような形で mutations を書いたが、ある action の中でどの state が書き換わるのか分かり辛い感じになってしまった。ある程度、state の粒度に合わせて mutations を書いた方がいいのかもしれない。

APIコンポーネントの分離

API の項にも書いたが、API の変更をコンポーネント側に波及させたくなかったので、コンポーネント内に API 呼び出しの処理は書かないことにした。が、結局、API のレスポンスオブジェクトがコンポーネントのあちこちで参照されていたため、このあたりはきれいに分離できなかった。

APIコンポーネントを分離するために、store 内で変換するような仕組みを持たせてもよかったかもしれない。ただ、store の責務がどんどん膨らんでしまうのが懸念。API と store の間にもう1レイヤあるとよかったのだろうか。

デザインとコンポーネントの設計

当たり前だが、デザインとコンポーネントは密接に関係する。きれいなコンポーネント設計にするためには、当然、それを意識したデザインを作らないといけない。

今回、開発体制的な理由で基本的にデザインはこちらでコントロールできるものではなかったため、一部のコンポーネントに重複や無駄が入り込んでしまった。が、これはしょうがないのかもしれない。

IE 対応

babel や es6-promise は使っていたものの、Edge では動くが IE10 では動かないということがあった。が、npm update してパッケージをアップデートしたら動くようになった...。詳しくは追い切れていないが、babel あたりの更新が要因か。

割と短期間で挙動が変わるような更新があるのは、ちょっと腑に落ちないところがある。このあたりもきちんと追えるようにしたい。

テストコード

今回、Avoriaz や Sinon.JS でコンポーネントのテストコードを作成する予定だったが、メンバーの教育に時間がかかりそうだったため諦めることにした。(その代わり社内で使われている画面系のテスト仕様書でテストした)

まとめ

Vue.js の特長として、シンプルな構成で、かつ拡張性が高いということが挙げられる。また、ミニマムにスタートできるメリットがある。ただ、この ミニマム にフォーカスされるためか、世間からは「小規模向け」というイメージを持たれているように思える。(主観ですが...)

決してそんなことはなく、小規模なアプリケーションはもちろん、大規模なアプリケーションにも十分適用できると思われる。

vue-cli の webpack テンプレートでテスト環境向けにビルドしたい

やりたいこととしては、dev.env.jsprod.env.js 以外に、テスト環境向けの config が欲しい。ちなみに、ここで言う「テスト環境」とは、ローカル以上、プロダクション未満のような環境を指します。

で、stg.env.js のような config を新しく用意しようかと思ったのですが、webpack.prod.conf.js を見ると、普通に webpack を使ってる分には test.env.js が使われてなさそう...?

というわけで、次の方法でビルドできるようにしてみました。

前提

  • vue-cli@2.9.1

コマンド

ローカル環境向け (dev.env.js)

> npm run dev

テスト環境向け (test.env.js)

> npm run build

プロダクション環境向け (prod.env.js)

> npm run build -- production

変更点

build/build.js

const targetEnv = process.argv[2] === 'production' ? 'production' : 'testing'
process.env.NODE_ENV = targetEnv

npm run build-- に続けてパラメータを渡すと、process.argv で受け取ることができる。

あとは、コンソールに表示するスピナーとか完了メッセージをいい感じにする。

...
// スピナー
const spinner = ora(`building for ${targetEnv}...`)
...
// 完了メッセージ
console.log(chalk.cyan(`  Build complete. (build for ${targetEnv})\n`))

現場からは以上です。

アロー関数で空のオブジェクト返そうとしてハマる

普通に考えれば当たり前なんですが、最初なにが起きてるのか分からなくてハマった...。

const func = () => {}
func() // => undefined

つまり、{} がオブジェクトのリテラルではなく、関数のブロックとして認識されている模様。で、return 文がないので undefined が返る。

こうする。

const func = () => { return {} }
func() // => {}

もしくはこう。

const func = () => new Object()
func() // => {}

あまり需要はないかもしれないですが。

その他

どうやら ES6 より ES2015 が推奨っぽいので、今日から ES2015 と呼ぶことにしよう。

追記 (2018/01/11)

括弧で囲めば式として扱われる模様。

const func = () => ({})
func() // => {}

2017年のふりかえりと2018年のこと

賀正。

2017年の行動指針 - kntmr-blog

2017年の初めにこんなエントリを書いていたので、これのふりかえりと2018年のことでも書いてみようかと。

『基本的に毎日勉強することが目標。』

これはさすがに無理でしたね。全然だめでした。圧倒的にだめでした。就寝前に勉強しようと思ってもついつい YouTube とか観ちゃうんですよね。まぁ、それがストレス発散になってるところもあるんですけど。

『平日の通勤時間を利用して英語を勉強する。』

一時期、mikan という英単語アプリで勉強してたんですが、1ヶ月程度しか続けられず...。2018年はもっとちゃんと頑張りたい。

Oracle Certified Java Programmer, Gold SE 8 認定資格』

取得しました。2017年はコードレビューする機会が多かったんですが、資格勉強の成果か、以前よりコードを読む力が向上したように感じます。たぶん。

Oracle Certified Java Programmer, Gold SE 8 認定資格 - kntmr-blog

JavaScript フレームワーク

2017年末頃から Vue.js を実案件で使っています。Vue + Vuex + axios の組み合わせ。最近は avoriaz を頑張っています。ついでに ES6 も学べていいですね。ところで ES6 と ES2015 はどっちで呼べばいいの。

というわけで、基本的に何か目標がないとだめだめマンみたいなので、今年は英語勉強の目標として TOEIC で600点あたりを目指してみようかな。あと、どこかで Java 9 と Spring 5 は腰を据えて学ばなければ。現場からは以上です。

Avoriaz で filters や methods の中身を差し替える

前回の続編。用途があるか不明ですが、こんな感じでできそうという話。

kntmr.hatenablog.com

filters を差し替える

例えば、こういうコンポーネントがあって、期待する値が出力されるかテストするパターン。ここでは、グローバルフィルターが設定されているものとする。

<template>
  <div>
    <span>{{ username | toUpper }}</span>
  </div>
</template>

<script>
export default {
  name: 'AvoriazTest',
  props: [ 'username' ]
}
</script>

この場合は、単純に Vue.filters で差し替える。というか、普通にテストを実行するとグローバルフィルターは参照不可のようで、何かしらのスタブは用意する必要がある模様...。ちなみにフィルター自体のテストは別途する前提。

import Vue from 'vue'
import { shallow } from 'avoriaz'
import AvoriazTest from '@/components/AvoriazTest'

Vue.filter('toUpper', (v) => 'TEST') // あらかじめフィルターにスタブをセットしておいて

describe('AvoriazTest.vue', () => {
  it('should render correct contents', () => {
    const wrapper = shallow(AvoriazTest, { propsData: { username: 'abc' } })
    expect(wrapper.find('span')[0].text()).to.equal('TEST') // 期待値が出力されることを確認する
  })
})
methods のメソッドを差し替える

例えば、レンダリング時に data で値を返す前に methods に食わせるパターン。

<template>
  <div>
    <span>{{ changedText }}</span>
  </div>
</template>

<script>
export default {
  name: 'AvoriazTest',
  props: [ 'username' ],
  data () {
    return {
      changedText: this.toUpper(this.username)
    }
  },
  methods: {
    toUpper (v) {
      return v.toUpperCase()
    }
  }
}
</script>

最初、前回のような wrapper.setMethods({ 'toUpper': stub }) でできるかと思ったんですが、これでは差し替えられず...。おそらくレンダリング時にはすでに data の内容が計算済みのためと思われる。

というわけで、今回は shallow または mount でラッパーオブジェクトを生成する際に propsData と同じように methods を指定してスタブを差し替える。

import { shallow } from 'avoriaz'
import AvoriazTest from '@/components/AvoriazTest'

describe('AvoriazTest.vue', () => {
  it('should render correct contents', () => {
    const stub = (v) => 'TEST' // スタブを用意して
    const wrapper = shallow(AvoriazTest, { propsData: { username: 'abc' }, methods: { toUpper: stub } }) // ここで差し替える
    expect(wrapper.find('span')[0].text()).to.equal('TEST')
  })
})

冒頭に書いた通り、これらの用途があるのか分かりませんが、いろいろいじくりまわしていると勉強になりますね。