Build Mobile Apps Faster with AWS API Gateway

As developers, we want to make sure we add the most value to our products. One of the main ways to add value to a mobile app is making a great UI. After reading this post, you’ll learn how to automate a common task of JSON parsing so you can focus on building a great UI (which is more fun to do anyway 😉)

Why we care about parsing JSON data

The answer is simple. Most apps are API powered. In order to make our apps useful, we need to fetch data either from our own back end API or from an external web service. A typical networking architecture for a mobile app looks like this:

Typical Networking Architecture

The data is usually provided by a back end API. Which is consumed by our mobile app over HTTPS. Inside the iOS app, we have a request object responsible for sending and receiving network requests. Once the data is fetched from the network, it’s parsed from JSON and mapped to your model object. After the model object is ready, it can be used by a view controller. This data is now can be displayed in our UI.

As you can see, even a typical networking architecture is complex. There are 4 moving pieces that require integration between each other. The more moving pieces we have in the system, the more potential bugs we have in the app.

Imagine the world where our networking architecture is simplified and it looks like this:

Simplified Networking Architecture

This architecture is simpler, the request and the JSON parsing components go away completely. Both of these components that you had to code yourself previously are replaced by an auto-generated API Gateway package. The only thing you have to do is to integrate this package with your view controller.

What is API Gateway

Before we dive into technicalities of integrating AWS API Gateway into your app, let’s figure out what API Gateway is. API Gateway is an architecture pattern. It allows a client (in our case a mobile app) to request data from a single location (an API Gateway). Instead of requesting multiple pieces of data from different places, API gateway takes our request and figures out which service request the data from. It allows for the micro services architecture on the back end. As an added benefit, both sides (the client and the server) can be changed independently of each other. Which leads to faster release cycles and faster product delivery.

Amazon Web Services (AWS) has its own implementation of the API Gateway pattern called AWS API Gateway. Here is how it looks:

AWS API Gateway

There is a web interface available where you can define your API. You can define resources and methods like in any REST API. AWS API Gateway also has an API of its own available as an HTTP API. You can also access it from a command line tool.

The best part about AWS API Gateway is that you can generate a client side SDK. This SDK allows you to hit the API you defined and parse a response automatically.

Generate AWS API Gateway SDK

As you can see, there are several options. You can generate an SDK for your Android app, a Javascript website, iOS in Objective-C (if you’re still stuck in the 90s), iOS in Swift, and Java SDK.

How to integrate AWS API Gateway SDK into your mobile app.

In order to integrate the auto-generated SDK into your mobile app, you’ll need to write a little bit of code. Once you set up the initial infrastructure, updating it to support new endpoints is trivial. The code below is in Swift which you can use for your iOS app. Integrating it into an Android, Javascript or a Java app is similar.

Back End API Type protocol

protocol BackEndAPIType {
  func createUser(
    name: String,
    completion: @escaping ((NetworkResult<User>) -> Void))
  ...
}

Let’s start with defining a protocol for our back end API. All our API classes will implement this protocol. In our example, it has just one function that creates a user with a name and a completion block. A completion block accepts NetworkResult. NetworkResult is a generic enum specialized with a type of our model (in our case User). Let’s dive into the NetworkResult next.

Network Result

enum NetworkResult<ModelResult> {
  case success(ModelResult)
  case failure(BackEndAPIError)
}

NetworkResult is an enum with two cases: success and failure. The success case has an associated value of our result model. A failure case has an associated value of a BackEndAPIError which we define next.

Back End API Error

enum BackEndAPIError: Error {
  case missingData
  case mockConfiguration
  case other(error: Error)
}

The BackEndAPIError enum implements the Error protocol. You can define your custom errors here. For example, return the missingData error if your request came back with no data. If none of the common cases apply, return the other case with an associated value of the error coming from the back end. Now, most of our networking infrastructure pieces are defined, let’s start making concrete implementations of the BackEndAPIType protocol.

Live Back End API

class LiveBackEndAPI: BackEndAPIType {
  let client: MyBackEndAPIClient

  init(apiKey: String? = nil, baseURL: URL? = nil, client: MyBackEndAPIClient = MyBackEndAPIClient.default()) {
    self.client = client
    if let apiKey = apiKey {
      self.client.apiKey = apiKey
    }
    if let baseURL = baseURL {
      let endpoint = AWSEndpoint(region: client.configuration.regionType, service: .APIGateway, url: baseURL)
      self.client.configuration.baseURL = baseURL
      self.client.configuration.endpoint = endpoint
    }
  }

