How to use a Proxy Design Pattern Explained

In this article, we discuss in detail about the Proxy design pattern, its use cases and implementing a simple Proxy with an ASP.NET Core example.

So far we have been looking at various design patterns and the ways in which they’re helpful in maintaining and solving design problems.

A design pattern is a design solution created to solve a specific problem of software design which can be applied to solve whenever such problems occur.

In this article, Let’s talk about yet another design pattern, which helps in maintaining abstraction: the proxy pattern.

Proxy pattern is one of the 23 Gang of Four (GOF) design patterns and is a Structural Pattern. Others are Bridge, Composite, Decorator, Facade, Flyweight and Adapter design patterns.

What is a Proxy?

A proxy can be termed as a substitute or a placeholder for another component; which cannot be otherwise accessed directly. Such a proxy is generally employed for validating a particular caller context, and to decide whether to actually allow the call to access the real component or deny access.

And the client which is the starting point of execution doesn’t actually know that the component to which it is passing execution flow is not the real component but a mere substitute for the actual component.

One general scenario we look at everyday is any identity access card, which resembles a person for whom the card is created for. And when this identity card is invoked for a purpose, it can be termed as that the person has invoked for that purpose.

Features of a Proxy

  1. The prime objective of a proxy is to ensure access control and security
  2. A proxy provides a substitute or a placeholder for another component or object
  3. The proxy thus can control access to the original component or object and can possess additional validation functionality before or after the component is called.
  4. These are also known as wrappers or surrogates since they encapsulate the actual functionality and provide an additional layer of abstraction.

Contrasting Proxies, Decorators and Adapters

When we look at the design of proxies and their features, we may get a question in our minds – “Aren’t these the same things that a decorator does?”. And yes a proxy does the same thing as a decorator does: wrapping up the actual component and adding a layer of abstraction.

But when you look at it, they’re quite different in their purpose and in the way they’re designed.

A decorator is a component wrapping over another component for the purpose of extending an existing component without having to modify the existing component. This might otherwise create issues in the component’s communication with other objects, when we modify the existing component for a new functionality.

This also violates the Open/Closed principle which states that the components must not be modified for newer changes, but instead should be extended. And a decorator does the same thing; it extends the functionality of an existing component.

Whereas in a Proxy, you don’t add any additional features or functionalities for an existing component but rather add another layer of abstraction on top of it, so that we could have a sort of control over it. While both might seem similar, they serve different purposes.

And comparing with the Adapter pattern would be even more different, because an Adapter pattern takes in a component of one type and wraps it onto another type. In this case the context would completely change.

Adapters are used for bridging compatibility between two components which are otherwise incompatible. This involves two entities, whereas a proxy would have only one entity for itself to bind to.

Example – How to design a Proxy in ASP.NET Core

For example, let’s take the example of a ReaderStore which has a method to fetch all the readers out of the store. A simple function GetReaders() would possess the necessary logic for the functionality to happen.

namespace ReadersApi.Providers 
{

	public interface IReaderStore
	{
		List<Reader> GetReaders(int passId);
	}
		
	public class ReaderStore : IReaderStore
	{
		public List<Reader> GetReaders(int passId)
		{
			// return readers who satisfy the passId
			return readers;
		}
	}
}

Now the client can invoke the ReaderStore.GetReaders() functionality by creating an object of the same and then calling on the function.

namespace ReadersApi.Providers
{
	public class Client
	{
		private IReaderStore store;
		
		public Client(IReaderStore store)
		{
			this.store = store;
		}

		public void StartingPoint()
		{
			int passId = 10;
			var readers = this.store.GetReaders(passId);
		}
	}
}

Now this is a normal scenario. Let’s assume we are interested in keeping a few reader passIds away from being read by external clients.

This would mean modifying the existing ReaderStore.GetReaders() functionality which is against the Open/Closed principle. For this, we create a Proxy stub for the ReaderStore class that internally invokes the same.

namespace ReadersApi.Providers
{
	public class ProxyReaderStore : IReaderStore
	{
		private IReaderStore store;
		private IConfiguration config;
		
		public ProxyReaderStore(IReaderStore store, IConfiguration config)
		{
			this.store = store;
			this.config = config;
		}
		
		public List<Reader> GetReaders(int passId)
		{
			var invalidPassIds = config["invalidPassIds"].ToString().Split(',');
			
			if(invalidPassIds.Contains(passId.ToString()))
			{
				return this.store.GetReaders(passId);
			}
			
			return new List<Reader>();
		}
	}	
}	

And the since the Client works on an abstraction IReaderStore, we can safely pass the stub implementation ProxyReaderStore which impersonates the actual ReaderStore component and adds a layer of control on what needs to be accessed and what doesn’t need to be.

Also, we can observe that this approach satisfies the Open/Closed principle and the Liskov Substitution SOLID principles.

And these services are registered under the ConfigureServices() method of Startup.cs as below:

services.AddScoped<ReaderStore>();
services.AddScoped<IReaderStore>(
	provider => ActivatorUtilities.CreateInstance(
			provider, 
			typeof(ProxyReaderStore), 
			provider.GetRequiredService<ReaderStore>(),
			configuration
	)
);

Buy Me A Coffee

Found this article helpful? Please consider supporting!

In this way, we can have a stub component impersonate an existing component for added layer of security and access control, which we call as a proxy.


Buy Me A Coffee

Found this article helpful? Please consider supporting!

Ram
Ram

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity. You can connect with me on Medium, Twitter or LinkedIn.

Leave a Reply

Your email address will not be published. Required fields are marked *