Scaling third party integrations

class TrafficController extends Controller {
	
	private static $allowed_actions = array('incidents');

	public function incidents($request) {
		$service = new RestfulService(TRAFFIC_API_URL);
		$response = $service->request('/incidents');
		
		return ($response->isError()) ? json_encode($response->getBody()) : '';
	}
}

RestfulService - y u no Guzzle?

Empty return value with HTTP 200 = bad

class TrafficController extends Controller {
	
	private static $allowed_actions = array('incidents');

	public function incidents($request) {
		$service = new RestfulService(TRAFFIC_API_URL);
		$response = $service->request('/incidents');
		if($response->isError()) {
			return $this->httpError(500);
		}

		return json_encode($response->getBody());
	}
}

How do you test the service?

class TrafficController extends Controller {
	
	protected $trafficService;

	private static $allowed_actions = array('incidents');

	public function __construct() {
		parent::__construct();

		$this->trafficService = Injector::inst()->create(
                    'TrafficService', 
                    TRAFFIC_API_URL
                );
	}

	public function incidents($request) {
		$incidents = $this->trafficService->getIncidents();
		if($incidents === null) {
			return $this->httpError(500);
		}

		return json_encode($incidents);
	}
}
class TrafficService {

	protected $httpClient;

	public function __construct($baseUrl) {
		$this->httpClient = new RestfulService($baseUrl);
	}
	
	public function getIncidents() {
		$response = $this->httpClient->request('/incidents');
		if($response->isError()) {
			return null;
		}

		$incidents = $response->getBody();
		// ... filter and transform

		return $incidents;
	}

	public function setHttpClient($client) {
		$this->httpClient = $client;
	}
}

How do you find about about errors?

class TrafficService {

	protected $httpClient;

	public function __construct($baseUrl) {
		$this->httpClient = new RestfulService($baseUrl);
	}
	
	public function getIncidents() {
		$response = $this->httpClient->request('/incidents');
		if($response->isError()) {
			SS_Log::log(
				sprintf(
					'TrafficService::getIncidents() failed with %d: %s', 
					$response->getStatusCode(),
					$response->getBody()
				),
				SS_Log::WARN
			);
			return null;
		}

		$incidents = $response->getBody();
		// ... filter and transform

		return $incidents;
	}
}

Raygun rocks

NFR

class TrafficService {

	protected $httpClient;

	public function __construct($baseUrl) {
		$this->httpClient = new RestfulService($baseUrl);
	}
	
	public function getIncidents() {
		$response = $this->httpClient->request('/incidents');
		if($response->isError()) {
			SS_Log::log(
				sprintf(
					'TrafficService::getIncidents() failed with %d: %s', 
					$response->getStatusCode(),
					$response->getBody()
				),
				SS_Log::WARN
			);
			return null;
		}

		$incidents = $response->getBody();
		// ... filter and transform

		return $incidents;
	}
}

RestfulService caches for an hour by default

Curl settings: 5 seconds connection timeout

Curl settings: No request timeout (just max_execution_time)

Only caches on HTTP success

class GenerateTrafficIncidentsJob extends AbstractQueuedJob implements QueuedJob {

  public function __construct(TrafficService $trafficService) {
    $this->trafficService = $trafficService;
  }
  
  public function process() {
    $service = $this->trafficService;

    $incidents = $service->getIncidents();
    if($incidents === null) {
      $this->addMessage('Incidents job failed', 'FAIL');
      return;
    }

    file_put_contents(ASSETS_PATH . '/incidents.json', json_encode($incidents));
    $this->completeJob();
  }

  protected function completeJob() {
    $job = new GenerateTrafficIncidentsJob();
    singleton('QueuedJobService')->queueJob($job, date('Y-m-d H:i:s', time() + 30));
    $this->isComplete = true;
  }
}

What if the queue stalls?

class TrafficEnvironmentCheck implements EnvironmentCheck {

  protected $maxAgeSecs = 600;

  public function check() {
    $modTime = filemtime(ASSETS_PATH . '/incidents.json');
    $isRecent = $modTime > (time() - $this->maxAgeSecs);

    if($isRecent) {
      return array(EnvironmentCheck::OK);
    } else {
      return array(EnvironmentCheck::ERROR, sprintf(
        "incidents.json is older than %d secs", $this->maxAgeSecs
      ));
    }
  }
}

Operation

Thanks

Made with Slides.com