Stateless versus Stateful scalability

We can write our services either in a stateful or stateless manner. I am referring to session state here. For example, at times we need to make sure that the user is logged in before we let them access a resource or perform an operation. Or we might want to maintain the previous state of operations; for example, you can submit form 2 only if you have submitted form 1.

To understand statelessness, let me show you a very vanilla service example. The following code shows a service to add up, two numbers:

package com.calculate.sum;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SumController {
@GetMapping("/sum")
public Integer addNumbers(@RequestParam Integer num1, @RequestParam Integer num2) {
return num1+num2;
}
}

This is a simple Spring REST service that takes two parameters, num1 and num2, and returns the sum. This is a completely stateless service, which is easy to scale. It is stateless because this service does not care about who is requesting, what call was made before and after this service was called, and so on. All it does is take an input and return the output.

Why do we say it is easy to scale? Well, I can add dozens of instances of the service behind a load balancer URL, it doesn't matter which instance serves the request as the result will always be same.

But, let's say we have a condition that only logged in users, that is, authenticated users, should be able to access this sum service. This adds complexity as now we somehow need to check that the user is indeed logged in before serving the sum. Traditionally, if we are dealing with a simple application getting deployed on a single server, an easy way out is the use of sessions:

package com.serviceone.serviceone;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SumSessionController {

@GetMapping("/sum2")
public Integer addNumbers(HttpServletRequest req, @RequestParam Integer num1, @RequestParam Integer num2) {
HttpSession sess = req.getSession(false);
if(sess!=null) {
Boolean authVal = (Boolean) sess.getAttribute("isAuthenticated");
if(authVal)
return num1+num2;
}
return -9999;
}
}

In the preceding code, we are making sure only an authenticated user is able to use the service. Sessions are maintained at the server level. The problem with this implementation is that it is hard to scale. For example, say we added multiple instances of the application on different servers. Now the initial login request, where we are setting the session information after a successful login, goes to Server 1. And after that, a subsequent request goes to Server 2, which does not have any details on user login (as that information is with Server 1), so it will assume the user is not authenticated and throw an error:

The problem with this implementation is that we need to make sure that all the requests from the same user are received by the same server instance.

There are solutions, such as sticky sessions, that would make sure all the requests coming from one IP address go to the same server, which would solve our problem. But this is not a perfect scenario as we are adding the limitation that all the requests from one user need to go to the same server, so we will not be able to make sure we are evenly distributing the load to our servers.

To solve this problem, we can use token-based authentication. Let's take an example of an implementation that is based on JWT to make it clear:

package com.statemanager;

import java.util.Calendar;
import java.util.Date;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

public class JWTUtil {
// You will use a application secret stored in properties file or database. s
String secret = "KeepSecretKeySameAccrossServers";

/**
* This method takes a user object and returns a token.
* @param user
* @return
*/
public String createAccessJwtToken(User user) {
Date date = new Date();

Calendar c = Calendar.getInstance();
c.setTime(date);
c.add(Calendar.DATE, 1);
// Setting expiration for 1 day
Date expiration = c.getTime();
JWTCreator.Builder builder = com.auth0.jwt.JWT.create();
builder.withSubject(user.getName())
.withKeyId(user.getId())
.withIssuedAt(date)
.withExpiresAt(expiration)
.withClaim("canAccessSum", true);
String token = builder.sign(Algorithm.HMAC256(secret));
return token;
}

/**
* This method takes a token and returns User Object.
* @param token
* @return
*/
public User parseJwtToken(String token) {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build();

DecodedJWT jwt =verifier.verify(token);

Boolean sumAccess = jwt.getClaim("canAccessSum").asBoolean();

User user = new User();
user.setId(jwt.getId());
user.setName(jwt.getSubject());
user.setSumAccess(sumAccess);
return user;
}
}

The preceding code helps us generate a token that can keep all the mandatory information required to serve the user. We can pull the required information at runtime from the token, so there is no need to maintain internal state and store user data on the server as part of session information.

The following code showcases the use of a JWT token:

package com.statemanager;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SumSessionController {

@GetMapping("/sum2")
public Integer sayHello(@RequestParam String token, @RequestParam Integer num1, @RequestParam Integer num2) {
User user = new JWTUtil().parseJwtToken(token);
if(user.getSumAccess()) {
return num1+num2;
}
return -9999;
}
}

This helps us keep services stateless and hence independently scalable:

We can see how tokens can help us keep our services session free, so there is no need to store information in server-side sessions. At the same time, we make sure that we can pass state information along with each request. We will look at JWT again in Chapter 7Securing Microservices, where we will focus on the security aspect of tokens.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.144.216