Spring Cloud Eureka Server

Keine Kommentare
Spring Cloud

One of the youngest children in the Spring stack is Spring Cloud which bundles tools to organize and manage distributed systems. There exist components being self developed by the Spring team like Spring Cloud Config which offer a centralized configuration server.
On the other hand the Spring team included components from the Netflix Open Source Software Center and embeds them in the new and easy annotation fashion known from Spring Boot. One of the main Netflix components is Eureka, a REST based service registration and discovery service.

This post is a very simple tutorial showing how to produce an Eureka server with Spring Cloud. The source code is available on GitHub.

Maven configuration

The Maven configuration for this project is very straight-forward:

  • import the Spring Cloud starter pom for Maven dependency settings
  • add a dependency to the Spring Cloud starter Eureka server
  • extend the Spring Boot Maven plugin configuration
  • add milestone repository
Last step is needed while Spring Cloud isn't released in 1.0 yet.

<?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>cnagel</groupId>
  <artifactId>spring-eureka-server-example</artifactId>
  <version>0.0.2-SNAPSHOT</version>
  <packaging>jar</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring-cloud.version>1.0.0.RC1</spring-cloud.version>
    <spring-boot.version>1.2.0.RELEASE</spring-boot.version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-parent</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-eureka-server</artifactId>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>${spring-boot.version}</version>
        <configuration>
          <requiresUnpack>
            <dependency>
              <groupId>com.netflix.eureka</groupId>
              <artifactId>eureka-core</artifactId>
            </dependency>
            <dependency>
              <groupId>com.netflix.eureka</groupId>
              <artifactId>eureka-client</artifactId>
            </dependency>
          </requiresUnpack>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <repositories>
    <repository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>http://repo.spring.io/milestone</url>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
  </repositories>

</project>

Java Application class

While everything is pre-configured by Spring Cloud it just needs to add some annotations to the application class. @ComponentScan, @Configuration and @EnableAutoConfiguration are typically Spring annotations initiating a Spring app the Boot way. @EnableEurekaServer loads the Eureka server.

package cnagel.spring_eureka_server_example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@ComponentScan
@Configuration
@EnableAutoConfiguration
@EnableEurekaServer
public class EurekaServerApp {

 public static void main(String[] args) {
  SpringApplication.run(EurekaServerApp.class, args);
 }

}

Application properties

Properties are defined in the application.yml which is loaded automatically by Spring Boot.

spring:
  application:
    name: eureka-server

server:
  port: 8761

eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false
  server:
    waitTimeInMsWhenSyncEmpty: 0
  
logging:
  level:
    com.netflix: 'WARN'
    org.springframework.cloud: 'WARN'

Starting the Eureka server

Last but not least the server gets started by executing mvn spring-boot:run. There exists a dashboard on http://localhost:8761 giving useful information about the Eureka server. The REST representation of registered services is available at http://localhost:8761/eureka/apps.

Keine Kommentare :

Kommentar veröffentlichen

Hinweis: Nur ein Mitglied dieses Blogs kann Kommentare posten.

Scalate integration in Spring Boot

Keine Kommentare
MongoDB

Developing the Surpreso frontend in Rails I really liked to write my views with the HAML template engine. Investing more time with Spring Boot I searched for a Java pendant and got aware of Scalate, the scala template engine.

At this time Scalate supports 4 template formats:
  • Mustache
  • SSP
  • Scaml (which is equal to HAML)
  • Jade (which is little bit nicer than HAML :-)

Integrating it in Spring Boot seemed very easy and I wrote a small Git pull-request with passing tests. Afterwards I wanted to write this small tutorial to do the steps manually and found some ugly packaging hooks. The post shows how to pass them and I hope to correct the pull-request in the next days.
As always you can find the source code for this project on GitHub.

Maven configuration

In opposite to the Spring Boot defaults I store my template files under src/main/webapp/WEB-INFO/templates. If you work with Eclipse it won't reload the complete server project when you change your views. But this needs some management in the pom.xml to get the tests run.

