d.sunnyone.org
sunnyone.org

ページ

2018-10-04

Spring Data JDBC で Enum を序数でない数値に変換する

Spring Data JDBC では、デフォルトではenumはnameに変換される。しかし、DBに格納する際は特定のコード(数値)に変換したいことも多いと思うので、その方法について書く。
Kotlinで書いてしまうが、Javaへの読み替えは難しくないと思う。

インタフェースの作成と利用

enumはordinal(序数)を持っているが、それぞれの項目で固定されたコード(数値)を設定したいので、コードを設定できるinterfaceを作成する。

interface CodedEnum {
    val code: Int
}
enum側で使っておく。
enum class Status(override val code: Int): CodedEnum {
    SUCCESS(1),
    FAILURE(2),
}

NamedParameterJdbcTemplateをカスタマイズしてパラメータを差し替える

Spring Data JDBC は内部でSpring JDBC のNamedParameterJdbcTemplateを使っているが、これが@Query で指定したパラメータの変換を担っている。 パラメータをenumで受け取るには、これがCodedEnumを扱えるようになる必要があるので、カスタマイズされたNamedParameterJdbcTemplateを準備する。
対応してる箇所が低いところすぎて気持ち悪いけど、getPreparedStatementCreator() は protected なので、NamedParameterJdbcTemplate的にはここを置き換えることは想定されているんだろうなと思いつつ対応。 enumに限らずここを書き換えたい作業はあると思うので、将来的にはSpring Data JDBC がもっと便利になって、こういう処理はいらなくなるかも。

Converterの追加とCustomizedNamedParameterJdbcTemplateの適用

@EnableJdbcRepositories の @Configuration で、まずnamedParameterJdbcTemplateを差し替える。

そして、値の変換を行うJdbcCustomConversionsにもEnumの変換処理を入れる。基本的には、write方面はinterfaceをそのまま.codeで変換して、readは型がわからないのでfactoryを利用して変換する。 ここでポイントになるのは、Spring Data JDBC の BasicRelationalPersistentPropertyがDB向け変換を行う際にEnumをStringに変換するということ。 これに対応するため、to Stringの変換を用意しておく。

 これで、codeで設定した値が利用されるようになる。

2018-03-26

AWS CloudFront Lambda@Edge の罠たち

CloudFront Lambda@Edge は、Lambda関数を実行しCloudFrontのリクエスト/レスポンスに手を加えることができるサービス。CloudFront⇔オリジン間、ビューワ⇔CloudFront間のリクエスト側/レスポンス側の4つの場所から適用する場所を選ぶことができる。

適切に使えば便利だと思うが、使ってみないとわからない罠が多いので、経験した範囲をまとめておく。

CloudFrontにはバージョンつきのLambda関数のARNを設定する必要がある

CloudFront BehaviorにLambda関数のARNを設定するのだが、名前だけのARNは設定することができず、バージョン番号入りのものを設定する必要がある。

つまり、Lambda関数の中身を書き換えたら、新しいバージョン番号を発行して、その番号をCloudFrontに設定する必要がある。

毎回手動でなんて現実的ではないので、AWS Serverless Application Model (AWS SAM)を利用してCloudFormationでLambda関数をデプロイし、AutoPublishAlias 機能によってバージョン番号を自動的に設定するのが現実的であろう。同一スタックであれば、CacheBehaviorsに以下のように設定する。
LambdaFunctionAssociations:
  - EventType: origin-response
    LambdaFunctionARN: !Ref "Function.Version"
これが何を意味するかというとLambda関数を更新する度にCloudFront Distributionを更新する、すなわちコードを書き換えるたびに数十分かかるということを意味する。

Lambda@Edgeに設定する関数はus-east-1リージョンに配置する必要がある

Lambdaはap-northeast-1でも利用可能だが、Lambda@Edgeに設定する関数はus-east-1に配置する必要がある。先のCloudFormationスタックを作るのであれば、us-east-1リージョンで作る必要があるということになる。

