CLOVER🍀

That was when it all began.

InfinispanのSpring Session Support(Remote)を試す

前に、Infinispan 9から入ったSpring Sessionのサポート(Embedded Mode)を試しました。

InfinispanのSpring Session Support(Embedded)を試す - CLOVER

今度は、Remote(Hot Rod)で試してみたいと思います。

Externalizing session using Spring Session

どういうものかというと、Spring Sessionでのデータの保存先を、Infinispan Server(Hot Rod)にできるという
話ですね。

プログラムを作るにあたり、前回同様、今回もSpring Boot向けのモジュールを使用します。

Using Infinispan with Spring Boot

GitHub - infinispan/infinispan-spring-boot: Infinispan Spring Boot

Infinispan: Spring Boot Starters

準備

作成したpom.xmlは、このような感じで。
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.littlewings</groupId>
    <artifactId>remote-spring-session</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>

        <scala-maven-plugin.version>3.2.2</scala-maven-plugin.version>

        <scala.version>2.12.2</scala.version>
        <infinispan.version>9.0.3.Final</infinispan.version>
        <infinispan-spring-boot.version>1.0.0.Final</infinispan-spring-boot.version>
        <spring-boot.version>1.5.4.RELEASE</spring-boot.version>
        <spring-session.version>1.3.1.RELEASE</spring-session.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.infinispan</groupId>
                <artifactId>infinispan-bom</artifactId>
                <version>${infinispan.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-spring-boot-starter</artifactId>
            <version>${infinispan-spring-boot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-spring4-remote</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session</artifactId>
            <version>${spring-session.version}</version>
        </dependency>

        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>${scala-maven-plugin.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <scalaVersion>${scala.version}</scalaVersion>
                    <args>
                        <arg>-Xlint</arg>
                        <arg>-unchecked</arg>
                        <arg>-deprecation</arg>
                        <arg>-feature</arg>
                    </args>
                    <recompileMode>incremental</recompileMode>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

主要なライブラリのバージョンは、BOMで指定。今回はInfinispanのものを優先したいので、こちらを先に書いておきます。

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.infinispan</groupId>
                <artifactId>infinispan-bom</artifactId>
                <version>${infinispan.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

InfinispanのSpring Boot用のモジュール、Spring 4向けのRemote(Hot Rod)用モジュールを加えます。

        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-spring-boot-starter</artifactId>
            <version>${infinispan-spring-boot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.infinispan</groupId>
            <artifactId>infinispan-spring4-remote</artifactId>
        </dependency>

最後に、Spring BootのWeb用のStarterとSpring Sessionを追加。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session</artifactId>
            <version>${spring-session.version}</version>
        </dependency>

依存関係については、このくらいですね。

Infinispan Serverについては1 Nodeで起動済みとしますが、InfinispanのSpring Session向けのモジュールでは、セッション用のCacheとして
デフォルトで「sessions」という名前のCacheを利用するので、こちらを先に作成しておきます。

Infinispan Serverの管理ユーザーの作成。今回は「ispn-admin / ispn-password」とします。

$ bin/add-user.sh -u ispn-admin -p ispn-password
Added user 'ispn-admin' to file '/path/to/standalone/configuration/mgmt-users.properties'
Added user 'ispn-admin' to file '/path/to/domain/configuration/mgmt-users.properties'

Distributed Cacheの作成。

$ bin/ispn-cli.sh -c -u=ispn-admin -p=ispn-password
[standalone@localhost:9990 /] /subsystem=datagrid-infinispan/cache-container=clustered/configurations=CONFIGURATIONS/distributed-cache-configuration=sessions-configuration:add(start=EAGER,mode=SYNC) 
{"outcome" => "success"}
[standalone@localhost:9990 /] /subsystem=datagrid-infinispan/cache-container=clustered/distributed-cache=sessions:add(configuration=sessions-configuration)
{"outcome" => "success"}

これで、準備はおしまいです。

サンプルコードの作成

サンプルコード自体は、Embedded Modeとほぼ同じものを使います。
src/main/scala/org/littlewings/infinispan/spring/App.scala

package org.littlewings.infinispan.spring

import org.infinispan.spring.session.configuration.EnableInfinispanRemoteHttpSession
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication

object App {
  def main(args: Array[String]): Unit = {
    SpringApplication.run(classOf[App], args: _*)
  }
}

@EnableInfinispanRemoteHttpSession
@SpringBootApplication
class App

ポイントは、@EnableInfinispanRemoteHttpSessionアノテーションを付与しておくことです。@EnableInfinispanRemoteHttpSessionアノテーションでは、
Spring Sessionで使うInfinispanのCache名とセッションの有効期限を設定することができます。

デフォルトでは、Cacheの名前が「sessions」で、有効期限が30分です。
https://github.com/infinispan/infinispan/blob/9.0.3.Final/spring/spring4/spring4-remote/src/main/java/org/infinispan/spring/session/configuration/EnableInfinispanRemoteHttpSession.java


今回は、デフォルトのまま使用します。

あとは、RestControllerとセッション間で共有するBeanを作成します。
src/main/scala/org/littlewings/infinispan/spring/CounterController.scala

package org.littlewings.infinispan.spring

import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

import org.springframework.stereotype.Component
import org.springframework.web.bind.annotation.{GetMapping, RestController}
import org.springframework.web.context.annotation.SessionScope

import scala.collection.JavaConverters._

@RestController
class CounterController(counter: Counter) {
  @GetMapping(Array("counter/access"))
  def access: java.util.Map[String, AnyRef] = {
    counter.increment()
    Map[String, AnyRef](
      "value" -> Integer.valueOf(counter.value),
      "time" -> counter.time.format(DateTimeFormatter.ISO_DATE_TIME)
    ).asJava
  }
}

@SessionScope
@Component
@SerialVersionUID(1L)
class Counter extends Serializable {
  var value: Int = 0

  val time: LocalDateTime = LocalDateTime.now

  def increment(): Unit = value += 1
}

設定ファイルについては、このように。
src/main/resources/hotrod-client.properties

infinispan.client.hotrod.server_list=172.17.0.2:11222

これは、InfinispanのHot Rod Clientが読み込む設定ファイルです。「hotrod-client.properties」ファイルを用意しない場合は、
Spring Bootのapplication.propertiesに設定を書きます。
src/main/resources/application.properties

## hotrod-client.properties を使わない場合はこちらでも可(ファイルがない場合に、このプロパティが参照される)
infinispan.remote.server-list=172.17.0.2:11222

コメントにも似たようなことを書いていますが、「hotrod-client.properties」ファイルを用意した場合は、application.propertiesに書いた
設定は無視されます。

Using Client/Server mode

なお、hotrod-client.propertiesに設定できる項目、キーは、こちらを参照するとよいです。
https://github.com/infinispan/infinispan/blob/9.0.3.Final/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/impl/ConfigurationProperties.java

これで、準備はおしまいです。

確認

では、パッケージングして確認してみます。

$ mvn package

アプリケーションは、2つ起動させましょう。

# No.1
$ java -jar target/remote-spring-session-0.0.1-SNAPSHOT.jar

# No.2
$ java -jar target/remote-spring-session-0.0.1-SNAPSHOT.jar --server.port=9080

curlで各アプリケーションにアクセスして確認します。

## アプリケーション1
$ curl -c cookie.txt -b cookie.txt -i http://localhost:8080/counter/access
HTTP/1.1 200 
Set-Cookie: SESSION=32ebed0d-d477-4352-b2ab-e31163a2ebfb; Path=/; HttpOnly
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 07 Jul 2017 14:36:03 GMT

{"value":1,"time":"2017-07-07T23:36:00.944"}


## アプリケーション2
$ curl -c cookie.txt -b cookie.txt -i http://localhost:9080/counter/access
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 07 Jul 2017 14:36:21 GMT

{"value":2,"time":"2017-07-07T23:36:00.944"}


## アプリケーション1
$ curl -c cookie.txt -b cookie.txt -i http://localhost:908080/counter/access
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 07 Jul 2017 14:36:56 GMT

{"value":3,"time":"2017-07-07T23:36:00.944"}


## アプリケーション2
$ curl -c cookie.txt -b cookie.txt -i http://localhost:809080/counter/access
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 07 Jul 2017 14:37:23 GMT

{"value":4,"time":"2017-07-07T23:36:00.944"}


## アプリケーション1
$ curl -c cookie.txt -b cookie.txt -i http://localhost:908080/counter/access
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 07 Jul 2017 14:37:32 GMT

{"value":5,"time":"2017-07-07T23:36:00.944"}

## アプリケーション2
$ curl -c cookie.txt -b cookie.txt -i http://localhost:809080/counter/access
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 07 Jul 2017 14:37:41 GMT

{"value":6,"time":"2017-07-07T23:36:00.944"}


Bean作成時に生成された時間も変わっていませんし、インクリメントした値も共有されてそうなのでOKですね。

まとめ

InfinispanのSpring Session向けのモジュールを、今回はHot Rod Clientで、かつSpring Boot向けのStarterと一緒に使ってみました。

Infinispan ServerにつなぐところとInfinispanのSpring Session有効化のアノテーションが違うくらいで、さらっと使えて
いいですね。

今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/infinispan-getting-started/tree/master/remote-spring-session