<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" 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>cnagel</groupid>
 <artifactid>spring-scalate</artifactid>
 <version>0.0.1-SNAPSHOT</version>

 <!-- The packaging format, use jar for standalone projects -->
 <packaging>war</packaging>

 <properties>
  <!-- The main class to start by executing java -jar -->
  <start-class>cnagel.spring_scalate.Application</start-class>
  <project .build.sourceencoding="">UTF-8</project>
  <project .reporting.outputencoding="">UTF-8</project>
  <scala .version="">2.10.0</scala>
  <scalate .version="">1.6.1</scalate>
 </properties>

 <!-- Inherit defaults from Spring Boot -->
 <parent>
  <groupid>org.springframework.boot</groupid>
  <artifactid>spring-boot-starter-parent</artifactid>
  <version>1.0.0.RC4</version>
 </parent>

 <dependencies>
  <!-- Spring Boot -->
  <dependency>
   <groupid>org.springframework.boot</groupid>
   <artifactid>spring-boot-starter-web</artifactid>
  </dependency>
  <!-- Spring Boot Test -->
  <dependency>
   <groupid>org.springframework.boot</groupid>
   <artifactid>spring-boot-starter-test</artifactid>
   <scope>test</scope>
  </dependency>
  <!-- Spring Tomcat libs needed for Eclipse -->
  <dependency>
   <groupid>org.springframework.boot</groupid>
   <artifactid>spring-boot-starter-tomcat</artifactid>
   <scope>provided</scope>
  </dependency>
  <!-- Scalate -->
  <dependency>
   <groupid>org.fusesource.scalate</groupid>
   <artifactid>scalate-spring-mvc_2.10</artifactid>
   <version>${scalate.version}</version>
  </dependency>
 </dependencies>

 <build>
  <plugins>
   <!-- Use plugin to package as an executable JAR -->
   <plugin>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-maven-plugin</artifactid>
   </plugin>
   <!-- Scalate template pre compiler -->
   <plugin>
    <groupid>org.fusesource.scalate</groupid>
    <artifactid>maven-scalate-plugin_2.10</artifactid>
    <version>${scalate.version}</version>
    <executions>
     <execution>
      <goals>
       <goal>precompile</goal>
      </goals>
     </execution>
    </executions>
   </plugin>
   <!-- Create classes and lib folder in test phase for Scalate -->
   <plugin>
    <groupid>org.apache.maven.plugins</groupid>
    <artifactid>maven-antrun-plugin</artifactid>
    <executions>
     <execution>
      <id>test-compile</id>
      <phase>test-compile</phase>
      <goals>
       <goal>run</goal>
      </goals>
      <configuration>
       <tasks>
        <mkdir dir="target/test-classes/WEB-INF/classes">
        <mkdir dir="target/test-classes/WEB-INF/lib">
       </mkdir></mkdir></tasks>
      </configuration>
     </execution>
    </executions>
   </plugin>
  </plugins>
  <pluginmanagement>
   <plugins>
    <!-- Executes the antrun plugin in Eclipse too -->
    <plugin>
     <groupid>org.eclipse.m2e</groupid>
     <artifactid>lifecycle-mapping</artifactid>
     <version>1.0.0</version>
     <configuration>
      <lifecyclemappingmetadata>
       <pluginexecutions>
        <pluginexecution>
         <pluginexecutionfilter>
          <groupid>org.apache.maven.plugins</groupid>
          <artifactid>maven-antrun-plugin</artifactid>
          <versionrange>1.7</versionrange>
          <goals>
           <goal>run</goal>
          </goals>
         </pluginexecutionfilter>
         <action>
          <execute>
         </execute></action>
        </pluginexecution>
       </pluginexecutions>
      </lifecyclemappingmetadata>
     </configuration>
    </plugin>
   </plugins>
  </pluginmanagement>
  <!-- Adds the WEB-INF folder to the test-classes directory to include templates -->
  <testresources>
   <testresource>
    <directory>src/main/webapp</directory>
   </testresource>
  </testresources>
 </build>

 <!-- Allow access to Spring milestones -->
 <repositories>
  <repository>
   <id>spring-milestones</id>
   <name>Spring Milestones</name>
   <url>http://repo.spring.io/milestone</url>
   <snapshots>
    <enabled>false</enabled>
   </snapshots>
  </repository>
 </repositories>
 <pluginrepositories>
  <pluginrepository>
   <id>spring-milestones</id>
   <url>http://repo.spring.io/milestone</url>
  </pluginrepository>
 </pluginrepositories>

