Card 07/ 09
A Self-Call Inside the Real Object Skips the Proxy
A method on the real object calls another of its own methods, and the proxy never sees the second call.
A proxy controls only the calls that arrive through it, and a call the real object makes to one of its own methods, a self-call, goes straight to the object. This card asks you to predict which calls a logging proxy sees, then shows the path the self-call takes, and the framework where it causes the most trouble.
ReportService.publish calls self.validate. A logging proxy wraps both methods, and the client calls publish through the proxy.
class ReportService:
def publish(self, report):
self.validate(report)
return f"published {report}"Does the proxy log the call to validate? Decide, then look.
class ReportService:
def publish(self, report: str) -> str:
self.validate(report)
return f"published {report}"
def validate(self, report: str) -> None:
print(f"service: validating {report}")
class LoggingProxy:
def __init__(self, service: ReportService) -> None:
self._service = service
def publish(self, report: str) -> str:
print("proxy: publish called")
return self._service.publish(report)
def validate(self, report: str) -> None:
print("proxy: validate called")
self._service.validate(report)
reports = LoggingProxy(ReportService())
print(reports.publish("Q3"))proxy: publish called
service: validating Q3
published Q3Only publish is logged. The proxy forwards publish to the real service, and inside it self is the real service, not the proxy, so self.validate calls the service's own method. The proxy's validate exists and is never used.
The arrow from ReportService back to itself is the self-call, and no arrow touches the proxy. Spring's @Transactional is the case most Java developers meet. Spring wraps a bean in a proxy that opens a transaction, so when a method of the bean calls another method marked @Transactional on itself, the call skips the proxy and no transaction starts. The code compiles, the annotation is there, and the behaviour is missing.
Three fixes exist. Move the second method to another object, so the call goes through that object's proxy. Have the object call through a reference to its own proxy. Or put the control in the object, if every call must have it. The first is usually the clearest.
← → move through sections and cards