Introducing the Knewton Client Library

Why Build a Client Library?

As part of Knewton’s mission to personalize learning for everyone, Knewton strives to provide an easy-to-use API that our partners can leverage in order to experience the power of adaptive learning. Knewton’s API follows industry norms and standards for authentication and authorization protocols, error handling, rate limiting, and API structure.

To authenticate incoming API requests from client applications, Knewton uses two-legged OAuth 2.0. Currently, each partner has to implement OAuth 2.0 on the client side from scratch, which may increase the potential of authentication failures when users try to log in.

Partners also need to be well-versed in Knewton’s API standards. Using the right API endpoint along with valid credentials is critical to ensuring that the client application scales effectively upon launching. Beyond the initial integration, partners are generally cautious about upgrading their applications when Knewton releases updates or new endpoints.

To help simplify and standardize development of client applications, Knewton is starting to pilot the Knewton Client Library (KCL). The KCL boilerplate code will allow partners to build client applications faster and more efficiently. Implementing KCL in client applications will allow partners to devote more resources to the actual client application and enriching the overall experience of the students and teachers who use it. Using KCL will also offer partners a smoother upgrade path, since Knewton will absorb any backwards compatibility complexities into the library.

Components of the Knewton Client Library

The Knewton Client Library uses the Swagger framework. Utilizing the contract first method of SOA development, Swagger provides a set of tools for REST APIs. It has a standardized definition format which can be used to generate code and documentation.

A centralized API definition ties together 1) server side implementation, 2) client code generation, and 3) API documentation, ensuring that they stay in sync and reducing time spent writing boilerplate code.

A centralized API definition ties together 1) server side implementation, 2) client code generation, and 3) API documentation, ensuring that they stay in sync and reducing time spent writing boilerplate code.

As part of the integration process, our partners must create and register student accounts through the Knewton API. The following Swagger definition is a simplified example of a dummy endpoint to create a student account, POST /student.

# Path
/student:
  post:
    summary: Create a student
    operationId: createStudent  
    parameters:
      - name: StudentCreatePayload
        in: body
        required: true
        schema:
          properties:
            name:
              description: Student's first and last name
              type: string
            courses:
              description: Array of Course IDs
              type: array
              items:
                type: integer  
            date_created:
              description: Date student was added
              type: string
              format: date
    responses:
      200:
        description: On success, returns no content

This example describes a dummy POST REST endpoint which takes in a JSON object with a student’s name, courses, and creation date. A request to such an endpoint might look like:

curl -H "Content-Type: application/json" -X POST -d '{"name":"Knewton Student","courses":[19, 123],"date_created":"September 1, 2016",}' http://knewton-dummy-api.com/student

The Knewton Java Client Library

The swagger-codegen library can generate API clients in many popular programming languages from a definition file. Let’s take a look at how we use this to generate the Knewton Java Client Library.

We use the swagger-codegen library to generate a Java Retrofit API interface from our API definition. Retrofit uses annotations on interface methods and parameters to describe the API.  These are then parsed by an HTTP client to execute requests.

Here is the Retrofit interface, which was automatically generated from the student creation endpoint definition. The interface matches the Swagger definition above: it defines a POST request to the “/student” endpoint, which takes a StudentCreatePayload (containing name, courses, and date created) as a parameter that is automatically serialized to JSON and added to the request body.

public interface Api {

    @POST("/student")
    Void createStudent(
        @Body StudentCreatePayload studentCreatePayload
    );
}

The auto-generated code provides a baseline client implementation. To make KCL as simple to use as possible, KCL wraps the auto-generated client with higher-level functionality such as authentication and rate limiting. The wrapper also refactors methods, parameters, and return objects to provide a more natural interface for Java developers.

For example, while Java has the java.util.Date object, the autogenerated code uses a String. The wrapper allows users to pass java.util.Date objects and will restructure them to the strings that the Swagger-generated API class expects.

Continuing with the student creation example above, we can further simplify the partner-facing interface. The API endpoint requires that the caller provides the date that the student record is created. There is no need for KCL to require the current date, as this can be determined automatically. Therefore the only parameters in our wrapped createStudent method are the student name and course list.

