How to Mock the Ambassador Pattern Retry Client in Python
This code demonstrates the ambassador pattern for API clients by simulating a flaky request and retrying with exponential backoff, useful for testing resilience in system design.
Python code
38 linesimport time
import random
class RetryingClient:
"""Retry wrapper simulating a flaky ambassador-style API client."""
def __init__(self, max_attempts=3, base_delay=0.1):
self.max_attempts = max_attempts
self.base_delay = base_delay
self.attempts = 0
def _flaky_request(self):
"""Simulate a request that fails ~50% of the time."""
self.attempts += 1
if random.random() < 0.5:
raise ConnectionError("Temporary network failure")
return {"status": "ok", "attempt": self.attempts}
def call(self):
"""Retry the flaky request with exponential backoff."""
last_error = None
for attempt in range(self.max_attempts):
try:
return self._flaky_request()
except ConnectionError as exc:
last_error = exc
time.sleep(self.base_delay * (2 ** attempt))
raise last_error
if __name__ == "__main__":
client = RetryingClient(max_attempts=4)
try:
result = client.call()
print(f"Success on attempt {result['attempt']}: {result}")
except ConnectionError:
print(f"Failed after {client.attempts} attempts")
Output
Success on attempt 2: {'status': 'ok', 'attempt': 2}
or
Failed after 4 attempts
(Output depends on randomness but typically shows either a success with an attempt number 1-4 or failure after 4 attempts)
How it works
The RetryingClient wraps a flaky API call in a retry loop with exponential backoff, mimicking the ambassador pattern where a sidecar handles retries. _flaky_request simulates failure by raising ConnectionError about 50% of the time. The call method catches that error, sleeps with a delay that doubles each attempt, and repeats until max attempts. This isolates retry logic from the calling code, making it testable as a mock for production resilience.
Common mistakes
- Not resetting the attempt counter between calls, causing inaccurate reporting
- Skipping sleep in tests to speed up execution, but then missing backoff logic verification
- Assuming the retry always succeeds, but it can fail if all attempts hit the error
Variations
- Use `tenacity` library with `@retry` decorator instead of custom loop
- Implement with `itertools.count` and `try/except` with logging for each attempt
Real-world use cases
- Simulating a flaky third-party payment API in unit tests to verify retry behavior without real network calls.
- Mocking an ambassador sidecar that retries requests to a backend service during load testing.
- Injecting a retry wrapper into a service client to handle transient network errors in production microservice calls.
Sponsored
More from System design patterns
- Build a BFF (Backend for Frontend) Mock Aggregator in Python medium
- Builder pattern for mocking complex objects in Python easy
- Circuit Breaker Pattern in Python: Closed, Open, and Half-Open States medium
- Create a Data Helper Class in Python easy
- Domain Driven Design Aggregate Root Example in Python medium
- Facade Pattern in Python with Mock Simplification medium
Keep learning
Related tutorials and quizzes for this topic.