Type-safe API call with Protocol Buffers in Swift

Apple recently open sourced swift-protobuf which is a plugin of Protocol Buffers for swift language. Protocol Buffers in Swift enables us to have type safety, make API faster and unify schema documentation of structured data. I had a chance to use swift-protobuf in my project and thought that it has many benefits for us, so I would like to share my knowledges and experiences.

I also created a repository which has sample server/client app with Protocol Buffers. Please take a look here if you’re interested in what implementation looks like.

What’s Protocol Buffers?

Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data — think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.

Protocol Buffers is developed by Google in 2008 and it’s been used internally, but they released it as open source project.

It’s also used in gRPC.

Languages

Most of major languages are supported. For example C++, Go, Java, Python, Ruby, C#, Objective-C, Javascript, PHP and of course Swift. You can see more details here.

How it works

Schema of data structure should be defined with message type in .proto file, so that swift-protobuf can generate swift source code.
 Here is an example of token.proto file.

message Token { 
string accessToken = 1;
}
// GET - Response 
message GetTokenResponse {
Token token = 1;
}
// POST - Request 
message PostTokenRequest {
string accessToken = 1;
}
// POST - Response 
message PostTokenResponse {
Token token = 1;
}

If you run protoc command, it compilies token.proto file and generates token.pb.swift file and you will see struct of Token which has all of properties in Token message you defined in .proto file. The Token conforms to SwiftProtobuf.Message protocol. It has serializeProtobuf(), serializeJSON(), init(protobuf:), init(json:) methods which allows you to serialize/deserialize value between Data and protobuf type.
 Here is an example of token.pb.swift file.

struct Token: SwiftProtobuf.Message { 
var accessToken: String = ""
    init() {} 
}
struct GetTokenResponse: SwiftProtobuf.Message { 
init() {}
    var token: Token {...} 
}
struct PostTokenRequest: SwiftProtobuf.Message { 
var accessToken: String = ""
    init() {} 
}
struct PostTokenResponse: SwiftProtobuf.Message { 
init() {}
    var token: Token {...} 
}

Also there are HTTP request and response type for API call along with Token sturct. You can simply serialize PostTokenRequest type by serializeProtobuf() method and set it to request body or deserialize GetTokenResponse or PostTokenResponse by init(protobuf:) method. Every request or response has their type. It's type safety works well in Swift!

Basic Type

See more detailed info here.

Customization

You can set namespace with package specifier, set access control of struct and change property name for specific language.

Pros

Type safety

You can write type-safe implementation for API call since every single request and response has their type. You can also express state elegantly with enum. Type safety enables us to type check at compile time so that it can save some amount of time to solve unexpected issues during runtime.

Serializing and deserializing methods are already implemented in swift-protobuf since they know what binary format is, so mapping your struct between data and protobuf type works like a charm.

To call API with Protocol Buffers, you set application/protbuf to Content-Type and Accept in HTTP headers and set serialized data to HTTP body. That's it! It's much simpler with Protocol Buffers. You don't need object mapping library or network layer library any more.

Unified schema of structured data

The only definition you’ll need is .proto file. It generates same output for each language, so it won't happen that iOS and Android unexpectedly has type mismatch of property. There also won't be any confusion from undocumented information of schema.

If you use protoc-gen-doc plugin, you can generate HTML or Markdown documentations from your comments in .proto file.

Faster API call

Binary data size gets smaller with awesome binary format mechanism so that API request and response gets faster. Encoding and Decoding are also lightweight compare to JSON.

Please see more detailed info here.

Versioning

Versioning handles backward compatibility well. If you add new field in server side, it will be treated as optional value in client side. However, if you change type of existing field or numbered field, it’ll crash your app. Make sure that you should be careful about them.

Cons

Human readability of raw data

Binary data is not key value like JSON, so it’s hard to understand at a glance. For that case, you can print log as String by using String.init(data:encoding:), or accept JSON instead for debug usage only.

Stability

swift-protobuf is still pre-released version, so it has breaking changes a lot. Make sure that you watch some activities on their repository or you can contribute to swift-protobuf since it’s open source.

Compatibility with plugin’s Swift

It’s likely to happen compile errors because of version conflict of Swift between your app and swift-protobuf plugin. It’s good to know during compile at least, but you have to choose appropriate version of plugin in terms of Swift usage. This issue is not specific one for swift-protobuf. It’s more like a general issue for auto-generate tool, but you should pay extra attention.

Usage in web browser

Javascript seems like that it’s not good at handling binary data, so it might be better to use JSON for some case.

Summary

I guess it’s too early to evaluate swift-protobuf yet, but I believe that it has so many benefits and potentials, especially type safety. You can prevent any future mistakes at compile time, not runtime. In my opinion, It would be best fit to Swift. It’s definitely worth a try!

If you’re interested in more details, take a look at sample app here as well.

References

https://developers.google.com/protocol-buffers/
 https://github.com/apple/swift-protobuf
 https://github.com/google/protobuf
 http://www.grpc.io/docs/
 https://github.com/estan/protoc-gen-doc