How Square makes its SDKs
At Square we leverage the OpenAPI standard, Swagger Codegen & GitHub to build and deliver our client SDKs in a scalable way.
The developer platform team at Square is a little different than most. Rather than build separate APIs for our developer products, we focus on exposing the APIs that our first-party product use to create a seamless experience for developers. We have many upstream teams that are stakeholders in our external facing APIs, constantly wanting to expose new features and make improvements. This was an important factor when deciding how we should build our SDKs; we did not want our team to be a bottle-neck, where product teams would have to wait on us to finish updating SDKs before releasing new features. The primary way we avoid that is with SDK generation.
SDK Generation
Instead of writing each of our SDKs by hand (which would not only be time consuming, error prone, and slow down the release of new features into the SDKs) we use a process that relies heavily on SDK generation. There are many flavors of SDK generation out there, so if you are looking into adopting a similar method for your SDKs, be sure to look at a range of the possibilities and find the right one for you. Our preferred flavor uses the OpenAPI specification to define our API endpoints and Swagger Codegen to programmatically generate the code for the SDKs.
API specification
We use the OpenAPI standard to define our APIs. For us, this is a JSON
file that defines the url, what kind of HTTP request to make, as well as what kind of information to provide, or expect to get back for each our API endpoints. Our specification is made up of 3 main parts: general info/metadata, paths, and models.
General info/metadata
This part of the spec contains some of the descriptive information for the API overall, like where you can find licensing information, or who to contact for help.
"info": {
"version": "2.0",
"title": "Square Connect API",
"description": "Client library for accessing the Square Connect APIs",
"termsOfService": "https://connect.squareup.com/tos",
"contact": {
"name": "Square Developer Platform",
"email": "[email protected]",
"url": "https://squareup.com/developers"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
}
},
Paths
These describe the individual endpoints (or URL paths) for the API. It describes what kind of HTTP
request to make, how it should be authorized, and what kind of information you should add to the request, and what you should expect to get back. In the example below, you can see that it is a POST
request, there are a couple required parameters in the URL, another one in the body, and you get back a CreateRefundResponse
object.
"/v2/locations/{location_id}/transactions/{transaction_id}/refund": {
"post": {
"tags": [
"Transactions"
],
"summary": "CreateRefund",
"operationId": "CreateRefund",
"description": "Initiates a refund for a previously charged tender.\n\nYou must issue a refund within 120 days of the associated payment. See\n(this article)[https://squareup.com/help/us/en/article/5060] for more information\non refund behavior.",
"x-oauthpermissions": [
"PAYMENTS_WRITE"
],
"security": [
{
"oauth2": [
"PAYMENTS_WRITE"
]
}
],
"parameters": [
{
"name": "location_id",
"description": "The ID of the original transaction\u0027s associated location.",
"type": "string",
"in": "path",
"required": true
},
{
"name": "transaction_id",
"description": "The ID of the original transaction that includes the tender to refund.",
"type": "string",
"in": "path",
"required": true
},
{
"name": "body",
"in": "body",
"required": true,
"description": "An object containing the fields to POST for the request.\n\nSee the corresponding object definition for field details.",
"schema": {
"$ref": "#/definitions/CreateRefundRequest"
}
}
],
"responses": {
"200": {
"description": "Success",
"schema": {
"$ref": "#/definitions/CreateRefundResponse"
}
}
}
}
},
Models
The models describe the different objects that the API interacts with. They are used primarily for serializing the JSON response from the API into native objects for each language. In this one, CreateRefundResponse
, you can see it has a couple other models that it is comprised of, as well as a description and even an example of what the response looks like.
"CreateRefundResponse": {
"type": "object",
"properties": {
"errors": {
"type": "array",
"items": {
"$ref": "#/definitions/Error"
},
"description": "Any errors that occurred during the request."
},
"refund": {
"$ref": "#/definitions/Refund",
"description": "The created refund."
}
},
"description": "Defines the fields that are included in the response body of\na request to the [CreateRefund](#endpoint-createrefund) endpoint.\n\nOne of `errors` or `refund` is present in a given response (never both).",
"example": {
"refund": {
"id": "b27436d1-7f8e-5610-45c6-417ef71434b4-SW",
"location_id": "18YC4JDH91E1H",
"transaction_id": "TRANSACTION_ID",
"tender_id": "TENDER_ID",
"created_at": "2016-02-12T00:28:18Z",
"reason": "some reason",
"amount_money": {
"amount": 100,
"currency": "USD"
},
"status": "PENDING"
}
},
"x-sq-sdk-sample-code": {
"python": "/sdk_samples/CreateRefund/CreateRefundResponse.python",
"csharp": "/sdk_samples/CreateRefund/CreateRefundResponse.csharp",
"php": "/sdk_samples/CreateRefund/CreateRefundResponse.php",
"ruby": "/sdk_samples/CreateRefund/CreateRefundResponse.ruby"
}
},
You can see the most recent version of our specification to date version in our Connect-API-Specification repo on GitHub.
The specification is an important part of our generation process, as it is the source of truth about how our APIs work. When other teams want to expand their APIs, release new APIs, or just increase the clarity of a model description, they can make an edit to this single file and have their changes propagate to all of the client SDKs. We actually generate most of our specification from the files that describe the internal service to service communication for even more process automation and easier changes.
Swagger Codegen
Now that we have the specification for our APIs ready to go, how do we turn it into a client facing SDK? The answer is Swagger Codegen. Swagger Codegen is an open source project supported by Smartbear (just like the other Swagger tools) that applies your Open API specification to a series of templates for SDKs in different languages with a little configuration sprinkled in.
Templates
The templates use a language called mustache to define their parts, and for the most part look and read like a file in the desired language. The one below is part of the templates for out PHP SDK. You can see that useful things like code comments are auto generated as well, so that the end SDK can have built in documentation, snippets & more.
<?php
{{#models}}
{{#model}}
/**
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen
* Do not edit the class manually.
*/
namespace {{modelPackage}};
use \ArrayAccess;
/**
* {{classname}} Class Doc Comment
*
* @category Class
* @package {{invokerPackage}}
* @author Square Inc.
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License v2
* @link https://squareup.com/developers
*/
class {{classname}} implements ArrayAccess
{
...
Configuration
These are actually much less complex, and are essentially small json
files that describe aspects of your SDK, generally around how it fits into the relevant package manager.
{
"projectName": "square-connect",
"projectVersion": "2.8.0",
"projectDescription": "JavaScript client library for the Square Connect v2 API",
"projectLicenseName": "Apache-2.0",
"moduleName": "SquareConnect",
"usePromises": true,
"licenseName": "Apache 2.0"
}
Because the Codegen project is so active, we actually check in a copy of our template files for each of our supported SDKS, and pin to specific Codegen versions to make sure that we don’t accidentally push breaking changes to our users as a result of all the automation. You can see the all of the templates and config files that power the {Java, PHP, C#, Python, Ruby, JavaScript} SDKs in the same repository as our specification file: Connect-API-Specification.
Other Ideas
Our process has evolved quite a bit, with tools like Travis CI making big impacts in the process. You can use CI & CD tools to make the process more automated but be sure that you have a good suite of test coverage to help prevent unexpected changes from creeping into your released code.
Hope your enjoyed the look into our SDK generation process. You can also see a recorded talk I gave at DevRelCon about the subject here. If you want to learn more about our SDKs, or other technical aspects of Square, be sure to follow on this blog, our Twitter account, and sign up for our developer newsletter!