</project>

OK, this one looks big, lets go through the interesting parts:

  • Packaging format is war to deploy the project in the STS or Eclipse server
  • Therefore we have to add a dependency to the spring-boot-starter-tomcat artifact which includes neccessary tomcat libraries
  • We have to add the scalate dependency, fortunately there exists a Spring MVC artifact
  • Scalate has a nice feature to precompile your templates in the maven package cycle and we only have to include the maven-scalate-plugin_2.10
  • Executing the tests Scalate logs some ugly warnings with exception stacktraces for missing folders (lib and classes) - we will use the maven-antrun-plugin to create the temporary directories in the test phase
  • Eclipse ignores this plugin and therefore we have to force the execution in the IDE too
  • Last we have to put the src/main/webapp folder to our test resources. Otherwise it is not possible to check the output of the templates in the tests

Spring configuration

For simplicity I put the Spring Boot configuration into the Application class.

package cnagel.spring_scalate;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableAutoConfiguration
@ComponentScan("cnagel.spring_scalate")
public class Application {

 public static void main(String... args) {
  SpringApplication.run(Application.class, args);
 }

}

Additionally it needs a Servlet initializer to run the application in a server like Tomcat or Jetty.

package cnagel.spring_scalate;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;

public class WebXml extends SpringBootServletInitializer {

 @Override
 protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
  return application.sources(Application.class);
 }

}

Being the default bootstrapping process it now needs a Scalate configuration to load the template engine.

package cnagel.spring_scalate.config;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.servlet.ServletContext;
import org.fusesource.scalate.layout.DefaultLayoutStrategy;
import org.fusesource.scalate.servlet.Config;
import org.fusesource.scalate.servlet.ServletTemplateEngine;
import org.fusesource.scalate.spring.view.ScalateViewResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.context.ServletContextAware;
import scala.collection.JavaConversions;

@Configuration
public class ScalateConfig {

 public static final String DEFAULT_PREFIX = "/WEB-INF/templates/";
 public static final String DEFAULT_SUFFIX = ".jade";
 public static final String DEFAULT_LAYOUT = "/WEB-INF/templates/layouts/default.jade";

 @Configuration
 protected static class ScalateConfigConfiguration implements ServletContextAware {

  private ServletContext servletContext;

  @Override
  public void setServletContext(ServletContext servletContext) {
   this.servletContext = servletContext;
  }

  @Bean
  public Config config() {
   return new Config() {

    @Override
    public ServletContext getServletContext() {
     return servletContext;
    }

    @Override
    public String getName() {
     return "unknown";
    }

    @Override
    public Enumeration getInitParameterNames() {
     return null;
    }

    @Override
    public String getInitParameter(String name) {
     return null;
    }
   };
  }

 }

 @Configuration
 protected static class ScalateServletTemplateEngineConfiguration {

  @Autowired
  private Config config;

  @Bean
  public ServletTemplateEngine servletTemplateEngine() {
   ServletTemplateEngine engine = new ServletTemplateEngine(config);
   List layouts = new ArrayList(1);
   layouts.add(DEFAULT_LAYOUT);
   engine.layoutStrategy_$eq(new DefaultLayoutStrategy(engine,
     JavaConversions.asScalaBuffer(layouts)));
   return engine;
  }
 }

 @Configuration
 protected static class ScalateViewResolverConfiguration {

  @Autowired
  private ServletTemplateEngine servletTemplateEngine;

  @Bean
  public ScalateViewResolver scalateViewResolver() {
   ScalateViewResolver resolver = new ScalateViewResolver();
   resolver.templateEngine_$eq(servletTemplateEngine);
   resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 20);
   resolver.setPrefix(DEFAULT_PREFIX);
   resolver.setSuffix(DEFAULT_SUFFIX);
   return resolver;
  }

 }

}

In a short version, the constants in the beginning of the class set the template folder, template extension, and the default layout. Furthermore it registers the ScalateViewResolver to render the views.

Controller

To show the functionality of Scalate we will only implement a very basic controller. This one renders an index site and offers an ajax endpoint to add form inputs to a list.

package cnagel.spring_scalate.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/")
public class HomeController {

 @RequestMapping(method = RequestMethod.GET)
 public ModelAndView index() {
  ModelAndView mav = new ModelAndView("layout:home/index");
  return mav;
 }