public void createStudent(String name, List<Integer> courses) {
    Date dateCreated = new Date();
    StudentCreatePayload studentCreatePayload = new StudentCreatePayload();
    studentCreatePayload.setName(name);
    studentCreatePayload.setCourses(courses);
    studentCreatePayload.setDateCreated(dateCreated.toString());
    generatedApi.createStudent(studentCreatePayload);
}

Instead of writing all of the code in the create student method, a partner application can now simply call wrapper.createStudent(name, courses);

OAuth, Retrying, and Logging

KCL uses OkHttp as its underlying client library.  OkHttp integrates well with Retrofit and provides an easy way to extend its functionality via Retrofit’s interceptor interface. Each client can have multiple interceptors. Each interceptor can modify a request or response at any point. For example, an interceptor could monitor, amend, and retry requests based on information read from responses.

As the diagram below illustrates, OkHttp nests interceptors added to a client, so the first interceptor to process a request will be the last interceptor to process a response.

KCL uses interceptors for logging, OAuth and retrying on rate limiting.

KCL uses interceptors for logging, OAuth and retrying on rate limiting.

Logging Interceptor

The logging interceptor is the simplest interceptor because it does not modify or react to a request or response. This interceptor logs information from the request made to Knewton and the response received from Knewton.

The following is a simplified example of the KCL logging interceptor. This sample interceptor logs the request headers and request body, as well as the response code.

@Override
public Response intercept(Chain chain) {
    Request request = chain.request();

    // log request information
    logger.log("Request Headers: " + request.headers());
    logger.log("Request Body: " + request.body());

    // Continue with the interceptor chain
    Response response = chain.proceed(request);

    // log response information
    logger.log("Response Code: " + response.code());

    return response;

}

OAuth is an industry standard authentication protocol used for API authentication.

KCL includes an OAuth interceptor to handle authentication. If a user is already authenticated, there will be an authorization header on the request and the interceptor will no-op. If there is no authorization header, two possible cases would require re-authentication. Either the user does not have an access token in which case KCL requests one, or the user’s access token is not working. Either the access token has expired, in which case KCL uses the refresh token to request a new access token, or the refresh token has expired, in which case KCL must request a new authorization altogether.

Here is a sample OAuth interceptor:

knewton-client-library-oauth-flowchart

@Override
public Response intercept(Chain chain) {
    Request request = chain.request();

    // Already authorized (no-op)
    if (request.header("Authorization") != null) {
    return chain.proceed(request);
    }

    // Get a new auth and add it to the request
    Auth auth = authCache.getAuth();
    request.addHeader("Authorization", auth);

    Response response = chain.proceed(request);

    // Access token expired
    if (response.code() == 401) {
        auth = authCache.getNewAccessToken();
        request.addHeader("Authorization", auth);
        response = chain.proceed(request);
    }

    // Refresh token expired
    if (response.code() == 401) {
        auth = authCache.getNewRefreshToken();
        request.addHeader("Authorization", auth);
        response = chain.proceed(request);
    }

    return response;

}

Rate Limiting and Entity Limiting

Knewton enforces a set of rate limits to ensure that no single user can overwhelm the API with traffic and shut out other partners. After a specified number of requests per minute for a given endpoint, the Knewton API will respond with HTTP 429 errors, indicating that the request has been rate-limited and rejected. It can be retried in the next minute.

Some requests to Knewton’s API are not so time-sensitive that a delay of up to a minute would affect users. Therefore, KCL has incorporated automatic configurable retrying after a time increment specified by the partner.

This is an example of one retry.

private static final int HTTP_TOO_MANY_REQUESTS = 429;

@Override
public Response intercept(Chain chain) {
    Request request = chain.request();
    Response response = chain.proceed(request);

    if (response.code() == HTTP_TOO_MANY_REQUESTS) {
        Thread.sleep(RETRY_WAIT_TIME);
        response = chain.proceed(request);
    }

    return response;

}

If KCL continues to receive HTTP 429s, it will repeat this process until it times out. Since partners can configure the timeline for this process, they do not have to worry about handling these errors in their client applications.

Conclusion

With the Knewton Client Library, partners can more easily integrate with the Knewton adaptive learning platform, and consequently focus their efforts on delivering a great user experience to students and teachers accessing their applications. KCL also allows partners to take advantage of new features as Knewton releases them.

Thank you to Aditya Subramaniam and Paul Sastrasinh for their contributions to this blog post.