Thursday, April 23, 2015

Spring-session demonstration using docker-compose

I have earlier written about an exciting new project called Spring-session which provides a clean way to externalize user sessions for java based web applications.

I managed to get a good demonstration set-up for spring-session using docker-compose which shows off the strengths of this project and I wanted to write about this here. In short, this is the set-up that running docker-compose will bring up:


Two instances of the application which makes use of Spring-session are started up, these instances use the same redis container for storing the session state and are in turn fronted by an nginx server.

All that needs to be done to bring up this topology is to:

  • clone my repo available here
  • install docker-compose 
  • build the app - "mvn package -DskipTests" - skipping tests as the tests depend on a local redis-server, which may or may not be available
  • run "docker-compose up" in the cloned folder
That is it, if everything has been set-up cleanly nginx should be available at http://docker-ip  url - in my mac, it is typically http://192.168.59.103

Details and Demonstration:
Docker-compose is a tool providing a means to put together a set of docker containers into a coherent stack. The stack can be defined declaratively, and the following is a sample stack used here:

nginx:
  image: nginx
  volumes:
    - nginx:/etc/nginx:ro
  links:
    - shop1
    - shop2
  ports:
   - "80:80"

shop1:
  build: .
  hostname: shop1
  links:
    - redis
  ports:
    - "8081:8080"

shop2:
  build: .
  hostname: shop2
  links:
    - redis
  ports:
    - "8082:8080"

redis:
  image: redis
  hostname: redis
  ports:
    - "6379:6379"


This application itself makes use of the user session to maintain the state of a "shopping cart", since this application is configured to use spring-session the session will be maintained in redis database. There are two instances of the application behind nginx, either one of the servers would end up getting the request, but the externalized session state would continue to work seamlessly irrespective of the application instance handling the request.

The following is the view of the shopping cart:


The session id and the details of the instance handling the request are printed at the bottom of the page.

As can be seen in the following screenshot, even if a different instance handles the request, the session state continues to be cleanly maintained.


Wednesday, April 8, 2015

Spring Enable* annotation - writing a custom Enable annotation

Spring provides a range of annotations with names starting with Enable*, these annotations in essence enable certain Spring managed features to be activated. One good example of such an annotation is EnableWebMvc which brings in all the beans needed to support a MVC flow in Spring based applications. Another good example is the EnableAsync annotation to activate beans to support async functionality in Spring based applications.

I was curious about how such annotations work and wanted to document my understanding. The way these annotations are supported can be considered part of the SPI and so may break if the internal implementation changes in future.

Simple Enable* Annotations

One way to think about these custom annotations is that they add a set of new beans into the Spring's application context. Let us start by defining one such custom annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface EnableSomeBeans {}



and apply this annotation on a Spring @Configuration class:

@Configuration
@EnableSomeBeans
public static class SpringConfig {}

So now to bring in a set of beans when this annotation is applied is as simple as adding the set of beans to bring in using @Import annotation this way:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(SomeBeanConfiguration.class)
@interface EnableSomeBeans {}

That is essentially it, if this imported @Configuration class defines any beans, they would now be part of the Application context:

@Configuration
class SomeBeanConfiguration {

    @Bean
    public String aBean1() {
        return "aBean1";
    }

    @Bean
    public String aBean2() {
        return "aBean2";
    }
}

Here is a gist with a working sample.

Enable* Annotations with Selectors


Enable annotations can be far more complex though, they can activate a different family of beans based on the context around them. An example of such an annotation is EnableCaching which activates configuration based on different caching implementations available in the classpath.

Writing such Enable* annotations is a little more involved than the simpler example earlier. As before start with a custom annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(SomeBeanConfigurationSelector.class)
public @interface EnableSomeBeansSelector {
    String criteria() default "default";
}

Note that in this case the custom annotation has a sample field called criteria, what I want to do is to activate two different set of beans based on this criteria. This can be achieved using a @Configuration selector which can return different @Configuration file based on the context(in this instance the value of the criteria field). This selector has a simple signature and this is a sample implementation:

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;

public class SomeBeanConfigurationSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        AnnotationAttributes attributes =
                AnnotationAttributes.fromMap(
                        importingClassMetadata.getAnnotationAttributes(EnableSomeBeansSelector.class.getName(), false));
        String criteria = attributes.getString("criteria");
        if (criteria.equals("default")) {
            return new String[]{"enableannot.selector.SomeBeanConfigurationDefault"};
        }else {
            return new String[]{"enableannot.selector.SomeBeanConfigurationType1"};
        }
    }
}

@Configuration
class SomeBeanConfigurationType1 {

    @Bean
    public String aBean() {
        return "Type1";
    }

}

@Configuration
class SomeBeanConfigurationDefault {

    @Bean
    public String aBean() {
        return "Default";
    }

}

So if the criteria field is "default", the beans in "SomeBeanConfigurationDefault" gets added in, else the one in "SomeBeanConfigurationType1"

Here is a gist with a working sample.

Conclusion

I hope this gives an appreciation for how Spring internally implements the @Enable* annotations, as an application developer you may not need to create such annotations yourself, a simpler mechanism will be to use @Configuration classes and Spring bean profiles to compose applications.