 @RequestMapping(method = RequestMethod.POST)
 public ModelAndView post(@RequestParam("text") String text) {
  ModelAndView mav = new ModelAndView("home/post");
  mav.addObject("text", text);
  return mav;
 }

}

In the index action we only define to render the home/index template. Given the default folder and extension in the configuration before, Scalate will use the src/main/webapp/WEB-INF/templates/home/index.jade file to render the output. The layout: prefix instructs Scalate to render the layout too and embed the view.
The post action handles incoming POST requests. Being an ajax endpoint it just renders the home/post view without the layout and assigns the posted text param.

Templates

There exist one layout and two templates in the project. The layout loads Twitter Bootstrap for CSS and jQuery for Javascript to easy the ajax request. Have a look at it to see the cuteness of the Jade template format.
The index view simply renders a form with a text input. By submitting the form, an ajax request gets executed and the response appended to a list.
The post view only renders a list element with the text param as content.

Testing

The tests simply use the provided Spring MVC mocks and check if both action render the correct views and if needed the layout.

package cnagel.spring_scalate.controller;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.internal.matchers.Contains;
import org.mockito.internal.matchers.StartsWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import cnagel.spring_scalate.Application;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration("classpath:.")
public class HomeControllerTests {

 @Autowired
 private WebApplicationContext ctx;

 private MockMvc mvc;

 @Before
 public void setUp() {
  mvc = MockMvcBuilders.webAppContextSetup(ctx).build();
 }

 @Test
 public void test_index() throws Exception {
  mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string(new Contains("<label>Say what ever:</label>"))).andExpect(content().string(new StartsWith("<!DOCTYPE html>")));
 }

 @Test
 public void test_post() throws Exception {
  mvc.perform(post("/").param("text", "Hello")).andExpect(status().isOk()).andExpect(content().string(new Contains("<li>Hello</li>")));
 }

}

Deployment

For development issues you can run the project in the server provided by Eclipse or STS. With mvn package it is possible to create a jar or war file by simply switching the package format in the pom.xml.
For me it was not possible to run the jar file under windows, having problems with illegal characters in the classpath.

Keine Kommentare :

Kommentar veröffentlichen

Hinweis: Nur ein Mitglied dieses Blogs kann Kommentare posten.

Spring MongoDB tutorial

Keine Kommentare
MongoDB

Time for a new Spring tutorial - this time we will implement a very simple but powerful data access to MongoDB. Therefore we will use the Spring Data Project which does the most work for us.

As always you can find the complete source code online on GitHub.
In my last post I showed a way how to use Spring Boot for bootstraping a Spring application. This project uses the described mechanisms and for a better understanding I recommend you to read the post if you are not familar with it.

Maven configuration

Let's start with the pom.xml and add the needed dependencies.

<?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>com.surpreso</groupId>
  <artifactId>spring-mongo</artifactId>
  <version>0.0.1-SNAPSHOT</version>

  <!-- The packaging format, use war for web projects -->
  <packaging>jar</packaging>

  <properties>
    <!-- The main class to start by executing java -jar -->
    <start-class>com.surpreso.spring_mongo.HelloWorldApplication</start-class>
  </properties>

  <!-- Inherit defaults from Spring Boot -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>0.5.0.BUILD-SNAPSHOT</version>
  </parent>

  <dependencies>
    <!-- Spring Boot -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!-- Spring Boot Test -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <!-- YAML parser -->
    <dependency>
      <groupId>org.yaml</groupId>
      <artifactId>snakeyaml</artifactId>
      <scope>runtime</scope>
    </dependency>
    <!-- @Inject annotation -->
    <dependency>
      <groupId>javax.inject</groupId>
      <artifactId>javax.inject</artifactId>
      <version>1</version>
    </dependency>
    <!-- Spring MongoDB integration -->
    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-mongodb</artifactId>
    </dependency>
    <!-- In-memory MongoDB for tests -->
    <dependency>
      <groupId>com.lordofthejars</groupId>
      <artifactId>nosqlunit-mongodb</artifactId>
      <version>0.7.8</version>
      <scope>test</scope>
    </dependency>
    <!-- Google Guava library -->
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>15.0</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <!-- Use plugin to package as an executable JAR -->
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

  <!-- Allow access to Spring milestones and snapshots -->
  <repositories>
    <repository>
      <id>spring-snapshots</id>
      <url>http://repo.spring.io/libs-snapshot</url>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
    <repository>
      <id>spring-milestones</id>
      <url>http://repo.spring.io/milestone</url>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
  </repositories>

  <pluginRepositories>
    <pluginRepository>
      <id>spring-snapshots</id>
      <url>http://repo.spring.io/snapshot</url>
    </pluginRepository>
    <pluginRepository>
      <id>spring-milestones</id>
      <url>http://repo.spring.io/milestone</url>
    </pluginRepository>
  </pluginRepositories>