日本のサービスであればap-northeast-1に配置することも多いと思うが、スタック間でのパラメータの引継ぎに利用できるクロススタック参照は同一リージョンでしか利用できない。したがって、us-east-1→ap-northeast-1のスタック間でリソースの参照がある場合、自前でパラメータ引き渡しをするための仕組みが必要となる。

Lambda@Edgeで設定した関数は各リージョンにレプリカが作られて実行される

us-east-1にLambda関数を設定したからといって、us-east-1で常に実行されるかというとそうではなく、CloudFrontのエッジのリージョンに必要なタイミングでコピーされて実行される。

「オレがCloudFront エッジをサービスしたいのは日本だけ!」ということはできないので、日本を含もうとすると、多くのリージョンにレプリカが作られることになる。

レプリカはLambdaから見ると、us-east-1.というprefixがついた変更不可能な関数にしか見えない。これは、Lambda@Edgeに設定した関数のログは各リージョンのCloudWatch Logsに散らばって配置されるということを意味する。 もしログを集約したりフィルタしたりといったことをしようとすると、ほぼ全リージョンのCloudWatch Logsのリソースに対して手続きを行う必要がある。

Lambda@Edgeでは環境変数は利用できない

Lambdaで環境ごとの値を設定するのに便利な環境変数は、Lambda@Edgeでは利用できない。よって、コードに埋め込むか何らかのAPI問い合わせで値を取得する必要がある。今回 必要な値は別のリソースのARNだったので、レプリカ元(マスター)のLambda関数リソースのタグに設定し、以下の手順で取得した。
  1. context.invokedFunctionArnからレプリカ関数のARNを取得
  2. レプリカ関数のARNからリージョンを取得し、そのリージョンに対してレプリカ関数を問い合わせるlambda:GetFunctionの呼び出しを行いマスター関数のARNを取得
  3. マスター関数のARNからリージョン(us-east-1)を取得し、lambda:GetFunctionを呼び出してタグを取得
別の関数の環境変数だとか、DynamoDBだとか、状況により選択肢はあると思うが、いずれにせよ工夫が必要である。

Lambdaよりもタイムアウトなどの制限が厳しい

通常のLambda関数よりも、制限が厳しくなっている(Lambda@Edge の制限)。

例えば、Lambda単体であればタイムアウトは300秒まで設定できるが、オリジンリクエスト/レスポンスに対する関数だと30秒、ビューワーだと5秒など項目によっては厳しくなっているので注意が必要。アップロードサイズも注意。

以上のように使えそうに見えて使ってみるとどうしよう、みたいなところが多いので皆様お気をつけください。

2017-10-20

Kotlinでコマンドラインツールを作成する(Gradle/fat jar)

Kotlinを始めるにはコマンドラインツールを作ってみてはと言いつつ、そういう話あんまりなくない?と思ったので書くことにした。 Gradleでfat jarを生成し、javaがあれば実行できるようにするところまで。

sdkmanでGradleをインストール

http://sdkman.io/install.html からsdkmanをインストールして、gradleをインストールする。
$ sdk install gradle

gradle initで雛形の作成

適当なディレクトリを作成し、gradle initでbuild.gradle他ファイルを生成する。

$ mkdir kotlin-cmd-example
$ cd kotlin-cmd-example
$ gradle init

build.gradleの編集

Using Gradle - Kotlin Programming Languageを参考にして、build.gradleを記述する。

kotlin-gradle-pluginを適用して、kotlin-stdlib他必要なライブラリを追加すればOK。

ここではlog出力のためのlogbackのほか、fat jarを生成するための johnrengelman/shadow を使っている。 shadowの設定については Shadow Plugin User Guide & Examples を。

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.1.51'
    id 'com.github.johnrengelman.shadow' version '2.0.1'
}

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.jetbrains.kotlin:kotlin-stdlib-jre8'

    compile 'org.slf4j:slf4j-api:1.7.25'
    compile 'org.slf4j:jcl-over-slf4j:1.7.25'
    compile 'org.slf4j:jul-to-slf4j:1.7.25'
    compile 'ch.qos.logback:logback-classic:1.2.3'
}

apply plugin: 'application'
mainClassName = "com.example.kotlincmdexample.KotlinCmdExampleKt"

