Proxy Pattern

The Proxy Pattern is a Structural Design Pattern that provides a surrogate or placeholder for another object, controlling access to it. In essence, a proxy object represents another object and is used to add an extra level of control or functionality before the real object is accessed. By acting as an intermediary, a proxy can handle additional operations such as access control, lazy initialization, logging, or caching.

The Proxy Pattern is especially useful when direct access to an object may be costly, unsecure, or require additional actions. For example, proxies are often used to manage remote resources, enforce access permissions, or control resource-intensive operations.

Key Characteristics of Proxy Pattern

  1. Acts as an Intermediary:
    • The proxy object represents another object and controls access to it, allowing you to interact with the real object through the proxy.
  2. Provides Additional Functionality:
    • The Proxy Pattern can add extra behavior to the original object, such as logging, caching, or checking permissions, without modifying the object itself.
  3. Types of Proxies:
    • Remote Proxy: Represents an object in a different location, such as a different server or machine.
    • Virtual Proxy: Delays the creation or loading of an object until it’s actually needed, optimizing resource usage.
    • Protection Proxy: Controls access based on permissions or access rights.
    • Caching Proxy: Caches results to avoid repeated resource-intensive operations.
  4. Encapsulates Complex Logic:
    • By adding a proxy, you can encapsulate and hide complex logic from the client, simplifying the client code.
  5. Transparent to Client:
    • The client interacts with the proxy just as it would with the real object, making it easier to add or remove proxies without changing the client code.
  6. Supports Lazy Initialization:
    • Virtual proxies enable lazy initialization, where the real object is only created or loaded when required, helping to save memory and processing time.

Proxy Pattern in Node.js

In Node.js, the Proxy Pattern is commonly used to control access to network resources, manage caching, or perform validations. Let’s illustrate this pattern with an example of an image loader that delays loading large image files until they are actually needed.

Example Scenario: Image Loader with Virtual Proxy

In this example, we’ll create an Image class that represents a large image file. We’ll then create a ProxyImage class that delays the actual loading of the image until it’s requested.

Step 1: Define the Real Subject (Image)

The Image class represents the real subject and includes a method to load the image from the file system.

// Real subject: Image
class Image {
  constructor(filename) {
    this.filename = filename;
    this.loadImage();
  }

  loadImage() {
    console.log(`Loading image from ${this.filename}`);
    // Simulate a time-consuming loading process
  }

  display() {
    console.log(`Displaying ${this.filename}`);
  }
}

Step 2: Create the Proxy (ProxyImage)

The ProxyImage class controls access to the Image class by delaying the loading of the image until the display method is called. This is a Virtual Proxy since it delays loading.

// Proxy: ProxyImage
class ProxyImage {
  constructor(filename) {
    this.filename = filename;
    this.realImage = null; // The real image is initially not loaded
  }

  display() {
    if (!this.realImage) {
      this.realImage = new Image(this.filename); // Load the image only when needed
    }
    this.realImage.display();
  }
}

Step 3: Using the Proxy Pattern

Now, we can use the ProxyImage class to control access to the Image class. The image will only load when it’s actually displayed, saving resources until then.

// Using ProxyImage
const image1 = new ProxyImage('photo1.jpg');
const image2 = new ProxyImage('photo2.jpg');

// The images are only loaded when display() is called
image1.display(); // Loading image from photo1.jpg, Displaying photo1.jpg
image2.display(); // Loading image from photo2.jpg, Displaying photo2.jpg

Output:

Loading image from photo1.jpg
Displaying photo1.jpg
Loading image from photo2.jpg
Displaying photo2.jpg

Explanation:

  • ProxyImage acts as a placeholder for Image, delaying the loading of the image until display is called.
  • This approach optimizes resource usage by only loading images when they are actually needed.

Real-World Examples of Proxy Pattern

Database Query Caching:

class DatabaseProxy {
  constructor(database) {
    this.database = database;
    this.cache = {};
  }

  query(sql) {
    if (this.cache[sql]) {
      console.log("Returning cached result");
      return this.cache[sql];
    } else {
      console.log("Executing query and caching result");
      const result = this.database.query(sql);
      this.cache[sql] = result;
      return result;
    }
  }
}

Access Control for APIs:

class AuthProxy {
  constructor(api, user) {
    this.api = api;
    this.user = user;
  }

  request(endpoint) {
    if (!this.user.isAuthenticated) {
      console.log("Access denied: User not authenticated");
      return;
    }
    console.log("Access granted: User authenticated");
    return this.api.request(endpoint);
  }
}

Network Proxies and Firewalls:

In networking, proxies act as intermediaries that control access to network resources. For example, firewalls use proxies to filter incoming and outgoing traffic, protecting systems from malicious requests.

Lazy Loading of Large Objects:

In systems where creating large objects is resource-intensive, virtual proxies can delay object creation until it’s actually needed. For example, an application may use virtual proxies to load large datasets only when the user explicitly requests them.

Payment Gateways:

In e-commerce applications, proxies are used to interact with third-party payment systems. The proxy handles API calls, manages response handling, and simplifies payment processing for the main application.

Remote Proxies for Microservices:

In a microservices architecture, a proxy can represent remote services and manage communication. For instance, when a service calls another service in a different location, the proxy acts as an intermediary, ensuring that requests are efficiently routed and retry logic is applied as needed.

Security and Logging Proxies:

Security proxies can intercept calls to critical functions, checking if the user has the necessary permissions. Logging proxies intercept calls to add logging functionality, recording actions or interactions with specific functions for auditing.

Conclusion

The Proxy Pattern is an essential design pattern that provides a flexible way to control access to objects. By adding a proxy as an intermediary, developers can add features like caching, access control, lazy loading, and logging without modifying the underlying object. This makes the Proxy Pattern especially valuable in performance-sensitive or security-critical applications.

In Node.js, the Proxy Pattern is frequently used to manage network resources, control access to APIs, and optimize memory usage. By leveraging the Proxy Pattern, developers can simplify client code, improve security, and optimize resource management for large applications, making it a vital tool in modern software design.

Leave a comment

I’m Tran Minh

Hi, I’m Trần Minh, a Solution Architect passionate about crafting innovative and efficient solutions that make technology work seamlessly for you. Whether you’re here to explore the latest in tech or just to get inspired, I hope you find something that sparks joy and curiosity. Let’s embark on this exciting journey together!

Let’s connect