Once upon an app…
Nowadays, systems data flow must be efficient and needs to do more with simplicity. Common ways of building modern apps are missing that point, oriented to dependent layers and cohesion between components. In this article, we'll discover an app with those failures in its data flow and split it into smaller pieces to resolve using the advantages of Event-Driven Architecture by the magic of Redis-ing it with Redis.
Supposing the scenario of making a login module, to filter access to our main application pointing to three objectives:
- Security authorization 📝
- Session storage📝
Which will be the entities related and their behavior?
How would be the data flow?
- Login
- User send its user/password.
- The security module valid credentials.
- affirmative case
- Authorization granted to home page.
- The session storage create a session related to the user.
- negative case
- Unauthorized.
- affirmative case
- Logout
- User request
- Session deleted
Flow would like:
- Session stored ✅
- Credentials validated ✅
Well, apparently have cover all , this is ready for production!
Oh wait!
I could forget something. A first perspective looks like a normal login flow but there are a few questions to answer:
- Could I integrate concurrent user sessions and limit them?
- What about expired sessions?
I mean, this current solution could work, but what about those scenarios:
- If the expired session is a must, where do I mark the expired date to remove expired sessions? Which method has this responsibility? The app? A database trigger? An elf that lives inside the app working extra hours to check all the sessions and remove it?
- What about the limit of sessions? If the app has restarted, how would it know how many session has every user? Which of them are the older ones?
Fortunately, in this article exist a solution for every of mentioned problems.
Event-Driven Design to put things in order
EAD is a paradigm about decoupling modules and layers pointing to making independence between them. Left behind the request-driven design, now the events will be the core of the structure, making them event dependents.
What would be out core events?
- A session was created.
- A session was expired.
- A session was deleted.
Besides the events, EAD must specify which will be the event consumer, the producer, and the channel, to interact with them.
Let’s identify what will be our flow
As shown above, the app will be informed about session events, being the event consumer and the event producer, being subscribed to the event channel, so:
- A session expired? The app has informed.
- A session created? The app has informed.
- A session deleted? The app has informed.
- How many sessions exist per user? Even if the app was restarted. The app will know it.
Our design resolves our raised problems, but how to bring it to reality?
A Redis-ing perspective
Redis technologies bring a lot of tools to confront data flow problems effectively and simply, from storing a key-value object to providing custom configurations in the related process of it based on its structure, indexing, and lifecycle.
Will use one of them:
-
Redis Pub/Sub
Redis could work as an event channel, being the event source in our design, making use of PUBLISH and SUBSCRIBE commands. Those could be executed through the Redis console as follows:
SUBSCRIBE {channel1} ...{channelN}
The subscribe command executes an active listening to a specific channel, then Redis will inform all channel subscribers when a message has been published.
PUBLISH {channel1} {message}
While the publish command, put a new message in an specific channel.
This tool is the implementation of Publish/Subscribe message paradigm.
Let’s bring the code!
Now, we’ll build a simple app web trough Spring initializr with these dependencies:
- Spring Web To make our project a web application, for this instance, we create a simple home page structure, to be the home app representation, called index.html. It must be stored in the /templates/static folder. The HTML looks like that:
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Home</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
</head>
<body class="d-flex flex-column h-100">
<main class="flex-shrink-0">
<div class="container">
<h1 class="mt-5">This is Home!</h1>
<a href="/logout">Take me out, please.</a>
</div>
</main>
</body>
</html>
-
Spring Security
A security framework you can complex as you need it. It could look cumbersome when you’re starting using it, but with constant practicing and an understanding of the filter logic, You’ll have a great tool to work with spring environments with the pass of the time.
@EnableWebSecurity public class SecurityConfig<S extends Session> { @Autowired private FindByIndexNameSessionRepository<S> sessionRepository; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .and() .sessionManagement() .maximumSessions(3) .sessionRegistry(sessionRegistry()) .expiredUrl("/login"); return http.build(); } @Bean public SpringSessionBackedSessionRegistry<S> sessionRegistry() { return new SpringSessionBackedSessionRegistry<>(sessionRepository); } @Bean public UserDetailsService userDetailsService() { UserDetails user = User.withDefaultPasswordEncoder() .username("user") .password("password") .build(); return new InMemoryUserDetailsManager(user); } }
As shown, I declared a simple class with EnableWebSecurity annotation. Inside it, a block with the specification of allowing any authenticated request; detail a default form login for users.
It configures a custom session management, to integrate the Redis library by session registry, being responsible of count sessions per user and establishing an expired time for each one.
If a user exceeds the session max limit session, the older session will be deleted and the current will be created. When a Redis session has expired, the user will be redirected to the login page.
Finally, a bean to mockup user access data called userDetailsService. -
Spring Data Redis
A library to integrate spring sessions with Redis with the EnableRedisHttpSession annotation which specifies max inactive seconds by session and a custom Redis namespace to store our session values in the Redis database.
The JedisConnectionFactory bean serves as a connector library between Redis and the spring framework.
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 15, redisNamespace = "WATERMELON") public class RedisConfig { @Bean public JedisConnectionFactory connectionFactory() { RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(); configuration.setHostName("yourHost"); configuration.setPassword("yourPassword"); configuration.setUsername("yourUsername"); configuration.setPort(yourPort); return new JedisConnectionFactory(configuration); } }
Our dependencies pom must look like:
... <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.9.0</version> </dependency> </dependencies>
In this instance, we could execute the app combined with a local Redis server, but to approach the Redis cloud environment, we’ll integrate with it.
A reliable cloud environment
First of all, we go to redislab
After the register step and login into the environment you’ll access to a page like:
With the example database created as default with a memory available for testing. After this, click it example-database link
To see general configs database as endpoint , username and password, to configure your JedisClient.
Spring Data Redis has a validation when the app is starting to validate if database configuration is accessible, so if your properties are correct, it wouldn't be errors related.
Now , the original flows looks like this:
There are 2 scenarios I invited you to test:
- Login in a new session , after have 3 existing sessions, to see how the oldest session is deleted and a new one is created.
- Let N seconds of none interaction with your home page application, to see how works Redis expiring sessions.
The code project could be found on watermelon_repo
.
This is all by now, I hope you could enjoy this article as I enjoy writing in. If you have any questions, please let me know.
Best to you!
- Try Redis Cloud for free
- Redis Developer Hub - tools, guides, and tutorials about Redis
- RedisInsight Desktop GUI
This post is in collaboration with Redis.
Top comments (0)