DEV Community

Cover image for Design Patterns Serie — Structural Chapter: Proxy Pattern
bryam vega
bryam vega

Posted on

Design Patterns Serie — Structural Chapter: Proxy Pattern

Introduction

Design patterns are solutions to common problems encountered in software development. The objective of these design patterns could be equated with the phrase “not reinventing the wheel” because its purpose is that, to provide solutions to problems that usually appear very frequently.

However, design patterns are not an absolute solution, I could say that it is a guide on how to solve a problem equal or similar to the moment the pattern was created, from then on, it is up to the developer to adapt the pattern to the business needs.

Design patterns are now divided into three sections: Creative, Structural and Behavioral. Each of them with its defined characteristics and structure.

In this article, we are going to learn about one of the most popular software design patterns within the structural patterns section and that is the Proxy Pattern.

What is Proxy Pattern?

To understand what the proxy pattern is, it is first important to understand the problem behind this pattern.

Imagine that as developers, we are told that in order to consume some services to obtain some User data, we must first consume a service to obtain a JWT (Json Web Token).

Problem

Currently we do not know the use of the proxy pattern, so we imagine that every time we need to consume the service to get the user’s data we will have to call the service to get the JWT that returns a JWT with a expiration time. With this we are doing the following scenario.

Proxy fail

Our QA tells us that the service to get the customer data is taking longer than usual. So, we analyze our service and we see that the service that calls the user data is working correctly, however, the service that provides the JWT is taking too long because we are requesting a token every time we call the service. So the concept of token expiration is not working.

proxy good

Therefore, they are asking you to fix this problem and you don’t know what to do.

Solution

Luckily, you found this article that talks about how to fix this problem using the proxy pattern

diagram

Reviewing in more detail, the proxy pattern consists of creating a control layer called Proxy whose main function is to act as an intermediary between the client and the real object to be accessed, managing or regulating this access in various ways.

This pattern is commonly used to control the creation of resource-intensive objects, protect them from unauthorized access, or add additional functionality during interaction with the actual objects.

With this idea, the initial problem using a proxy pattern looks as follows:

diagram

Now that we see it through a higher level diagram, we see how this proxy pattern works through a class diagram to see how to implement it.

diagram

As we can see in the class diagram, the proxy pattern is composed of the following classes:

  • Subject (JWTService): This is the common interface that both the proxy and the real subject will implement. It defines the operations that the client can perform. In this case we can see that request JWT will be the common method that both classes will implement.
  • Real Subject (JWTServiceImpl): This is the class that implements the JWTService interface and provides the real functionality of the service. This object performs the real operations related to JWT tokens, such as generating a token.
  • Proxy (JWTServiceProxy): This is the object that controls access to the real object (JWTServiceImpl). The proxy can perform additional tasks such as security handling, logging, or cache handling before or after invoking calls to the real object. In this case it handles the lifecycle of the JWT class that returns the JWTServiceImpl . If we notice JWT class contains a method called validateJWT that allows to validate that our JWT is still functional and not to call it every time.

I know that the class diagram may seem a bit weird and confusing to understand, but believe me, when we put this into code, everything will be more understandable!

Implement Proxy Pattern for JWT generation

The code of this implementation is hosted in this repository in case you want to clone it and try it yourself: Proxy Pattern Repo

Based on our created class diagram, we are going to implement it in code and this way you will see that you will understand the proxy pattern in a faster way.

The JWT object

A JWT class that contains the data necessary for a Json Web Token to work. It is presented as follows.

public record JWT(String token, long expirationTime, long creationTime) {
    public boolean validateJWT(){
        long currentTimeInSeconds = System.currentTimeMillis() / 1000;
        return currentTimeInSeconds < this.creationTime() + this.expirationTime();
    }
}
Enter fullscreen mode Exit fullscreen mode

Since Java 16, the records known as records are born, which allow the attributes that contain a class to be immutable by default, keeping the same characteristics of a normal class, such as creating methods such as validateJWT(). This method will be important because it will be used by our proxy method to validate the token lifecycle.

The subject

The subject is represented by the JWTService interface. This interface defines a single requestJWT() method that, when implemented, should be responsible for requesting and returning a JWT (JSON Web Token) object.

public interface JWTService {

    JWT requestJWT();
}
Enter fullscreen mode Exit fullscreen mode

This interface will be implemented by the Real Subject and the proxy, since both require the common operation requestJWT().

The Real Subject

The JWTServiceImpl class serves as the RealSubject within the Proxy pattern, where its primary responsibility is the generation of JSON Web Tokens (JWT).

public class JWTServiceImpl implements JWTService {
    @Override
    public JWT requestJWT() {
        return new JWT(generateToken(), 5,
                System.currentTimeMillis() / 1000);
    }

