Swagger Spotlight: 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.
  July 24, 2018
In this edition of Swagger Spotlight, Tristan Sokol, the Developer Advocate for Square, talks about how his team uses Swagger Codegen for SDK generation. To submit your own topic for Swagger Spotlight, click here

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 (formerly the Swagger Specification) 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 POSTrequest, 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 ;
use \ArrayAccess;
/**
 *  Class Doc Comment
 *
 * @category Class
 * @package  
 * @author   Square Inc.
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache License v2
 * @link     https://squareup.com/developers
 */
class  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!

This post was also featured on Square's technical blog - check it out to learn more 

Swagger Spotlight is an opportunity for memebers of the Swagger Community to share their insights and projects with the rest of the community. Learn more about how you can be featured next!