注意点としては、特に設定しないとJava側からはクラス名にはKtがついているのでつける。

src/main/kotlin に Kotlin ソースコードを作成

デフォルトではソースの場所はsrc/main/kotlinなので、ここにソースコードを配置していく。 IntelliJ IDEAであれば、build.gradleを開けば開始できる。

src/main/kotlin/com/example/kotlincmdexample/KotlinCmdExample.kt

package com.example.kotlincmdexample

import org.slf4j.LoggerFactory

fun main(args: Array<String>) {
    val log = LoggerFactory.getLogger("com.example.kotlincmdexample.KotlinCmdExample")

    if (args.isEmpty()) {
        println("Usage: kotlin-cmd-example name")
        return
    }

    log.info("Hello, {}", args[0])
}

あえてclassを作ってないのでgetLoggerが冗長な感じがするが、中途半端なのでloggerの名前を適当にしてもいいし、クラスを作って渡すかしたほうがいいかも。

./gradlew runでテスト実行

./gradlew runすれば実行できる。IDEAであれば右クリックしてRunあるいはDebugでもOK。

$ ./gradlew run 

> Task :run
Usage: kotlin-cmd-example name


BUILD SUCCESSFUL in 0s
2 actionable tasks: 1 executed, 1 up-to-date

./gradlew buildでビルド(jar生成)

./gradlew buildするとbuild/distributionsにkotlin-cmd-example-shadow.tarと.zipが入っているので、これを展開すると起動スクリプトがあるので実行できる。

build/libsに-all.jarがあるので、java -jarで直接起動することも可能。

$ java -jar build/libs/kotlin-cmd-example-all.jar 
Usage: kotlin-cmd-example name

$ java -jar build/libs/kotlin-cmd-example-all.jar MyFirstKotlinProject
19:49:00.796 [main] INFO com.example.kotlincmdexample.KotlinCmdExample - Hello, MyFirstKotlinProject

今回のプロジェクト

GitHubのsunnyone/kotlin-cmd-exampleに置いておくので、参考になれば。

2017-06-27

Kotlin で引数の名前つきでの呼び出しを強制する裏ワザ

小ネタ。

Kotlinでは引数を名前つきで呼び出せる。なしでもOK。
fun add(x: Int, y: Int) = x + y

fun f() {
    add(1, 2)
    add(x=1, y=2)
    add(1, y=2)
}

名前付きで呼んでほしいときはどうするか?名前つきにしてほしいところの前にvararg Voidをおく。

fun add(vararg useNamed: Void, x: Int, y: Int) = x + y

fun f() {
    // add(1, 2) // コンパイルエラー!
    add(x=1, y=2)
    // add(1, y=2) // コンパイルエラー!
}

途中からでもOK。
fun add(x: Int, vararg useNamed: Void, y: Int) = x + y

fun f() {
    // add(1, 2) // コンパイルエラー!
    add(x=1, y=2)
    add(1, y=2)
}

…いやいやだめだろこれ。もっといい方法があれば教えて下さい。
参考:How can I force calls to some constructors/functions to use named arguments?

2017-01-29

RTX810 からmydnsにIPアドレスを通知する(Luaをスケジュール実行する)

変わるIPを知ってるのはルータなので、ルータに通知してもらおうということでRTX810にmydnsの更新をさせることにする。なんのことはない、shiwork氏のLuaスクリプトをRTXで動かそうというだけの話。

ただし、いわゆるフラッシュ領域にtftpでスクリプトをもってきて実行するというネットワーク機器にありがちそうなやり方をしようとすると、いくつかのドキュメントにまたがって追わないといけないので、あとで追えるようにまとめておく。

スクリプトの準備

gistからスクリプトを持ってきて、ユーザID/パスワードを書き換えておく。

tftpによるLuaスクリプトの転送