</project>

The new part starts at line 49 where we add the spring-data-mongo artifact which includes the MongoDB Java Driver. To test our code I found a very nice in-memory implementation of MongoDB developed by FourSquare and avaiable on GitHub.

Last we add the Google Guava library which delivers nice helping methods for equals, toHash and toString.

Spring configuration

After setting up the maven project we have to configure the Spring context. To initialize Spring Boot correctly we will use the DefaultConfig class introduced in the last post.

package com.surpreso.spring_mongo.config;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableAutoConfiguration
@ComponentScan(basePackages = "com.surpreso.spring_mongo")
public class DefaultConfig {

}

Further we will add a MongoDB config which initializes the Spring MongoTemplate. Similar to JdbcTemplate this provides the access to our database.

package com.surpreso.spring_mongo.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import com.mongodb.Mongo;
import com.mongodb.MongoURI;

@Profile("default")
@Configuration
@EnableMongoRepositories(basePackages = "com.surpreso.spring_mongo.dao")
public class MongoDbConfig extends AbstractMongoConfiguration {

 @Value("${mongo.url}")
 private String url;

 @Value("${mongo.db}")
 private String databaseName;

 @Override
 protected String getDatabaseName() {
  return databaseName;
 }

 @Override
 public Mongo mongo() throws Exception {
  return new Mongo(new MongoURI(url));
 }

 @Override
 protected String getMappingBasePackage() {
  return "com.surpreso.spring_mongo.beans";
 }

}

This configuration extends the Spring AbstractMongoConfiguration, doing the most work for us.
In the test environment we want to use the in-memory MongoDB version and therefore we annotate the configuration with @Profile("default"). This instructs Spring to only load the configuration when there exists no profile.
A further new annotation is @EnableMongoRepositories(basePackages = "com.surpreso.spring_mongo.dao") which tells the spring-data-mongo project where to look for MongoDB repositories.
The getMappingBasePackage method simply defines our bean package.
The values for the database name and url are stored in the application.yml.

mongo:
  url: mongodb://127.0.0.1:27017
  db: foo

For the test environment exists an own configuration.

package com.surpreso.spring_mongo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import com.foursquare.fongo.Fongo;
import com.mongodb.Mongo;

@Profile("test")
@Configuration
public class MongoDbTestConfig extends MongoDbConfig {

 @Override
 public Mongo mongo() throws Exception {
  return new Fongo("foo test server").getMongo();
 }

}

This one extends the default MongoDB configuration but returns the in-memory implementation.

Beans & Repositories

In the defined mapping package we create a very simple bean called Foo having only an ID and a name property.

package com.surpreso.spring_mongo.beans;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import com.google.common.base.Objects;

@Document
public class Foo {

 @Id
 private Long id;
 private String name;

 public Foo() {
  super();
 }

 public Long getId() {
  return id;
 }

