DEV Community

Cover image for Proxy Design Pattern with Caching Example in Java
William
William

Posted on

Proxy Design Pattern with Caching Example in Java

Imagine an application that repeatedly queries a database for user data, causing performance bottlenecks due to slow database operations. How can we optimize this without modifying the core logic? The Proxy Design Pattern offers a solution by introducing an intermediary that controls access to the real object, enabling optimizations like caching.

Solution: Proxy Pattern with Caching

The Proxy Pattern introduces a Proxy class that sits between the client and the real service, adding caching to reduce database calls. We'll implement this in pure Java for simplicity, demonstrating a caching proxy for user data.

Interface: UserService
public interface UserService {
    String getUserData(String userId);
}

Real Service: RealUserService
public class RealUserService implements UserService {
    @Override
    public String getUserData(String userId) {
        // Simulate a slow database query
        try {
            Thread.sleep(2000); // 2-second delay
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Data for user: " + userId;
    }
}

//Proxy Service: UserServiceProxy
import java.util.HashMap;
import java.util.Map;

public class UserServiceProxy implements UserService {
    private final UserService realUserService;
    private final Map<String, String> cache = new HashMap<>();

    public UserServiceProxy(UserService realUserService) {
        this.realUserService = realUserService;
    }

    @Override
    public String getUserData(String userId) {
        // Check cache first
        if (cache.containsKey(userId)) {
            System.out.println("Retrieved from cache: " + userId);
            return cache.get(userId);
        }
        // Fetch from real service and cache the result
        String data = realUserService.getUserData(userId);
        cache.put(userId, data);
        System.out.println("Fetched from database and cached: " + userId);
        return data;
    }
}

//Main Application
public class ProxyPatternDemo {
    public static void main(String[] args) {
        UserService realUserService = new RealUserService();
        UserService proxy = new UserServiceProxy(realUserService);

        // Test the proxy
        System.out.println(proxy.getUserData("user1")); // Hits database
        System.out.println(proxy.getUserData("user1")); // Hits cache
        System.out.println(proxy.getUserData("user2")); // Hits database
        System.out.println(proxy.getUserData("user2")); // Hits cache
    }
}

Enter fullscreen mode Exit fullscreen mode

How It Works

Interface: UserService defines the contract for both the real service and the proxy.
Real Service: RealUserService simulates a slow database query with a 2-second delay.
Proxy: UserServiceProxy intercepts calls, checks a HashMap cache, and only calls RealUserService if the data isn't cached.
Main Class: Demonstrates the proxy in action, showing how repeated requests leverage the cache.

When you run, the first call to getUserData("user1") hits the database (slow), but subsequent calls for the same userId retrieve data from the cache (fast).

Benefits

  • Performance: Caching reduces database calls, improving response times.
  • Transparency: The client uses the same interface, unaware of the proxy.
  • Extensibility: The proxy can be extended for additional features like logging or access control.

Conclusion

The Proxy Pattern elegantly solves the problem of inefficient database access by introducing a caching layer without altering the core service. In this Java example, we used a simple HashMap for caching, significantly improving performance for repeated requests.
In a real-world project, caching approaches would be more robust like Redis.

Additionally, the Proxy Pattern is versatile beyond caching. It can be used for logging, capturing method calls, parameters, and execution times for debugging or auditing. For example, you could extend UserServiceProxy to log each request to a file or monitoring system, enhancing observability without changing the core service logic. This makes the Proxy Pattern a powerful tool for optimizing and extending system behavior transparently.

Top comments (0)