├── .gitignore
├── README.md
├── pom.xml
├── scripts
├── featureflags
│ ├── curls.md
│ └── flag.json
└── hyperloglog
│ └── curls.md
├── slides
└── High Performance Web Applications.pdf
└── src
└── main
└── java
└── com
└── justinrmiller
└── redismicroservices
├── Main.java
├── config
└── LocalRedisConfig.java
├── controllers
├── AuthTokenController.java
├── FeatureFlagsController.java
└── HyperLogLogController.java
├── pojo
├── Feature.java
└── TokenPermissions.java
└── service
├── AuthTokenService.java
├── FeatureFlagsService.java
└── HyperLogLogService.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # mac
2 | .DS_store
3 |
4 | # build
5 | target
6 |
7 | # idea
8 | .idea
9 | *.iml
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Redis Presentation and Source Code
2 | ==================================
3 |
4 | src/ - A Spring Boot application with three examples (Auth, Feature Flags, HyperLogLog/User Counts)
5 | slides/ - the slides
6 |
7 | Prereqs:
8 | - A running Redis instance (localhost)
9 | - Java 1.7
10 | - Maven
11 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.justinrmiller.redismicroservices
8 | redis-microservices
9 | 1.0-SNAPSHOT
10 |
11 |
12 |
13 | spring-milestones
14 | Spring Milestones
15 | http://repo.spring.io/milestone
16 |
17 | false
18 |
19 |
20 |
21 |
22 |
23 | UTF-8
24 | UTF-8
25 |
26 | 1.7
27 |
28 | [30.0-jre,)
29 |
30 | 1.5.0.M1
31 |
32 | 2.6.0
33 |
34 |
35 |
36 | org.springframework.boot
37 | spring-boot-starter-parent
38 | 1.2.1.RELEASE
39 |
40 |
41 |
42 |
43 | org.springframework.boot
44 | spring-boot-starter-web
45 |
46 |
47 | org.slf4j
48 | slf4j-log4j12
49 |
50 |
51 | log4j
52 | log4j
53 |
54 |
55 |
56 |
57 |
58 | org.springframework.boot
59 | spring-boot-starter-actuator
60 |
61 |
62 |
63 | com.google.guava
64 | guava
65 | ${guava.version}
66 |
67 |
68 |
69 | org.springframework.data
70 | spring-data-redis
71 | ${spring.data.redis.version}
72 |
73 |
74 | org.slf4j
75 | slf4j-log4j12
76 |
77 |
78 | log4j
79 | log4j
80 |
81 |
82 |
83 |
84 |
85 | redis.clients
86 | jedis
87 | ${jedis.version}
88 | jar
89 | compile
90 |
91 |
92 |
93 |
94 |
95 |
96 | org.springframework.boot
97 | spring-boot-maven-plugin
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/scripts/featureflags/curls.md:
--------------------------------------------------------------------------------
1 | Add a feature flag (MEGA_FEATURE) for application "app" with flag definition found in flag.json (note: the name in the json must match the name in the path):
2 |
3 | curl -X POST -d@flag.json -H "Content-Type: application/json" http://localhost:8080/flags/application/app/feature/MEGA_FEATURE
4 |
5 | Retrieve all feature flags for application "app":
6 |
7 | curl http://localhost:8080/flags/application/app
8 |
9 |
--------------------------------------------------------------------------------
/scripts/featureflags/flag.json:
--------------------------------------------------------------------------------
1 | {"name":"MEGA_FEATURE","createTimestamp":1421417126,"author":"Justin Miller","enabled":false}
2 |
--------------------------------------------------------------------------------
/scripts/hyperloglog/curls.md:
--------------------------------------------------------------------------------
1 | Add a user visit for the main site (adding the user multiple times should result in the same count):
2 |
3 | curl -X POST http://localhost:8080/hyperloglog/key/user_main_site_visits/value/1
4 |
5 | Get a count for the number of visitors to the main site:
6 |
7 | curl http://localhost:8080/hyperloglog/key/user_main_site_visits
8 |
--------------------------------------------------------------------------------
/slides/High Performance Web Applications.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justinrmiller/spring-boot-redis-microservices/f5383490c3481bb0000e2d89e3e26b012064d542/slides/High Performance Web Applications.pdf
--------------------------------------------------------------------------------
/src/main/java/com/justinrmiller/redismicroservices/Main.java:
--------------------------------------------------------------------------------
1 | package com.justinrmiller.redismicroservices;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import org.springframework.boot.SpringApplication;
7 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
8 |
9 | import org.springframework.context.annotation.ComponentScan;
10 |
11 | @EnableAutoConfiguration
12 | @ComponentScan
13 | public class Main {
14 | private static Logger logger = LoggerFactory.getLogger(Main.class);
15 |
16 | public static void main(String[] args) throws Exception {
17 | SpringApplication.run(Main.class, args);
18 | }
19 | }
--------------------------------------------------------------------------------
/src/main/java/com/justinrmiller/redismicroservices/config/LocalRedisConfig.java:
--------------------------------------------------------------------------------
1 | package com.justinrmiller.redismicroservices.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.ComponentScan;
5 | import org.springframework.context.annotation.Configuration;
6 |
7 | import org.springframework.data.redis.connection.RedisConnectionFactory;
8 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
9 | import org.springframework.data.redis.core.StringRedisTemplate;
10 |
11 | import redis.clients.jedis.JedisPoolConfig;
12 |
13 | /**
14 | * @author Justin Miller (Copyright 2015)
15 | */
16 | @Configuration
17 | @ComponentScan(basePackages="com.justinrmiller.redismicroservices.service")
18 | public class LocalRedisConfig {
19 | @Bean
20 | public RedisConnectionFactory jedisConnectionFactory(){
21 | JedisPoolConfig poolConfig=new JedisPoolConfig();
22 | poolConfig.setMaxIdle(5);
23 | poolConfig.setMinIdle(1);
24 | poolConfig.setTestOnBorrow(true);
25 | poolConfig.setTestOnReturn(true);
26 | poolConfig.setTestWhileIdle(true);
27 | poolConfig.setNumTestsPerEvictionRun(10);
28 | poolConfig.setTimeBetweenEvictionRunsMillis(60000);
29 |
30 | JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(poolConfig);
31 | return jedisConnectionFactory;
32 | }
33 |
34 | @Bean
35 | public StringRedisTemplate redisTemplate() {
36 | StringRedisTemplate redisTemplate = new StringRedisTemplate(jedisConnectionFactory());
37 | return redisTemplate;
38 | }
39 | }
--------------------------------------------------------------------------------
/src/main/java/com/justinrmiller/redismicroservices/controllers/AuthTokenController.java:
--------------------------------------------------------------------------------
1 | package com.justinrmiller.redismicroservices.controllers;
2 |
3 | import com.google.common.base.Optional;
4 |
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.http.HttpStatus;
10 | import org.springframework.http.ResponseEntity;
11 | import org.springframework.web.bind.annotation.*;
12 |
13 | import java.io.IOException;
14 |
15 | import com.justinrmiller.redismicroservices.pojo.TokenPermissions;
16 | import com.justinrmiller.redismicroservices.service.AuthTokenService;
17 |
18 | /**
19 | * @author Justin Miller (Copyright 2015)
20 | */
21 | public class AuthTokenController {
22 | private static Logger logger = LoggerFactory.getLogger(AuthTokenController.class);
23 |
24 | @Autowired
25 | private AuthTokenService authTokenService;
26 |
27 | @RequestMapping(
28 | value = "/auth/token/{token}",
29 | method = RequestMethod.GET,
30 | produces = "application/json; charset=utf-8")
31 | @ResponseBody
32 | public ResponseEntity get(@PathVariable String token) throws IOException {
33 | logger.info("Getting a token");
34 |
35 | Optional tokenPermissionsOptional = authTokenService.get(token);
36 |
37 | if (tokenPermissionsOptional.isPresent()) {
38 | return new ResponseEntity<>(tokenPermissionsOptional.get(), HttpStatus.OK);
39 | } else {
40 | return new ResponseEntity<>(HttpStatus.NOT_FOUND);
41 | }
42 | }
43 |
44 | @RequestMapping(
45 | value = "/auth/token/{token}",
46 | method = RequestMethod.POST,
47 | produces = "application/json")
48 | @ResponseBody
49 | public ResponseEntity post(
50 | @PathVariable String token,
51 | @RequestBody TokenPermissions tokenData) throws IOException {
52 | logger.info("Token {}", tokenData);
53 |
54 | authTokenService.add(token, tokenData);
55 | return new ResponseEntity(HttpStatus.OK);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/justinrmiller/redismicroservices/controllers/FeatureFlagsController.java:
--------------------------------------------------------------------------------
1 | package com.justinrmiller.redismicroservices.controllers;
2 |
3 | import com.google.common.base.Optional;
4 |
5 | import com.google.common.collect.ImmutableList;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.http.HttpStatus;
11 | import org.springframework.http.ResponseEntity;
12 | import org.springframework.web.bind.annotation.*;
13 |
14 | import java.io.IOException;
15 | import java.util.List;
16 |
17 | import com.justinrmiller.redismicroservices.pojo.Feature;
18 | import com.justinrmiller.redismicroservices.service.FeatureFlagsService;
19 |
20 | /**
21 | * @author Justin Miller (Copyright 2015)
22 | */
23 | @RestController
24 | public class FeatureFlagsController {
25 | private static Logger logger = LoggerFactory.getLogger(FeatureFlagsController.class);
26 |
27 | @Autowired
28 | private FeatureFlagsService featureFlagsService;
29 |
30 | @RequestMapping(
31 | value = "/flags/application/{application}/feature/{feature}",
32 | method = RequestMethod.GET,
33 | produces = "application/json; charset=utf-8")
34 | @ResponseBody
35 | public ResponseEntity get(
36 | @PathVariable String application,
37 | @PathVariable String feature) throws IOException {
38 | logger.info("Getting a feature flag");
39 |
40 | Optional featureOptional = featureFlagsService.get(application, feature);
41 |
42 | if (featureOptional.isPresent()) {
43 | return new ResponseEntity<>(featureOptional.get(), HttpStatus.OK);
44 | } else {
45 | return new ResponseEntity<>(HttpStatus.NOT_FOUND);
46 | }
47 | }
48 |
49 | @RequestMapping(
50 | value = "/flags/application/{application}",
51 | method = RequestMethod.GET,
52 | produces = "application/json; charset=utf-8")
53 | @ResponseBody
54 | public ResponseEntity get(
55 | @PathVariable String application) throws IOException {
56 | logger.info("Getting all feature flags");
57 |
58 | Optional> featureFlagOptional = featureFlagsService.getAll(application);
59 |
60 | if (featureFlagOptional.isPresent()) {
61 | return new ResponseEntity<>(featureFlagOptional.get(), HttpStatus.OK);
62 | } else {
63 | return new ResponseEntity<>(HttpStatus.NOT_FOUND);
64 | }
65 | }
66 |
67 | @RequestMapping(
68 | value = "/flags/application/{application}/feature/{feature}",
69 | method = RequestMethod.POST,
70 | produces = "application/json")
71 | @ResponseBody
72 | public ResponseEntity post(
73 | @PathVariable String application,
74 | @PathVariable String feature,
75 | @RequestBody Feature featureData) throws IOException {
76 | logger.info("Feature {}", featureData);
77 |
78 | if (!feature.equals(featureData.getName())) {
79 | // error out
80 | return new ResponseEntity(HttpStatus.BAD_REQUEST);
81 | }
82 |
83 | featureFlagsService.add(application, featureData);
84 | return new ResponseEntity(HttpStatus.OK);
85 | }
86 | }
87 |
88 |
--------------------------------------------------------------------------------
/src/main/java/com/justinrmiller/redismicroservices/controllers/HyperLogLogController.java:
--------------------------------------------------------------------------------
1 | package com.justinrmiller.redismicroservices.controllers;
2 |
3 | import com.justinrmiller.redismicroservices.service.HyperLogLogService;
4 |
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.http.HttpStatus;
10 | import org.springframework.http.ResponseEntity;
11 | import org.springframework.web.bind.annotation.*;
12 |
13 | /**
14 | * @author Justin Miller (Copyright 2015)
15 | */
16 | @RestController
17 | public class HyperLogLogController {
18 | private static Logger logger = LoggerFactory.getLogger(HyperLogLogController.class);
19 |
20 | @Autowired
21 | private HyperLogLogService hyperLogLogService;
22 |
23 | @RequestMapping(
24 | value = "/hyperloglog/key/{key}",
25 | method = RequestMethod.GET,
26 | produces = "application/json; charset=utf-8")
27 | @ResponseBody
28 | public ResponseEntity get(@PathVariable String key) {
29 | logger.info("Retrieving HyperLogLog count: {}", key);
30 |
31 | Long count = hyperLogLogService.count(key);
32 |
33 | return new ResponseEntity<>(count, HttpStatus.OK);
34 | }
35 |
36 | @RequestMapping(
37 | value = "/hyperloglog/key/{key}/value/{value}",
38 | method = RequestMethod.POST,
39 | produces = "application/json")
40 | @ResponseBody
41 | public ResponseEntity post(@PathVariable String key, @PathVariable String value) {
42 | logger.info("Key: {}, Value: {}", key, value);
43 |
44 | hyperLogLogService.add(key, value);
45 | return new ResponseEntity(HttpStatus.OK);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/com/justinrmiller/redismicroservices/pojo/Feature.java:
--------------------------------------------------------------------------------
1 | package com.justinrmiller.redismicroservices.pojo;
2 |
3 | /**
4 | * @author Justin Miller (Copyright 2015)
5 | */
6 | public class Feature {
7 | private String name;
8 | private Long createTimestamp;
9 | private String author;
10 | private Boolean enabled;
11 |
12 | public Feature() {
13 | // jackson
14 | }
15 |
16 | public Feature(String name, Long createTimestamp, String author, Boolean enabled) {
17 | this.name = name;
18 | this.createTimestamp = createTimestamp;
19 | this.author = author;
20 | this.enabled = enabled;
21 | }
22 |
23 | public Boolean getEnabled() {
24 | return enabled;
25 | }
26 |
27 | public String getName() {
28 | return name;
29 | }
30 |
31 | public Long getCreateTimestamp() {
32 | return createTimestamp;
33 | }
34 |
35 | public String getAuthor() {
36 | return author;
37 | }
38 |
39 | @Override
40 | public boolean equals(Object o) {
41 | if (this == o) return true;
42 | if (o == null || getClass() != o.getClass()) return false;
43 |
44 | Feature feature = (Feature) o;
45 |
46 | if (author != null ? !author.equals(feature.author) : feature.author != null) return false;
47 | if (createTimestamp != null ? !createTimestamp.equals(feature.createTimestamp) : feature.createTimestamp != null)
48 | return false;
49 | if (enabled != null ? !enabled.equals(feature.enabled) : feature.enabled != null) return false;
50 | if (name != null ? !name.equals(feature.name) : feature.name != null) return false;
51 |
52 | return true;
53 | }
54 |
55 | @Override
56 | public int hashCode() {
57 | int result = name != null ? name.hashCode() : 0;
58 | result = 31 * result + (createTimestamp != null ? createTimestamp.hashCode() : 0);
59 | result = 31 * result + (author != null ? author.hashCode() : 0);
60 | result = 31 * result + (enabled != null ? enabled.hashCode() : 0);
61 | return result;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/justinrmiller/redismicroservices/pojo/TokenPermissions.java:
--------------------------------------------------------------------------------
1 | package com.justinrmiller.redismicroservices.pojo;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * @author Justin Miller (Copyright 2015)
7 | */
8 | public class TokenPermissions {
9 | private Long createTimestamp;
10 | private List permissions;
11 |
12 | public TokenPermissions() {
13 | // jackson
14 | }
15 |
16 | public TokenPermissions(Long createTimestamp, List permissions) {
17 | this.createTimestamp = createTimestamp;
18 | this.permissions = permissions;
19 | }
20 |
21 | public Long getCreateTimestamp() {
22 | return createTimestamp;
23 | }
24 |
25 | public List getPermissions() {
26 | return permissions;
27 | }
28 |
29 | @Override
30 | public boolean equals(Object o) {
31 | if (this == o) return true;
32 | if (o == null || getClass() != o.getClass()) return false;
33 |
34 | TokenPermissions that = (TokenPermissions) o;
35 |
36 | if (createTimestamp != null ? !createTimestamp.equals(that.createTimestamp) : that.createTimestamp != null)
37 | return false;
38 | if (permissions != null ? !permissions.equals(that.permissions) : that.permissions != null) return false;
39 |
40 | return true;
41 | }
42 |
43 | @Override
44 | public int hashCode() {
45 | int result = createTimestamp != null ? createTimestamp.hashCode() : 0;
46 | result = 31 * result + (permissions != null ? permissions.hashCode() : 0);
47 | return result;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/justinrmiller/redismicroservices/service/AuthTokenService.java:
--------------------------------------------------------------------------------
1 | package com.justinrmiller.redismicroservices.service;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 |
5 | import com.google.common.base.Optional;
6 |
7 | import com.justinrmiller.redismicroservices.pojo.TokenPermissions;
8 |
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.data.redis.core.HashOperations;
11 | import org.springframework.data.redis.core.StringRedisTemplate;
12 | import org.springframework.stereotype.Component;
13 |
14 | import java.io.IOException;
15 |
16 | /**
17 | * @author Justin Miller (Copyright 2015)
18 | */
19 | @Component
20 | public class AuthTokenService {
21 | private final static ObjectMapper MAPPER = new ObjectMapper();
22 |
23 | @Autowired
24 | private StringRedisTemplate redisTemplate;
25 |
26 | public Optional get(String token) throws IOException {
27 | HashOperations hashOps = redisTemplate.opsForHash();
28 |
29 | String permissionsAsJson = hashOps.get(token, "permissions");
30 |
31 | if (permissionsAsJson != null) {
32 | return Optional.of(MAPPER.readValue(permissionsAsJson, TokenPermissions.class));
33 | } else {
34 | return Optional.absent();
35 | }
36 | }
37 |
38 | public void add(String token, TokenPermissions permissions) throws IOException {
39 | HashOperations hashOps = redisTemplate.opsForHash();
40 |
41 | String permissionsAsJson = MAPPER.writeValueAsString(permissions);
42 |
43 | hashOps.put(token, "permissions", permissionsAsJson);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/justinrmiller/redismicroservices/service/FeatureFlagsService.java:
--------------------------------------------------------------------------------
1 | package com.justinrmiller.redismicroservices.service;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 |
5 | import com.google.common.base.Optional;
6 | import com.google.common.collect.ImmutableList;
7 |
8 | import com.justinrmiller.redismicroservices.pojo.Feature;
9 |
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.data.redis.core.HashOperations;
12 | import org.springframework.data.redis.core.StringRedisTemplate;
13 | import org.springframework.stereotype.Component;
14 |
15 | import java.io.IOException;
16 | import java.util.List;
17 |
18 | /**
19 | * @author Justin Miller (Copyright 2015)
20 | */
21 | @Component
22 | public class FeatureFlagsService {
23 | private final static ObjectMapper MAPPER = new ObjectMapper();
24 |
25 | @Autowired
26 | private StringRedisTemplate redisTemplate;
27 |
28 | public Optional get(String application, String feature) throws IOException {
29 | HashOperations hashOps = redisTemplate.opsForHash();
30 |
31 | String featureJson = hashOps.get(application, feature);
32 |
33 | if (featureJson != null) {
34 | return Optional.of(MAPPER.readValue(featureJson, Feature.class));
35 | } else {
36 | return Optional.absent();
37 | }
38 | }
39 |
40 | public Optional> getAll(String application) throws IOException {
41 | HashOperations hashOps = redisTemplate.opsForHash();
42 |
43 | List featureJson = hashOps.values(application);
44 |
45 | if (featureJson != null) {
46 | ImmutableList.Builder builder = new ImmutableList.Builder<>();
47 |
48 | for (String s : featureJson) {
49 | builder.add(MAPPER.readValue(s, Feature.class));
50 | }
51 | return Optional.of(builder.build());
52 | } else {
53 | return Optional.absent();
54 | }
55 | }
56 |
57 | public void add(String application, Feature feature) throws IOException {
58 | HashOperations hashOps = redisTemplate.opsForHash();
59 |
60 | String featureAsJson = MAPPER.writeValueAsString(feature);
61 |
62 | hashOps.put(application, feature.getName(), featureAsJson);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/justinrmiller/redismicroservices/service/HyperLogLogService.java:
--------------------------------------------------------------------------------
1 | package com.justinrmiller.redismicroservices.service;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.data.redis.core.StringRedisTemplate;
5 | import org.springframework.stereotype.Component;
6 |
7 | /**
8 | * @author Justin Miller (Copyright 2015)
9 | *
10 | * This relies on a milestone release of Spring Data
11 | * see:
12 | * https://github.com/spring-projects/spring-data-redis/pull/116
13 | */
14 | @Component
15 | public class HyperLogLogService {
16 | @Autowired private StringRedisTemplate redisTemplate;
17 |
18 | public Long count(String ... key) {
19 | return redisTemplate.opsForHyperLogLog().size(key);
20 | }
21 |
22 | public void add(String key, String ... values) {
23 | redisTemplate.opsForHyperLogLog().add(key, values);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------