  func createUser(name: String, completion: @escaping ((NetworkResult<User>) -> Void)) {
    client.userPost(name: name).continueWith { task in
      self.processExpectingData(task: task, completion: completion)
      return nil
    }
  }

  private private func processExpectingData<Model>(task: AWSTask<Model>, completion: ((NetworkResult<Model>) -> Void)) {
    if let error = task.error {
      completion(.failure(.other(error: error)))
    } else if let result = task.result {
      completion(.success(result))
    } else {
      completion(.failure(.missingData))
    }
  }
}

The first implementation of the BackEndAPIType protocol is LiveBackEndAPI. As you might’ve guessed by its name, this class makes live requests to the server. LiveBackEndAPI holds the reference to the client class coming from the AWS API Gateway SDK (it’s named MyBackEndAPIClient in our case). If you have multiple environments (like development, staging, production), you can define a different set of keys for each environment and pass it inside the initializer to configure it appropriately. Once LiveBackEndAPI is initialized, we can call the AWS API Gateway provided function called userPost on the client object. It will make a real HTTP request and give us back an AWSTask class containing either an error or a result. If your request is successful, your result will contain a fully instantiated model (in our case a User) with its fields mapped to the values from the payload.

Mock Back End API

class MockBackEndAPI: BackEndAPIType {
    func createUser(name: String, completion: @escaping ((NetworkResult<User>) -> Void)) {
    completeWithMockUser(name: name, completion: completion)
  }

  private func completeWithMockUser(name: String, completion: ((NetworkResult<User>) -> Void)) {
    guard let user = User() else {
      assertionFailure("Failed to create a mock version of a model. Make sure the mock models are used correctly.")
      completion(.failure(.mockConfiguration))
      return
    }
    user.name = name
    completion(.success(user))
  }
}

The second concrete implementation of our BackEndAPIType protocol is MockBackEndAPI. It doesn’t make live requests and it’s used primarily in automated tests (both UI and unit tests) and for local development. It’s very useful to use a mock API for writing a robust and a repeatable test suite. You also get a benefit of being able to develop against a non-functioning or even non-existent back end API.

Default API

struct BackEndAPI {
  static let APIKey = "super_secure_key"
  static let baseURL = URL(string: "https://api.example.com")

 static var defaultAPI: BackEndAPIType {
    if ProcessInfo.isRunningTests {
      return MockBackEndAPI()
    } else {
      let liveBackEnd = LiveBackEndAPI(apiKey: APIKey, baseURL: baseURL)
      return liveBackEnd
    }
  }
}

The final piece of our network infrastructure is BackEndAPI. It has a static property called defaultAPI which returns us an instance of MockBackEndAPI or a LiveBackEndAPI depending on whether the process runs inside tests or not. The isRunningTests property is specific to each project. There are multiple ways of figuring out whether your process is running inside a test. One of the options is detecting an XCTest class inside the process environment or setting an environment variable of your own.

That’s it

That's it

As you saw, we didn’t have to write any JSON parsing code (which is tedious to write). If you update your API, you just export a new SDK, add it to your app and integrate the new endpoints with your app. It involves significantly less effort and allows you to move faster.

Should you use API Gateway for your project?

It depends. I’m a firm believer in the value of working code. If there is already an existing API in your project, even if this code is old, as long as you don’t have to touch it, there is no need to rewrite it. What you can try is migrating towards using API Gateway gradually. You can try one endpoint, see how it goes and then migrate the rest.

On the other hand, if you’re starting on a new greenfield project I would definitely give API Gateway a shot. Especially if you’re looking to implement the micro services architecture on the back end. AWS API Gateway also integrates well with AWS Lambda, which is their severless product allowing for cost efficient request execution.

Final words

Focus on what matters most

We covered a lot of things in this post. We found out why consuming data is important since most apps are API powered. We went over the API Gateway pattern and learned how we can simplify our app’s networking architecture to take advantage of AWS API Gateway.

I want to leave you with one takeaway. As software engineers, we play a crucial role in human society. We build tech products which millions of people use every day. In order to continue to do so, we have to focus on what matters most. In mobile apps, the most value is in the user facing UI and the data. The plumbing in between is important and the UI and the data are more important. Using API Gateway is only one of the ways of achieving it. Your project may have a different way of delivering the most value. Let it be your challenge, think about the ways to focus on what matters most.

More Info