├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── javieraviles │ │ └── springredis │ │ ├── SpringRedisApplication.java │ │ ├── entities │ │ └── User.java │ │ ├── rest │ │ ├── UserResource.java │ │ └── UserResourceAdvice.java │ │ └── services │ │ └── UserService.java └── resources │ └── application.properties └── test └── java └── com └── javieraviles └── springredis └── SpringRedisApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | src/main/java/META-INF/MANIFEST.MF 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Javier Aviles 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spring-boot-redis-rest 2 | 3 | API REST boilerplate with Spring Boot and Redis as database. No big motivation, just because is fun :) 4 | 5 | ## TODO 6 | - Add Unit Tests 7 | 8 | ## Getting started with Spring Boot 9 | The [reference documentation](https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/) includes detailed [installation instructions](https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#getting-started-installing-spring-boot) 10 | as well as a comprehensive [``getting 11 | started``](https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#getting-started-first-application) guide. Documentation is published in [HTML](https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/), [PDF](https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/pdf/spring-boot-reference.pdf) and [EPUB](https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/epub/spring-boot-reference.epub) formats. 12 | 13 | ## Rest Controller 14 | The class `UserResource.java` contains every endpoint. The code is very self-descriptive. 15 | 16 | **AVAILABLE ENDPOINTS** 17 | 18 | | method | resource | description | 19 | |:------------------|:------------------|:----------------------------------------------------------------------------------------------| 20 | | `GET` | `/users` | get the collection of users -> 200(OK) | 21 | | `GET` | `/users/:id` | get a user by Id -> 200(OK), 400(wrong id format), 404(no user with such id) | 22 | | `POST` | `/users` | creates a user in the DB -> 201(created) | 23 | | `PUT` | `/users/:id` | updates a user in the DB -> 204(updated), 400(wrong id format), 404(no user with such id) | 24 | | `DELETE` | `/users/:id` | deletes a user from the DB -> 204(deleted), 400(wrong id format), 404(no user with such id) | 25 | 26 | ## Exception Handling 27 | An Spring Boot advice `UserResourceAdvice.java` has been created for handling exceptions and convert them into desired responses. This way both the Rest Controller and the CRUD service are cleaner as they don't need to contain the error handling. 28 | 29 | | exception | response | 30 | |:----------------------------------------------|:--------------------------------------| 31 | | `NotFoundException` | `404 NOT FOUND` | 32 | | `RedisConnectionFailureException` | `500 INTERNAL SERVER ERROR` | 33 | | `IllegalArgumentException` | `400 BAD REQUEST` | 34 | 35 | ## CRUD service 36 | The CRUD is done in `UserService.java` through a **RedisTemplate**, created as a @bean in `SpringRedisApplication.java`. A RedisTemplate should be created for each entity that will be persisted to Redis. 37 | 38 | Redis keys for stored users will follow the format `users:`. That way, the whole collection of users can be fetched through the pattern `users:*`. The code is dead simple and very self-descriptive. 39 | 40 | ## Redis connection 41 | The connection to Redis is established through a `Jedis client`. A `JedisConnectionFactory` is defined in `SpringRedisApplication.java`, taking Redis hostname and password from application properties. 42 | 43 | Notice that a `StringRedisSerializer` has been used for keys, otherwise the marshalling would be done through the default serializer (as happens with stored values since we are not specifying any serializer). 44 | 45 | ## Local dev environment 46 | **Eclipse IDE** -> Spring Tools for Eclipse IDE definitely useful. 47 | 48 | Run local **Redis server** in Docker: 49 | docker pull redis 50 | docker run -d --name redis -p 6379:6379 redis 51 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.javieraviles 7 | spring-redis 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | spring-redis 12 | Boilerplate project for creating a REST API with Spring Boot and Redis 13 | 14 | 15 | UTF-8 16 | UTF-8 17 | 1.8 18 | ${java.version} 19 | ${java.version} 20 | 2.0.4.RELEASE 21 | 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-parent 28 | ${spring-boot.version} 29 | pom 30 | import 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-jersey 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-web 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-test 49 | test 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-data-redis 55 | 56 | 57 | io.lettuce 58 | lettuce-core 59 | 60 | 61 | 62 | 63 | redis.clients 64 | jedis 65 | 66 | 67 | 68 | 69 | spring-redis 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-maven-plugin 74 | ${spring-boot.version} 75 | 76 | 77 | 78 | repackage 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/main/java/com/javieraviles/springredis/SpringRedisApplication.java: -------------------------------------------------------------------------------- 1 | package com.javieraviles.springredis; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.ComponentScan; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.data.redis.connection.RedisStandaloneConfiguration; 10 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 11 | import org.springframework.data.redis.core.RedisTemplate; 12 | import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; 13 | import org.springframework.data.redis.serializer.StringRedisSerializer; 14 | 15 | import com.javieraviles.springredis.entities.User; 16 | 17 | @SpringBootApplication 18 | @EnableRedisRepositories 19 | @ComponentScan 20 | @Configuration 21 | public class SpringRedisApplication { 22 | 23 | @Value(value = "${redis.hostname}") 24 | private String redisHostname; 25 | 26 | @Value(value = "${redis.port}") 27 | private int redisPort; 28 | 29 | public static void main(final String[] args) { 30 | SpringApplication.run(SpringRedisApplication.class, args); 31 | } 32 | 33 | @Bean 34 | JedisConnectionFactory jedisConnectionFactory() { 35 | RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisHostname, redisPort); 36 | return new JedisConnectionFactory(redisStandaloneConfiguration); 37 | } 38 | 39 | @Bean 40 | public RedisTemplate userTemplate() { 41 | RedisTemplate template = new RedisTemplate<>(); 42 | template.setConnectionFactory(jedisConnectionFactory()); 43 | template.setKeySerializer(new StringRedisSerializer()); 44 | return template; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/javieraviles/springredis/entities/User.java: -------------------------------------------------------------------------------- 1 | package com.javieraviles.springredis.entities; 2 | 3 | import java.io.Serializable; 4 | 5 | public class User implements Serializable { 6 | 7 | private static final long serialVersionUID = -305726463442998985L; 8 | 9 | private String id; 10 | 11 | private String name; 12 | 13 | private String email; 14 | 15 | public String getId() { 16 | return id; 17 | } 18 | 19 | public void setId(final String id) { 20 | this.id = id; 21 | } 22 | 23 | public String getName() { 24 | return name; 25 | } 26 | 27 | public void setName(final String name) { 28 | this.name = name; 29 | } 30 | 31 | public String getEmail() { 32 | return email; 33 | } 34 | 35 | public void setEmail(final String email) { 36 | this.email = email; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/javieraviles/springredis/rest/UserResource.java: -------------------------------------------------------------------------------- 1 | package com.javieraviles.springredis.rest; 2 | 3 | import java.util.List; 4 | 5 | import javax.ws.rs.core.MediaType; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.DeleteMapping; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.PutMapping; 15 | import org.springframework.web.bind.annotation.RequestBody; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RestController; 18 | 19 | import com.javieraviles.springredis.entities.User; 20 | import com.javieraviles.springredis.services.UserService; 21 | 22 | @RestController 23 | @RequestMapping("/users") 24 | public class UserResource { 25 | 26 | @Autowired 27 | private UserService userService; 28 | 29 | @GetMapping(value = "/", produces = MediaType.APPLICATION_JSON) 30 | public ResponseEntity> getUsers() { 31 | final List users = userService.findByPattern("*"); 32 | return new ResponseEntity<>(users, HttpStatus.OK); 33 | } 34 | 35 | @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON) 36 | public ResponseEntity getUsers(@PathVariable("id") final String userId) { 37 | final User user = userService.findById(userId); 38 | return new ResponseEntity<>(user, HttpStatus.OK); 39 | } 40 | 41 | @PostMapping(value = "/", consumes = MediaType.APPLICATION_JSON) 42 | public ResponseEntity createUser(@RequestBody final User user) { 43 | userService.save(user); 44 | return new ResponseEntity<>(HttpStatus.CREATED); 45 | } 46 | 47 | @PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON) 48 | public ResponseEntity updateUser(@PathVariable("id") final String userId, @RequestBody final User user) { 49 | user.setId(userId); 50 | userService.update(user); 51 | return new ResponseEntity<>(HttpStatus.NO_CONTENT); 52 | } 53 | 54 | @DeleteMapping(value = "/{id}") 55 | public ResponseEntity deleteUser(@PathVariable("id") final String userId) { 56 | userService.delete(userId); 57 | return new ResponseEntity<>(HttpStatus.NO_CONTENT); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/javieraviles/springredis/rest/UserResourceAdvice.java: -------------------------------------------------------------------------------- 1 | package com.javieraviles.springredis.rest; 2 | 3 | import javax.ws.rs.NotFoundException; 4 | import javax.ws.rs.core.MediaType; 5 | 6 | import org.springframework.data.redis.RedisConnectionFailureException; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.ControllerAdvice; 10 | import org.springframework.web.bind.annotation.ExceptionHandler; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 13 | 14 | @ControllerAdvice(assignableTypes = {UserResource.class}) 15 | @RequestMapping(produces = MediaType.APPLICATION_JSON) 16 | public class UserResourceAdvice extends ResponseEntityExceptionHandler { 17 | 18 | @ExceptionHandler(NotFoundException.class) 19 | public ResponseEntity assertionException(final NotFoundException e) { 20 | return new ResponseEntity<>(errorToJson(e.getMessage()), HttpStatus.NOT_FOUND); 21 | } 22 | 23 | @ExceptionHandler(RedisConnectionFailureException.class) 24 | public ResponseEntity assertionException(final RedisConnectionFailureException e) { 25 | return new ResponseEntity<>(errorToJson("Redis not available"), HttpStatus.INTERNAL_SERVER_ERROR); 26 | } 27 | 28 | @ExceptionHandler(IllegalArgumentException.class) 29 | public ResponseEntity assertionException(final IllegalArgumentException e) { 30 | return new ResponseEntity<>(errorToJson(e.getMessage()),HttpStatus.BAD_REQUEST); 31 | } 32 | 33 | private String errorToJson(final String error) { 34 | return "{\"error\":\"" + error + "\"}"; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/javieraviles/springredis/services/UserService.java: -------------------------------------------------------------------------------- 1 | package com.javieraviles.springredis.services; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | import javax.ws.rs.NotFoundException; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.data.redis.core.RedisTemplate; 10 | import org.springframework.data.redis.core.ValueOperations; 11 | import org.springframework.stereotype.Service; 12 | 13 | import com.javieraviles.springredis.entities.User; 14 | 15 | @Service 16 | public class UserService { 17 | 18 | @Autowired 19 | private RedisTemplate userTemplate; 20 | 21 | private static final String REDIS_PREFIX_USERS = "users"; 22 | 23 | private static final String REDIS_KEYS_SEPARATOR = ":"; 24 | 25 | public List findByPattern(final String pattern) { 26 | return getValueOperations().multiGet(userTemplate.keys(getRedisKey(pattern))); 27 | } 28 | 29 | public User findById(final String userId) { 30 | final User user = getValueOperations().get(getRedisKey(UUID.fromString(userId).toString())); 31 | if(user == null) { 32 | throw new NotFoundException("User does not exist in the DB"); 33 | } 34 | return user; 35 | } 36 | 37 | public void save(final User user) { 38 | user.setId(UUID.randomUUID().toString()); 39 | getValueOperations().set(getRedisKey(user.getId()), user); 40 | } 41 | 42 | public void update(final User user) { 43 | findById(user.getId()); 44 | getValueOperations().set(getRedisKey(user.getId()), user); 45 | } 46 | 47 | public void delete(final String userId) { 48 | if(!userTemplate.delete(getRedisKey(UUID.fromString(userId).toString()))) { 49 | throw new NotFoundException("User does not exist in the DB"); 50 | } 51 | } 52 | 53 | private String getRedisKey(final String userId) { 54 | return REDIS_PREFIX_USERS + REDIS_KEYS_SEPARATOR + userId; 55 | } 56 | 57 | private ValueOperations getValueOperations() { 58 | return userTemplate.opsForValue(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | redis.hostname=192.168.99.100 2 | redis.port=6379 -------------------------------------------------------------------------------- /src/test/java/com/javieraviles/springredis/SpringRedisApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.javieraviles.springredis; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class SpringRedisApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------