AWS SDK Go JAVA python php logo
Technology

How To Build A Scalable SDK The Right Way (Learnings From AWS SDKs)

AWS SDK maintains and publishes SDK for over 110+ services with simultaneous and multiple updates on all SDKs each day. They have achieved what interestingly all big companies strive for, launching new APIs seamlessly and effortlessly. During AWS’s flagship entertainment packed conference “Re:Invent, the SDKs are updated with new APIs that were just announced in less than 2 hours for the world to use.

To interact with any of AWS services you need to use a AWS SDK in either python, JAVA or any other language your software is in.

I have been an engineer in the PHP SDK for AWS which is open-sourced on Github and anyone can look at the code published by the AWS teams. AWS manages over 8 SDKs including SDK for JAVA, ruby, python, javascript, PHP, go, C++ and few other languages.

Generally, it requires a significant number of engineers to manage just one SDK. To make sure all APIs and features are available across all SDKs is a big feat. In this post, we are going to talk about how to scale the SDK and how AWS is able to achieve perfect synchronicity across all SDKs without sloshing the SDKs team with manually updating each SDK.

How does the AWS SDK works?

All of the SDKs have two parts one is the core and another one has all the service-specific code. The core of the SDK handles various features such as “Signing each request using AWS’s Sigv4 protocol”, security features, SSL handling, multi-threading, asynchronous requests, waiters, paginators, and a ton more features.

The other part of each SDK is based on “model” files specific to each service. All model files are exactly similar across each SDK.

The difference is very evident in AWS’s python SDK which is divided into two different repositories “boto” and “botocore”.

All the model files are built on OpenApi protocol to some extent and are compatible with Swagger API Protocols.

Here is a JSON definition for DynamoDB in AWS PHP SDK with name “api-2.json” and  the exact same file also exists in Boto SDK as well with name “service-2.json

At AWS each service will release these JSON model files which specify all information related to each API & service. Each SDK handles these files a bit differently as compared to other SDKs. PHP SDK loads these JSON files dynamically in each client to allow various APIs while JAVA SDK will pre-build each of these methods into concrete classes.

Each AWS team maintains and releases these service-specific model file, which are then consumed by each SDK team in the way they would like.

Show Me The Code!

I am sure these look like magic to you like it looked to me when I first saw it. So let’s dig into the code. We will look at PHP SDK example as that is team I was part of for over a year and know in and out of the SDK.

We will look at the AWS Lambda’s invoke method.

"Invoke":{
      "name":"Invoke",
      "http":{
        "method":"POST",
        "requestUri":"/2015-03-31/functions/{FunctionName}/invocations"
      },
      "input":{"shape":"InvocationRequest"},
      "output":{"shape":"InvocationResponse"},
      "errors":[
        {"shape":"ServiceException"},
        {"shape":"ResourceNotFoundException"},
        {"shape":"InvalidRequestContentException"},
        {"shape":"RequestTooLargeException"},
        {"shape":"UnsupportedMediaTypeException"},
        {"shape":"TooManyRequestsException"},
        {"shape":"InvalidParameterValueException"},
        {"shape":"EC2UnexpectedException"},
        {"shape":"SubnetIPAddressLimitReachedException"},
        {"shape":"ENILimitReachedException"},
        {"shape":"EC2ThrottledException"},
        {"shape":"EC2AccessDeniedException"},
        {"shape":"InvalidSubnetIDException"},
        {"shape":"InvalidSecurityGroupIDException"},
        {"shape":"InvalidZipFileException"},
        {"shape":"KMSDisabledException"},
        {"shape":"KMSInvalidStateException"},
        {"shape":"KMSAccessDeniedException"},
        {"shape":"KMSNotFoundException"},
        {"shape":"InvalidRuntimeException"}
      ]
    }

The above API is expecting an “InvocationRequest” object and returns a “InvocationResponse” object.

This is how the “InvocationRequest” is defined.

"InvocationRequest":{
      "type":"structure",
      "required":["FunctionName"],
      "members":{
        "FunctionName":{
          "shape":"NamespacedFunctionName",
          "location":"uri",
          "locationName":"FunctionName"
        },
        "InvocationType":{
          "shape":"InvocationType",
          "location":"header",
          "locationName":"X-Amz-Invocation-Type"
        },
        "LogType":{
          "shape":"LogType",
          "location":"header",
          "locationName":"X-Amz-Log-Type"
        },
        "ClientContext":{
          "shape":"String",
          "location":"header",
          "locationName":"X-Amz-Client-Context"
        },
        "Payload":{"shape":"Blob"},
        "Qualifier":{
          "shape":"Qualifier",
          "location":"querystring",
          "locationName":"Qualifier"
        }
      },
      "payload":"Payload"
    }

This is the response object.

 "InvocationResponse":{
      "type":"structure",
      "members":{
        "StatusCode":{
          "shape":"Integer",
          "location":"statusCode"
        },
        "FunctionError":{
          "shape":"String",
          "location":"header",
          "locationName":"X-Amz-Function-Error"
        },
        "LogResult":{
          "shape":"String",
          "location":"header",
          "locationName":"X-Amz-Log-Result"
        },
        "Payload":{"shape":"Blob"},
        "ExecutedVersion":{
          "shape":"Version",
          "location":"header",
          "locationName":"X-Amz-Executed-Version"
        }
      },
      "payload":"Payload"
    },

The PHP SDK auto-generates code for each of these request and response object and here how the code would look:

$result = $client->invoke(array(
    // FunctionName is required
    'FunctionName' => 'string',
    'InvocationType' => 'string',
    'LogType' => 'string',
    'ClientContext' => 'string',
    'Payload' => 'mixed type: string|resource|\Guzzle\Http\EntityBodyInterface',
    'Qualifier' => 'string',
));

Even the docs are automatically generated from the model files.

So the various SDK team will not need to write any code “manually” at all for any of these service updates. The service teams,( in this case, AWS Lambda) just needs to make sure that the model file they are creating is error free. The SDK will be able to auto-generate any updates to existing clients as well as auto-generating code for new APIs and services.

The best part of the SDK team is that is is completely open-sources and is directly integrated into various AWS services.

API Gateway allows you to deploy Swagger (also knows as OpenAPI Standards) models and then use the APIs to directly generate SDKs with all the features that are already inbuilt into the AWS SDKs.

I hope this will prove helpful in designing your SDKs for your services. In any SDK the focus should be on creating a model file that is compliant with “OpenApi” standards. By having a standard model for APIs, it will get super easy and fast to launch various SDKs in various languages with very little development time.

Comments

0 comments

Leave a Reply