 public void setId(Long id) {
  this.id = id;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 @Override
 public boolean equals(Object obj) {
  if (obj == null) {
   return false;
  }
  if (!getClass().isInstance(obj)) {
   return false;
  }
  Foo foo = (Foo) obj;
  return Objects.equal(getId(), foo.getId())
    && Objects.equal(getName(), foo.getName());
 }

 @Override
 public int hashCode() {
  return Objects.hashCode(getId(), getName());
 }

 @Override
 public String toString() {
  return Objects.toStringHelper(this).addValue(getId())
    .addValue(getName()).toString();
 }

}

The bean gets annotated by @Document so Spring will map it to JSON, the Default MongoDB Format, on the fly. The @Id annotation for the id property defines the primary key for the document in the collection.
We will add equals, hashCode and toString methods for our tests and application output.
The implementation of the FooRepository will be done by Spring and we only have to define an interface giving the framework the needed information.

package com.surpreso.spring_mongo.dao;

import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import com.surpreso.spring_mongo.beans.Foo;

@Repository
public interface FooRepository extends MongoRepository<Foo, Long> {

}

Sweeeeet! That's all. Now we have a working data access for our Foos in MongoDB.

Testing

I know, we should not test the functionality of Spring if it implements the FooRepository in the right way, but let's do it to see if our fake MongoDB gets initialized and the data access works.

package com.surpreso.spring_mongo.dao;

import static org.junit.Assert.assertEquals;
import javax.inject.Inject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.SpringApplicationContextLoader;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.surpreso.spring_mongo.beans.Foo;
import com.surpreso.spring_mongo.config.DefaultConfig;

@ActiveProfiles("test")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { DefaultConfig.class }, loader = SpringApplicationContextLoader.class)
public class FooRepositoryTests {

 @Inject
 FooRepository repo;

 @Test
 public void test_basicOperations() throws Exception {
  // check if collection is empty
  assertEquals(0, repo.count());
  // create new document
  Foo foo = new Foo();
  foo.setId(1l);
  foo.setName("Foo 1");
  repo.save(foo);
  // store new document
  repo.save(foo);
  // check if document stored
  assertEquals(1, repo.count());
  // check stored document
  assertEquals(foo, repo.findOne(1l));
 }

}

The test context initialization is familiar to my last post and the test itself only perfoms some Basic operations on the database. Using the in-memory MongoDB you can run this test without a working MongoDB Environment.

Deployment & Execution

Last we will implement an application which uses a real MongoDB Environment (install instructions).

package com.surpreso.spring_mongo;

import javax.inject.Inject;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.Import;
import com.surpreso.spring_mongo.beans.Foo;
import com.surpreso.spring_mongo.config.DefaultConfig;
import com.surpreso.spring_mongo.dao.FooRepository;

@Import(DefaultConfig.class)
public class HelloWorldApplication implements CommandLineRunner {

 @Inject
 private FooRepository repo;

 public static void main(String... args) {
  SpringApplication.run(HelloWorldApplication.class, args);
 }

 public void run(String... args) throws Exception {
  // get size of the collection
  long count = repo.count();
  // create new document
  Foo foo = new Foo();
  foo.setId(count + 1);
  foo.setName("Foo " + foo.getId());
  // add new document to collection
  repo.save(foo);
  // output collection info
  System.out.println("= Found " + repo.count()
    + " documents in Foo collection");
  for (Foo f : repo.findAll()) {
   System.out.println("+ " + f);
  }
 }

}

The application just creates a new document in your collection and outputs all stored records. With each run of the program there will be one Foo more in the database.
How to deploy and run the program have a look at the last part of my previous post.

Keine Kommentare :

Kommentar veröffentlichen

Hinweis: Nur ein Mitglied dieses Blogs kann Kommentare posten.

Spring Boot example

Kommentare
MongoDB

In my first post I introduced a simple skeleton for bootstraping a Spring application. With the new release of the Spring Boot project this one gets obsolete and starting a Spring application gets much easier.

As before, the complete source code is available on GitHub.

To use the new Spring libraries it needs to configure the Maven POM file (there is Gradle support on the Spring Boot project site too). Last time this was simply done by a small dependency, now it needs some more XML code.

<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>com.surpreso</groupId>
 <artifactId>spring-skeleton</artifactId>
 <version>0.0.3-SNAPSHOT</version>

 <!-- The packaging format, use war for web projects -->
 <packaging>jar</packaging>

 <!-- Inherit defaults from Spring Boot -->
 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.2.1.RELEASE</version>
 </parent>

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

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

</project>

The XML code doesn't look difficult, but lets go through it step for step. First we have to define the packaging format. As this application is no web project we will choose jar, otherwise war.

Now there comes the interesting part: Our project needs to inherite default values from the Spring Boot module to get well compiled. Running the project in an IDE worked without, but to build a jar containing all neccessary libraries you need to define the parent maven module.

