async-aws
Jérémy DERUSSÉ
Developer at Blackfire.io
@symfony core team
@jderusse
DX
Developer
eXperience
tooling
- debug bar
- maker bundle
- flex
- autowiring/configure
- ...
simple and intuitive Interfaces
$response = $client->request(
'GET',
'https://api.github.com/users/jderusse');
// do something asynchronous
dump($response->getContent());
$promise = $client->requestAsync(
'GET',
'https://api.github.com/users/jderusse');
$promise->then(function(ResponseInterface $res) {
dump($res->getBody());
});
// do something asynchronous
$promise->wait();
symfony/http-client
guzzle
aws/aws-sdk-php
- 100% feature complete
- maintained
- documented
official
no auto-completion
unreadable code
/**
* ...
* @method \Aws\Result sendBulkEmail(array $args = [])
* @method \GuzzleHttp\Promise\Promise sendBulkEmailAsync(array $args = [])
* @method \Aws\Result sendCustomVerificationEmail(array $args = [])
* @method \GuzzleHttp\Promise\Promise sendCustomVerificationEmailAsync(array $args = [])
* ...
* @method \Aws\Result sendEmail(array $args = [])
* @method \GuzzleHttp\Promise\Promise sendEmailAsync(array $args = [])
* ...
*/
class SesV2Client extends AwsClient
{
public function __call($name, array $args)
{
// ...
}
}
symfony AWS
- messenger SQS
- mailer SES
- notifier SNS
basic support
@Nyholm
Tobias Nyholm
Developer at Happyr.com
@symfony core team
@symfony CARE team
async-aws
how to maintain?
- 276 services
- 10,294 operations
- new version ~every day
generated code
{
"operations":{
"SendEmail":{
"name":"SendEmail",
"http":{
"method":"POST",
"requestUri":"/v2/email/outbound-emails"
},
"input":{"shape":"SendEmailRequest"},
"output":{"shape":"SendEmailResponse"},
"errors":[
{"shape":"TooManyRequestsException"},
{"shape":"LimitExceededException"}
]
}
},
"shapes": {
"SendEmailRequest":{
"type":"structure",
"required":["Content"],
"members":{
"FromEmailAddress":{"shape":"EmailAddress"},
"FromEmailAddressIdentityArn":{"shape":"AmazonResourceName"},
"Destination":{"shape":"Destination"},
"ReplyToAddresses":{"shape":"EmailAddressList"},
"FeedbackForwardingEmailAddress":{"shape":"EmailAddress"},
"FeedbackForwardingEmailAddressIdentityArn":{"shape":"AmazonResourceName"},
"Content":{"shape":"EmailContent"},
"EmailTags":{"shape":"MessageTagList"},
"ConfigurationSetName":{"shape":"ConfigurationSetName"},
"ListManagementOptions":{"shape":"ListManagementOptions"}
}
},
"SendEmailResponse":{
"type":"structure",
"members":{
"MessageId":{"shape":"OutboundMessageId"}
}
}
}
}
official sdk
class SesClient
{
public function sendEmail($input): SendEmailResponse { ... }
}
class SendEmailRequest
{
/* var string|null */
private $fromEmailAddress;
/* var Destination|null */
private $destination
...
}
class SendEmailResponse
{
public function getMessageId(): ?string { ... }
}
class TooManyRequestsException extends Exception {}
class LimitExceededException extends Exception {}
final class Destination
{
private $toAddresses;
private $ccAddresses;
private $bccAddresses;
}
...
async-aws
automated updates
how to provide great DX?
- simple interfaces
- allowing advanced usage
leverage symfony/http-client
$response = $sesClient->sendEmail([
'Content' => $message,
]);
// do something asynchronous
class SesClient
{
private HttpClientInterface $httpClient;
public function sendEmail($input): SendEmailResponse
{
$req = $this->sign($input);
$response = $this->httpClient->request(...$req);
return new SendEmailResponse(
$response,
$this->client
);
}
}
class SesClient
{
private HttpClientInterface $httpClient;
public function sendEmail($input): SendEmailResponse
{
$req = $this->sign($input);
$response = $this->httpClient->request(...$req);
$response = $sesClient->sendEmail([
'Content' => $message,
]);
SesClient
HttpClient
HttpResponseInterface
SendEmailResponse
$response = $sesClient->sendEmail([
'Content' => $message,
]);
// do something asynchronous
$response->getMessageId();
no magic
$response = $sesClient->sendEmail([
'Content' => $message,
]);
// do something asynchronous
echo $response->getMessageId();
class SendEmailResponse
{
private ResponseInterface $httpResponse;
private HttpClientInterface $httpClient;
public function getMessageId(): ?string
{
$this->resolve();
$this->populateResponse();
return $this->messageId;
}
}
class SendEmailResponse
{
private ResponseInterface $httpResponse;
private HttpClientInterface $httpClient;
public function getMessageId(): ?string
{
$this->resolve();
$this->populateResponse();
return $this->messageId;
}
public function resolve(?float $timeout = null): bool
{
foreach ($this->httpClient->stream($this->httpResponse, $timeout) as $chunk) {
if ($chunk->isTimeout()) {
return false;
}
if ($chunk->isFirst()) {
break;
}
}
$this->handleErrors();
return true;
}
}
async first
$response = $sesClient->sendEmail([
'Content' => $message,
]);
echo $response->getMessageId();
async
sync/blocking
$response = $sesClient->sendEmail([
'Content' => $message,
]);
// do something asynchronous
echo $response->getMessageId();
streaming
$response = $s3Client->putObject([
'Bucket' => 'my-bucket',
'Key' => 'video.mkv',
'Body' => fopen($filename, 'r'),
]);
while (!$response->resolve(0.1)) {
$spinner->tick();
}
echo 'Done: '.$response->getEtag();
parallel processing
$results = [];
for ($users as $user) {
$results[] = $sesClient->sendEmail([
'Content' => $message,
'Destination' => ['ToAddresses' => [$user->email]],
]);
}
foreach (Result::wait($results, null, true) as $result) {
echo $result->getMessageId();
}
retry failed request
$httpClient = HttpClient::create();
$httpClient = new RetryableHttpClient(
$httpClient,
new GenericRetryStrategy()
);
namespace AsyncAws\Core\HttpClient;
class AwsRetryStrategy extends GenericRetryStrategy
{
public function shouldRetry(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): ?bool
{
if (parent::shouldRetry($context, $responseContent, $exception)) {
return true;
}
...
$error = $this->createError($responseContent, $context->getHeaders());
return \in_array($error->getCode(), [
'RequestLimitExceeded',
'Throttling',
'ThrottlingException',
'ThrottledException',
'LimitExceededException',
'PriorRequestNotComplete',
'ProvisionedThroughputExceededException',
'RequestThrottled',
// ...
], true);
}
}
you already use it
- bref/bref
- bref/symfony-messenger
- oneup/flysystem-bundle
- league/flysystem-bundle
- symfony/amazon-mailer
- symfony/amazon-sqs-messenger
@jderusse
slides
Thank you!
something is missing?
Contribute
async-aws
By Jérémy Derussé
async-aws
- 1,764