    private String generateToken(){
        String header = UUID.randomUUID().toString();

        String payload = UUID.randomUUID().toString();

        String signature = UUID.randomUUID().toString();

        return header + "." + payload +
                "." + signature;
    }
}
Enter fullscreen mode Exit fullscreen mode

For this case, we simulate the creation of a JWT using the generateToken() method, by generating UUID for the header, payload and signature, just as an example, in real life, this must be obtained through a security service or a platform such as keycloak or auth0.

Additionally let’s take into account that our JWT will have a lifetime of 5 seconds and its creation time will be the current time in which we execute the service in seconds, as you can see in the requestJWT() method.

Finally we can notice that our RealSubject is only in charge of generating the JWT, it does absolutely nothing else, so at the beginning,** we did not realize that we had to validate the lifetime of the jwt*, so our **proxy pattern* will come in to solve our life.

The proxy

The proxy, in this case JWTServiceProxy, serves as an intermediary between the client and the actual service (JWTServiceImpl). Its role is to manage the JWT token request flow, implementing a caching strategy that improves operational efficiency and reduces the load on system resources.

  • Initialization: The proxy class initializes its concrete dependency on JWTServiceImpl within its constructor (composition). This ensures all token requests are routed through the proxy before reaching the actual service.
  • Token Caching: It maintains a cached instance of the JWT. Before processing a new request, it checks whether it already possesses a valid token. If the stored token is invalid or has expired, it proceeds to request a new token from the real service, JWTServiceImpl.
public class JWTServiceProxy implements JWTService {

    private final JWTService jwtService;
    private JWT jwt;

    public JWTServiceProxy() {
        this.jwtService = new JWTServiceImpl();
    }

    @Override
    public JWT requestJWT() {
        if (jwt == null || !jwt.validateJWT()) {
            jwt = jwtService.requestJWT();
        }
        return jwt;
    }
}
Enter fullscreen mode Exit fullscreen mode

The requestJWT() method encapsulates the logic for validating and issuing tokens:

  • Token Validation: Checks if the current cached token is valid using jwt.validateJWT(). This step is crucial to ensure that the tokens are not only present but also secure and reliable.
  • Conditional Token Generation: If the token is null or invalid, the proxy requests a new token, which is done by calling the real service. This mechanism ensures that the system does not generate tokens unnecessarily, optimizing resource usage.

Test Proxy Pattern

Finally, since we have implemented our proxy pattern, the last thing we have left is to validate to see if this pattern really works. For it we instantiate the Subject initializing it with the proxy and not with the RealSubject.

public class Main {

    private static final Logger log = Logger.getLogger(Main.class.getName());

    public static void main(String[] args) throws InterruptedException {
        JWTService proxy = new JWTServiceProxy();

        JWT jwt = proxy.requestJWT();
        log.info("TOKEN: " + jwt.token());

        jwt = proxy.requestJWT();
        log.info("TOKEN TWO: " + jwt.token());

        Thread.sleep(5000);

        jwt = proxy.requestJWT();
        log.info("TOKEN THREE: " + jwt.token());

    }
}
Enter fullscreen mode Exit fullscreen mode

If we notice, we can notice that we make 3 requests to get the JWT, however, before the last request we use Thread.sleep(5000) which guarantees that we are going to wait for the thread to sleep for about 5 seconds, which is the time configured in our JWT mock to expire.

Nov 27, 2024 9:16:21 PM org.bvega.Main main
INFO: TOKEN: 9b348add-fcca-4187-b7cd-f36e07e97e97.63a81aa0-6c75-4b7e-8178-6488c61a5181.5ae3bea6-9ba9-4727-b183-875baf8de9a8
Nov 27, 2024 9:16:21 PM org.bvega.Main main
INFO: TOKEN TWO: 9b348add-fcca-4187-b7cd-f36e07e97e97.63a81aa0-6c75-4b7e-8178-6488c61a5181.5ae3bea6-9ba9-4727-b183-875baf8de9a8
Nov 27, 2024 9:16:26 PM org.bvega.Main main
INFO: TOKEN THREE: f06eea26-15c7-408b-9a73-1804121cca1f.2f3360be-5cb5-4ac1-be32-d3af242cd998.19e68e66-6f03-493e-8b42-8542347cace7
Enter fullscreen mode Exit fullscreen mode

And what a surprise!!! 😮, as you can see, the first 2 tokens are the same, but the third one is a new one since the life time of the initial token expired, this validates that our proxy pattern worked correctly, congratulations !!!! 😄

exito

Conclusion

The Proxy pattern is a powerful tool in the software developer’s arsenal, offering significant advantages in terms of performance enhancement and security. However, its implementation should be approached with a clear understanding of the system’s architectural needs and operational challenges. Properly utilized, the Proxy pattern can lead to more maintainable, secure, and efficient software systems, making it a worthwhile consideration for projects requiring controlled access and optimized resource management.

Happy coding!! ❤️

Top comments (0)