REST Client Architecture
REST Client Wrapper
namespace Vendor\Integration\Api\Client;
interface RestClientInterface
{
public function get(string $endpoint, array $params = []): ResponseInterface;
public function post(string $endpoint, array $data = []): ResponseInterface;
public function put(string $endpoint, array $data = []): ResponseInterface;
public function delete(string $endpoint): ResponseInterface;
}
Implementation
namespace Vendor\Integration\Api\Client;
class RestClient implements RestClientInterface
{
private HttpClientInterface $httpClient;
private AuthInterface $auth;
private RateLimiterInterface $rateLimiter;
private RetryHandler $retryHandler;
private string $baseUrl;
public function __construct(
HttpClientInterface $httpClient,
AuthInterface $auth,
RateLimiterInterface $rateLimiter,
RetryHandler $retryHandler,
string $baseUrl
) {
$this->httpClient = $httpClient;
$this->auth = $auth;
$this->rateLimiter = $rateLimiter;
$this->retryHandler = $retryHandler;
$this->baseUrl = rtrim($baseUrl, '/');
}
public function get(string $endpoint, array $params = []): ResponseInterface
{
return $this->request('GET', $endpoint, $params);
}
public function post(string $endpoint, array $data = []): ResponseInterface
{
return $this->request('POST', $endpoint, $data);
}
private function request(string $method, string $endpoint, array $data = []): ResponseInterface
{
$url = $this->baseUrl . '/' . ltrim($endpoint, '/');
return $this->retryHandler->executeWithRetry(function () use ($method, $url, $data) {
$this->rateLimiter->wait();
$headers = $this->auth->getHeaders();
$headers['Content-Type'] = 'application/json';
return $this->httpClient->request($method, $url, [
'headers' => $headers,
'body' => $data ? json_encode($data) : null,
]);
});
}
}
API Versioning
Version Strategy
namespace Vendor\Integration\Api\Version;
class ApiVersionManager
{
private string $currentVersion = 'v2';
private array $versionMap = [
'v1' => 'https://api.erp.com/v1/',
'v2' => 'https://api.erp.com/v2/',
];
public function getUrl(string $version = null): string
{
$version = $version ?? $this->currentVersion;
return $this->versionMap[$version];
}
public function getDeprecated(): array
{
return array_filter(
$this->versionMap,
fn($v, $k) => $k !== $this->currentVersion,
ARRAY_FILTER_USE_BOTH
);
}
}
Versioned Client
namespace Vendor\Integration\Api\Client;
class VersionedClient
{
private RestClientFactory $clientFactory;
private ApiVersionManager $versionManager;
public function getClient(string $version = null): RestClientInterface
{
$url = $this->versionManager->getUrl($version);
return $this->clientFactory->create($url);
}
}
Response Version Handling
namespace Vendor\Integration\Api\Transform;
class VersionTransformer
{
private array $transformers = [
'v1' => V1Transformer::class,
'v2' => V2Transformer::class,
];
public function transform(string $version, array $response): array
{
$transformer = $this->transformers[$version] ?? null;
if ($transformer) {
return (new $transformer())->transform($response);
}
return $response;
}
}
Authentication Patterns
OAuth2 Authentication
namespace Vendor\Integration\Api\Auth;
class OAuth2Auth implements AuthInterface
{
private RestClientInterface $httpClient;
private string $clientId;
private string $clientSecret;
private string $tokenUrl;
private ?string $accessToken = null;
private ?\DateTime $tokenExpiry = null;
public function getHeaders(): array
{
if ($this->isTokenExpired()) {
$this->refreshToken();
}
return [
'Authorization' => 'Bearer ' . $this->accessToken,
];
}
private function refreshToken(): void
{
$response = $this->httpClient->post($this->tokenUrl, [
'grant_type' => 'client_credentials',
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
]);
$data = json_decode($response->getBody(), true);
$this->accessToken = $data['access_token'];
$this->tokenExpiry = (new \DateTime())->modify("+{$data['expires_in']} seconds");
}
private function isTokenExpired(): bool
{
return $this->tokenExpiry === null || $this->tokenExpiry <= new \DateTime();
}
}
API Key Authentication
namespace Vendor\Integration\Api\Auth;
class ApiKeyAuth implements AuthInterface
{
private string $apiKey;
private string $headerName = 'X-API-Key';
public function getHeaders(): array
{
return [
$this->headerName => $this->apiKey,
];
}
}
Rate Limiting
Rate Limiter
namespace Vendor\Integration\Api\RateLimit;
class RateLimiter
{
private int $maxRequestsPerSecond = 10;
private int $currentRequests = 0;
private \DateTime $windowStart;
public function wait(): void
{
$now = new \DateTime();
// Reset window every second
if ($now->getTimestamp() - $this->windowStart->getTimestamp() >= 1) {
$this->currentRequests = 0;
$this->windowStart = $now;
}
// Wait if rate limit exceeded
while ($this->currentRequests >= $this->maxRequestsPerSecond) {
usleep(100000); // 100ms
$now = new \DateTime();
if ($now->getTimestamp() - $this->windowStart->getTimestamp() >= 1) {
$this->currentRequests = 0;
$this->windowStart = $now;
}
}
$this->currentRequests++;
}
}
Adaptive Rate Limiter
namespace Vendor\Integration\Api\RateLimit;
class AdaptiveRateLimiter
{
private int $currentLimit = 100;
private int $minLimit = 10;
private int $maxLimit = 200;
public function handleResponse(ResponseInterface $response): void
{
$statusCode = $response->getStatusCode();
$remaining = $response->getHeader('X-RateLimit-Remaining');
if ($statusCode === 429) {
// Slow down
$this->currentLimit = max($this->minLimit, $this->currentLimit / 2);
} elseif ($remaining && (int) $remaining > 20) {
// Speed up
$this->currentLimit = min($this->maxLimit, $this->currentLimit + 10);
}
}
}
Quiz
1. What is API versioning?
2. Why use OAuth2 for API authentication?
3. What does a rate limiter do?
Flashcards
Question
What is API versioning?
Click to reveal answer
Answer
Managing multiple API versions for backward compatibility
Question
How does OAuth2 work?
Click to reveal answer
Answer
Client credentials → token request → Bearer token → refresh on expiry
Question
What is adaptive rate limiting?
Click to reveal answer
Answer
Adjusting limits based on API response codes
Question
What is retry with backoff?
Click to reveal answer
Answer
Increasing delays between failed request retries
Revision Notes
Key Takeaways
- 1. REST clients wrap HTTP calls with auth and rate limiting
- 2. API versioning enables backward compatibility
- 3. OAuth2 provides secure token-based authentication
- 4. Rate limiting prevents API abuse and overload
- 5. Retry with backoff handles transient failures
Interview Tips
- • Explain API versioning strategies (URL, header, query)
- • Discuss OAuth2 flow for server-to-server auth
- • Describe adaptive rate limiting based on response codes
- • Talk about handling rate limit responses gracefully
Cheat Sheet
API Integration:
RestClient → GET/POST/PUT/DELETE
Auth → OAuth2 / API Key
Rate Limit → requests/second
Retry → exponential backoff
Versioning:
URL: /v1/resource, /v2/resource
Header: Accept-Version: v2
Query: ?version=2
Rate Limiting:
Window-based: N requests/second
Adaptive: adjust based on response
Token bucket: allow bursts