RTXのいわゆるフラッシュ領域はRTFSというらしい。ここにディレクトリを作ってスクリプトを転送する。
(→RTFS

/scripts/ディレクトリの作成

ルートにカスタムなスクリプトをべたに置くと気持ち悪いので、/scriptsというディレクトリを作って置くことにする。
# make directory /scripts
# show file list /
2017/01/29 21:12:50 <DIR>           scripts

スクリプト転送元端末からのtftpアクセスを許可する

# tftp host 192.0.2.10

tftpで送り込む

c:\> tftp 192.0.2.254 PUT mydns.lua /scripts/mydns.lua/(administratorパスワード) 
adminパスを入れる必要があるのに気づかずにはまった。
(→RTシリーズのインストールとリビジョンアップに関するFAQ tftpの機能と注意事項

確認。
# show file list /scripts
2017/01/29 21:12:55            2769 mydns.lua

tftpの無効化

# tftp host none

Luaスクリプトの実行

単発実行

単発の実行はlua パスでOK。show status luaでsuccessなことを確認する。
# lua /scripts/mydns.lua
# show status lua
Lua Library Version:            Lua 5.1.5
Lua Script Function Version:    1.07

[running]
There is no running script.

[history]
(1)
Running Trigger:     executed by 'lua' command
Command Line:        lua /scripts/mydns.lua
Script File:         /scripts/mydns.lua
Running Counts:      1
Error Counts:        0
First Starting Date: 2017/01/29 21:14:52
Last Starting Date:  2017/01/29 21:14:52
Last Ending Date:    2017/01/29 21:14:53
Last Result:         success

スケジュール実行

scheduleコマンドで実行をスケジュールする。
schedule at 2 */* *:0,10,20,30,40,50 * lua /scripts/mydns.lua
これでおしまい。
(→Lua スクリプト機能

2016-11-02

Vue.js 1.x から 2.0 への移行のポイント

先日Vue.js 1.xから2.0への移行を行った。まるっと書き換えないといけないというレベルではないが、メジャーバージョンアップだなぁと感じる程には違いを感じられたのでいくつかポイントを書いておく。

もちろん、Vue.jsの機能を全て使いこなしているわけではないため、すべて網羅しているとはとても考えていないので、詳しくは「Migration from Vue 1.x」を参照してほしい。

大きな違い:テンプレートが事前コンパイルできるようになった

Vue 1.xと2.xの大きな違いは、テンプレートのコンパイルと実行を分離できるようになったこと。実際に移行してみた感じ、このコンパイル処理の邪魔になるようなことに関しては排除するような形で変更されているように見える。

このコンパイルというのがどのようなものかは、browserify + vueifyの処理結果を見るとわかりやすい。

Single File Components (.vue)という形でコンポーネントを記述した場合、スクリプトやスタイル、テンプレートを同一のファイルに記述できる。browserifyを使う場合、vueifyというプラグインがこの.vueをJavaScript から読める形に変換してくれるのだが、この処理結果が1.xから2.xで大きく変わっている。

コンパイル対象のテンプレートが以下だとする。

Vue 1.xの場合は以下の形。

template変数に文字列として代入するだけになっている。

Vue 2.xの場合は以下の形。

render関数に、DOM要素を作っていくような関数がセットされていることがわかる。JSX中の要素がReact.createElement関数に変換される構造に近い。

移行の方法

Migration from Vue 1.x」の頭のほうに書いてあるが、vue-migration-helperを実行し表示されたメッセージの対処後、実行時に表示される警告を見つつ対処していく形になる。

移行時に変更する必要のあった点

browserify + vueifyで利用しているため、それを前提とする。webpackでも似たようなことをしないといけないはず。

npmインストールのデフォルトがRuntime-only Buildとなることへの対応

npmを利用すると、「Runtime-only build」を利用するようにインストールされる(Standalone vs. Runtime-only Build)。このRuntime-only buildは、template: によるテンプレートコンパイルが行えないため、HTML中にテンプレートを書いて実行する方式で実装されているアプリケーションは実行できなくなる。対処する方法は2通り。

render関数の実装(Runtime-only buildで実行できるようにする)

「Failed to mount component: template or render function not defined」と言われるので、その通りrender関数をnew Vue()の指定につける。この場合、ルートとなるコンポーネントも.vueに記述し、HTML側にはマウントされる要素しか用意しない。

HTMLは、

などとし、.js側は

というようにする。

SPA的なアプリケーションであれば十分であるかもしれないが、ちょいちょいVueを利用しているようなアプリケーションの場合、マウントの呼び出しがちょいちょいあるということなので、propsを受け取れるようなマウント関数を作っておくと便利である。


詳しくは「Render Functions」を参照。

Standalone buildの利用

既存のHTML中のテンプレートもコンパイルする場合、Standalone buildを利用するように変更する。

browserify + vueifyの場合は、aliasifyによってimport Vue from "vue"の動きを書き換える

まずaliasifyをインストール。
$ npm install aliasify --save-dev

package.jsonに以下の記述を追加。

browserifyの呼び出しに「.transform(aliasify)」を追加する。

属性中の{{ }}をv-bindと式に変更する

属性中の{{ hoge }}は解釈されなくなったため、v-bindと式に変更する必要がある。

今まではこのような形だったものは


このように変更する必要がある。


v-model中のフィルタの廃止

今まではv-model="obj.val | filter"という形で、v-modelにもフィルタが適用できていたのだが、これが廃止になった。

「v-model="something"」が「<input v-bind:value="something" v-on:input="something = $event.target.value">」のsyntax sugarである(Form Input Components using Custom Events)ということを利用して、コンポーネントを作る必要がある。

実際の例は、Migration from Vue 1.xに書いてあるが、v-modelに対するフィルタに比べると手間なので、多用していた場合は、結構痛いと思う。

Vue.config.delimitersをコンポーネントレベルに変更する

Vue.config.delimitersは廃止になり、"{{"と"}}"の変更はコンポーネントレベルでしか受け付けなくなった。

サーバサイドテンプレートとの混乱を避けるため、デリミタは変更していたのだが、このような場合new Vue()の呼び出しにそれぞれつける必要がある。

ただし、先述の通りvueifyがテンプレートをコンパイルするようになったのだが、vueifyが.vueに設定されたdelimitersを解釈しないため、変更ができなくなった。そのため、.vueで変更されたデリミタを使用している場合、デリミタ自体を全て書き換える必要がある。

propsでのJavaScript予約語の利用不可

コンパイル結果を見たらそれはそうかという感じだが、コンポーネントプロパティに予約語を使うと怒られるようになった。地味に痛いのが"class"で、注意が必要。

readyライフサイクルイベントをmounted+αに

"ready"ライフサイクルイベントが廃止になった。

一番近いのがmountedで、必要であればvm.$nextTickでDOM要素が構築されてから処理を実行する必要がある。

v-for中の$indexの代わりに(value, index)で受ける

$indexは削除されたので、(value, index) in valuesの形で受ける必要がある。

v-elとv-refはref="hoge"とvm.$refs.hogeへ

テンプレート中のDOM要素及びコンポーネントをスクリプトから利用できるv-refはref属性に変更になった。ref="hogehoge"とつけると、this.$refs.hogehogeと参照できる。

transitionの構造変更

transitionの構造が変更になった。

今までは"transition"属性をつけると適宜v-transition, v-enter, v-leaveクラスが付与されるという形だったが、transition要素で囲むと〜-enter, 〜-enter-active, 〜-leave, 〜-leave-activeが付与されるようになった。

タイミングも違うので、「Transition Classes」の図を見て対処する必要がある。

おわりに

自分のところではまったのはこんな感じ。ガイドはもっと長いので、もっと大きなアプリケーションでは他にもあると思う。感想としては、それ削らなくてもいいじゃないという点もあるが、基本的にはテンプレートコンパイル結果を見て納得という感じだった。

Migration from Vue 1.x」の他に、Vue.js 2 アップデートメモも参考になる。

2016-08-08

Java → Kotlin 連携時は検査例外に注意する

Kotlinには検査例外はない(Kotlin does not have checked exceptions)。しかし、Javaにおいては検査例外として取り扱われるクラスを普通にthrowすることができる。

コード例にすると、以下の通り。




もともとJavaでも宣言していなくても検査例外を投げてくるときは投げてくる(ERR06-J. 宣言されていないチェック例外をスローしない)が、throwsが目印にもならないという点で注意が必要である。