Proxy
Fetching a report from a slow service takes two seconds, and the same report is requested again and again.
Create a stand-in with the same interface as the real service.
Caching an expensive call (caching proxy)
The picture from life
A manager's personal assistant. You do not walk straight into the manager's room. The assistant checks who you are, keeps a note of answers she has already given, and only disturbs the manager when really needed. To you the assistant looks like the manager's voice: same questions, same answers.
The problem
What goes wrong when you do not use it.
Fetching a report from a slow service takes two seconds, and the same report is requested again and again. Every caller pays the full price, and nothing stops a banned user from calling it.
- calls / uses
- Caching an expensive call (caching proxy)
- Delaying creation until first use (virtual proxy)
- Checking permission before the real call (protection proxy)
- Hiding that the object lives on another machine (remote proxy)
The idea
How the pattern fixes it.
Create a stand-in with the same interface as the real service. It holds the real one, decides when to forward a call and when to answer itself, and the caller cannot tell the difference.
- implements
- has a / holds
- calls / uses
Who is who
- Service (interface)
- fetch(key).
- SlowService
- The real subject, expensive to call.
- CachingProxy
- Same interface; answers from a dictionary or forwards.
Build it in steps
- 1Extract the interface of the real service.
- 2Write a proxy implementing it and holding the real one.
- 3Add the extra control (cache, check, lazy load).
- 4Give callers the proxy instead.
Watch it run
Step through the calls. Tap an arrow to jump to it.
The client calls what looks like the normal service.
fetch('sales')The code
Python and Java. Try writing the classes yourself from the diagram first.
class SlowService:
def __init__(self):
self.real_calls = 0
def fetch(self, key):
self.real_calls += 1 # imagine this takes two seconds
return "report:" + key
class CachingProxy: # same interface as SlowService
def __init__(self, service):
self.service = service
self.cache = {}
def fetch(self, key):
if key not in self.cache:
self.cache[key] = self.service.fetch(key)
return self.cache[key]
def demo():
real = SlowService()
proxy = CachingProxy(real)
answers = [proxy.fetch("sales"), proxy.fetch("sales"), proxy.fetch("cost")]
return answers, real.real_callsWhat you gain
- Caching, security or lazy loading without touching the real class
- Callers do not change
What it costs
- Another class and a possible delay
- A caching proxy can serve stale data
You have already met it in
- Hibernate lazy-loaded entities
- Reverse proxies and API gateways in front of services
- Python's functools.lru_cache around a function
Often mixed up with
Make it stick
Write an ImageProxy that loads the heavy image file only the first time display() is called. Print when the real load happens.