Further more I added two dependencies to my project, starting with spring-boot-starter, which includes all Spring core libraries. To write unit and integration tests in a spring-fashion I added spring-boot-starter-test too.

The spring-boot-maven-plugin is needed to package all neccessary libraries into one uber-jar, which eases the deployment massively.


Now lets come to the fun part of this tutorial. At first we define a Spring configuration class.
Most Spring Boot tutorials skip this step and merge configuration and application class. Using our configurations in tests too, I seperated both.

package com.surpreso.spring_skeleton;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class DefaultConfig {

}

The first annotation defines this class as a Spring configuration. The @EnableAutoConfiguration annotation enables the Spring Boot module to load default values and properties. With @ComponentScan we initiate Spring to search for auto wiring classes.

In the next step we create a very simple HelloWorld service which returns the current version of our application.

package com.surpreso.spring_skeleton;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class HelloWorldService {

 @Value("${app.version}")
 private String version;

 /**
  * @return the version
  */
 public String getVersion() {
  return version;
 }

}

The java code of the service class is very easy and there are only two spring relevant features used.
The @Compontent annotation tells Spring to auto wire the class. For simplicity I didn't define a interface before, which would be the enterprise way.
The @Value annotation introduces Spring to auto load the instance property version by the application property app.version. This property can be defined in a config file or by shell parameters.

In this project I choosed to read the properties from an YAML configuration file.

app:
  version: 0.3

For different application properties like database access information you can define further configuration files by naming them application-[profile].yml.
Before creating a test in the next steps, we define a application-test.yml configuration file to store a different version. Only being used in the test environment this file can use the src/test/resources folder.

app:
  version: 0.3-test

The following test simply checks if the HelloWorld serice loads the correct version from the properties.

package com.surpreso.spring_skeleton;

import static org.junit.Assert.assertTrue;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationContextLoader;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.surpreso.spring_skeleton.DefaultConfig;
import com.surpreso.spring_skeleton.HelloWorldService;

@ActiveProfiles("test")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DefaultConfig.class, loader = SpringApplicationContextLoader.class)
public class HelloWorldServiceTests {

 @Autowired
 HelloWorldService service;

 @Test
 public void test_getVersion() throws Exception {
  assertTrue(service.getVersion().endsWith("-test"));
 }

}

The test class has three annotations. The first one defines the profile being used to load properties and classes. The @RunWith annotation instructs junit to use the Spring test runner. Last the @ContextConfiguration defines the configuration classes and which loader is used.
The test itself is very straight forward, calls the getVersion method and checks if it ends with "-test". This should be true if the profile is loaded correctly.

Now we have tested our service, it is time to create an application which executes it. With Spring Boot this can be realized in a very elegant way.

package com.surpreso.spring_skeleton;

import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.Import;

@Import(DefaultConfig.class)
public class HelloWorldApplication implements CommandLineRunner {

 @Autowired
 private HelloWorldService helloWorldService;

 public static void main(String... args) {
  SpringApplication.run(HelloWorldApplication.class, args);
 }

 public void run(String... args) throws Exception {
  LoggerFactory.getLogger(getClass()).info(
    "This application works on version "
      + helloWorldService.getVersion());
 }

}

Not much code as you can see. The class simply implements the CommandLineRunner interface and executes the SpringApplication.run method by passing itself as parameter.
The @Import(DefaultConfig.class) annotation tells which configuration to us.
After loading the Spring framework, the run method gets executed and logs the version of the application, being delivered by our impressive service.
That's all! You can execute the application in your IDE or by maven and should see the Spring Boot splash output and the version of our project.


The last part of this tutorial covers the deployment of our project. The packaging of all libraries in one jar isn't trivial but Spring Boot offers a nice Maven plugin. All we need to execute is the maven package goal by running mvn package in the shell. The resulting jar file in the target folder has a size of about 5MB and contains all classes and libraries. To execute the jar we need to run the following command: java -jar target/spring-skeleton-0.0.3-SNAPSHOT.jar.

But there exists a second way to start our little application the Spring Boot way. Just execute mvn spring-boot:run in the shell and the app is launched but not packaged.

OK, that's all for today. Thanks to the Spring Boot developers for this nice project.

Kommentare :

Kommentar veröffentlichen

Hinweis: Nur ein Mitglied dieses Blogs kann Kommentare posten.