CLOVER🍀

That was when it all began.

Spring Data GeodeをとりあえずSpring Boot Starterなしで使う

Apache Geode 1.0.0-incubating.M2がリリースされた時に、Spring Data Geodeの1.0.0.APACHE-GEODE-INCUBATING-M2がリリースされました。

Spring Data Geode 1.0.0.APACHE-GEODE-INCUBATING-M2 Released

Spring Data Gemfireのバージョンのうち、特にApache Geodeのサポートに寄ったものをSpring Data Geodeと呼ぶみたいですね。
※ところで、Spring Data Geodeの個々のバージョンがSpring Data Gemfireのどれと近いのかってわかるんでしょうか…

で、Spring Data Gemfireがリリースされていたことは知っていましたが、試してはいませんでした。

Spring Boot Starterはまだないし、なんか忘れましたがムリヤリ使おうとしてハマったような気が…。

まあ、Geode用のStarterはGeodeのGA待ちみたいです。

Add starter and sample for Apache Geode. by jxblum · Pull Request #5445 · spring-projects/spring-boot · GitHub

ところでですね、Spring BootのSpring Data Gemfire用のStarterとAutoConfigureってほとんど中身がないのを見て、これってSpring Data Geodeだけで動かせるんじゃ?と思い、試してみました。

https://github.com/spring-projects/spring-boot/tree/v1.3.6.RELEASE/spring-boot-starters/spring-boot-starter-data-gemfire
https://github.com/spring-projects/spring-boot/tree/v1.3.6.RELEASE/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data
※gemfireパッケージがない

とりあえず、最小構成で動かしてみましょう。

準備

pomの定義から。
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>p2p-spring-data-without-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.6.RELEASE</version>
    </parent>

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

    <dependencies>
        <!-- Spring Data Geode -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-geode</artifactId>
            <version>1.0.0.APACHE-GEODE-INCUBATING-M2</version>
        </dependency>

        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- AssertJ -->
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.5.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Spring Data Geode 1.0.0.APACHE-GEODE-INCUBATING-M2、トランザクションも軽く使うのでSpring Tx、あとはSpring Boot関連とAssertJを足しています。

Apache Geode用のJavaConfigを書く

Apache GeodeのCache/Regionを定義するための、JavaConfigを書きます。トランザクション管理も有効にしておきます。
src/main/java/org/littlewings/geode/springdata/GeodeConfig.java

package org.littlewings.geode.springdata;

import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.PartitionAttributes;
import com.gemstone.gemfire.cache.RegionAttributes;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.data.gemfire.GemfireTransactionManager;
import org.springframework.data.gemfire.PartitionAttributesFactoryBean;
import org.springframework.data.gemfire.PartitionedRegionFactoryBean;
import org.springframework.data.gemfire.RegionAttributesFactoryBean;
import org.springframework.data.gemfire.repository.config.EnableGemfireRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableGemfireRepositories
@EnableTransactionManagement
public class GeodeConfig {
    @Bean
    public CacheFactoryBean gemfireCache() {
        CacheFactoryBean cacheFactory = new CacheFactoryBean();
        cacheFactory.setClose(true);
        return cacheFactory;
    }

    @Bean(name = "myPartitionedRegion")
    public PartitionedRegionFactoryBean<String, String> partitionedRegion(Cache cache, RegionAttributes<String, String> regionAttributes) {
        PartitionedRegionFactoryBean<String, String> partitionedRegionFactory = new PartitionedRegionFactoryBean<>();
        partitionedRegionFactory.setAttributes(regionAttributes);
        partitionedRegionFactory.setClose(true);
        partitionedRegionFactory.setCache(cache);
        partitionedRegionFactory.setRegionName("myPartitionedRegion");
        partitionedRegionFactory.setPersistent(false);
        return partitionedRegionFactory;
    }

    @Bean
    public RegionAttributesFactoryBean regionAttributes(PartitionAttributes<String, String> partitionAttributes) {
        RegionAttributesFactoryBean regionAttributesFactory = new RegionAttributesFactoryBean();
        regionAttributesFactory.setKeyConstraint(String.class);
        regionAttributesFactory.setValueConstraint(String.class);
        regionAttributesFactory.setPartitionAttributes(partitionAttributes);
        return regionAttributesFactory;
    }

    @Bean
    public PartitionAttributesFactoryBean partitionAttributes() {
        PartitionAttributesFactoryBean partitionAttributesFactory = new PartitionAttributesFactoryBean();
        partitionAttributesFactory.setRedundantCopies(1);
        return partitionAttributesFactory;
    }

    @Bean
    public GemfireTransactionManager gemfireTransactionManager(Cache cache) {
        return new GemfireTransactionManager(cache);
    }
}

@EnableGemfireRepositoriesを付けてSpring Data Geodeを有効に、@EnableTransactionManagementを付けてトランザクション管理を有効に。

あとはCacheと、今回はPartitionedRegionを定義しています。

テストを書く

それでは、動作確認を兼ねてテストコードを書いて動かしてみます。
src/test/java/org/littlewings/geode/springdata/SpringDataGeodeTest.java

package org.littlewings.geode.springdata;

import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.Region;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(GeodeConfig.class)
public class SpringDataGeodeTest {
    @Autowired
    Cache cache;

    @Autowired
    PlatformTransactionManager transactionManager;

    @Before
    public void setUp() {
        Region<String, String> region = cache.getRegion("myPartitionedRegion");
        region.keySet().forEach(key -> region.remove(key));
    }

    @Test
    public void simpleUsage() {
        Region<String, String> region = cache.getRegion("myPartitionedRegion");

        region.put("key", "value");
        assertThat(region.get("key")).isEqualTo("value");
    }

    @Test
    public void txCommit() {
        Region<String, String> region = cache.getRegion("myPartitionedRegion");

        new TransactionTemplate(transactionManager)
                .execute(status -> region.put("key", "value"));

        assertThat(region.get("key")).isEqualTo("value");
    }

    @Test
    public void txRollback() {
        Region<String, String> region = cache.getRegion("myPartitionedRegion");

        new TransactionTemplate(transactionManager).execute(status -> {
            region.put("key", "value");
            status.setRollbackOnly();
            return null;
        });

        assertThat(region.get("key")).isNull();
    }
}

単純なものですが、OKでした。

まとめ

Spring Data Geodeを、ふつうに依存関係に突っ込むというやり方でSpring Bootに投げ込んで使ってみました。

Spring Data Geode用のStarterができるまでは、これでいいかなぁと。

個人的にはSpring Data Geode、Spring Data Gemfireとのバージョンの関連性がよくわからなくなったのと(前は1.7.0.APACHE-GEODE-EA-M1とかでした)、メンテって大変なのかな?(別ブランチっぽいし、Gemfire/Geodeの差とか)とちょっと気になったり。