Exploring Server-side Swift
When Apple open-sourced Swift, many people were quite excited to use this interesting new language on other platforms. Early on, Swift was ported to Linux and people began to look into building servers using Swift. More recently, IBM's existing partnership with Apple deepened as they took a prominent role in the new Server APIs Project. As an iOS developer at ustwo, I decided to take server-side Swift out for a spin on Linux.
To explore both writing Swift on Linux and the Swift Package Manager, I decided to create a little sample called Mockingbird. Mockingbird is the beginnings of a mock server that takes a Swagger specification and stubs out the various endpoints defined.
While I chose to use IBM's Kitura framework due to their relationship with Apple and because they provide a cloud platform-as-a-service themselves, there are many alternatives that are worth considering (Perfect, Vapor and Zewo just to name a few).
Why Write a Server with Swift?
Beyond simple curiosity or strong preferences for the Swift language, why might you want to build a server with Swift? There are a few reasons that come to mind, but ultimately you need to decide whether they make sense in your context.
Writing your server in Swift in addition to an iOS or macOS app (and maybe Android and Windows in the future!) allows the two to share code and frameworks. Having to only write the code once saves time both in the short-term as well as the long-term when looking at maintenance. It also reduces the overhead in terms of testing. All of this is particularly appealing to an indie developer, but can be beneficial to larger enterprises as well.
Swift, as with any language, is opinionated and is designed to solve certain problems in a particular way. For example, Swift favours static typing. Swift also prefers short, expressive code over verbosity. Not that Swift is better than other languages for these things, quite the contrary. Each language is a tool and has its place. But if these things appeal to you, you may be interested in using Swift. Also, if you plan to run on iOS, tvOS, or watchOS your choices of languages that have first-party support and frameworks are limited. Apple's focus lately has definitely been on Swift over Objective-C or other languages.
Building a Linux Server with Swift
Building Mockingbird using Kitura and the various related packages that IBM has developed made it extremely easy to start-up and tie together basic logic to a HTTP server. The bulk of the code written was for file management and parsing. Little code was required to build the server itself.
The biggest challenges I ran into developing this mini-server fell into two categories. First, Apple maintains two versions of their Foundation framework, which is one of the key frameworks most Apple developers use. The Foundation framework on Darwin (i.e. macOS, iOS, etc.) and the Foundation framework on Linux are different implementations. In other words, these implementations do not always produce the same output when you provide them the same input, you get different results. These implementations are also not entirely API compatible. Secondly, Kitura leaves a lot to be desired in terms of testability.
There were a number of occasions where I would have the package building perfectly on macOS, only to find that when I fired up my Docker container it blew up running the tests or starting the server. Scattered throughout the code you will see snippets where I had to provide conditional compilation blocks such as:
#if os(Linux)let regexObj = try? RegularExpression(pattern: Endpoint.kituraPathRegexString, options: )#elselet regexObj = try? NSRegularExpression(pattern: Endpoint.kituraPathRegexString)#endif
While I did not encounter it often in my small implementation, Apple's swift-corelibs-foundation (the open-sourced version of Foundation) still has many parts not yet implemented (do a search for
NSUnimplemented() in the repository). For anyone using Swift on Linux, I strongly recommend that you star this repository as you will likely need to reference what has been implemented and what may have been implemented differently on Linux versus macOS.
It was also non-trivial writing tests for my Kitura implementation. Walking the API endpoints defined by the server was not possible, due to the internal scoping of the
elements property of the router and not having a publicly accessible iterator for them either (even read-only). Nor does Kitura provide a testing framework to remove the boilerplate that is common to all endpoint tests (see ProcedureKit for a good example of how to do this). Thus, I wrote my own simplified version in
EndpointTests.swift to make testing easier.
Using the Swift Package Manager
The Swift Package Manager (SwiftPM) is Apple's new, cross-platform dependency manager. Unlike Swift itself which has clear release goals and (some) stability within a given version, the SwiftPM is still in early development and changes rapidly. It also does not support iOS, watchOS, nor tvOS at this time. So don't drop your use of CocoaPods or Carthage just yet. It also comes with a number of constraints/limitations when dealing with Xcode. Writing your SwiftPM
Package.swift file is easy enough and uses a JSON-like syntax. It tries to do intelligent things about matching your tests with the appropriate source files, linking to common Apple frameworks such as Foundation or Dispatch, and can generate an Xcode project file for you. Unfortunately, that is about all it does at this point (though to be fair, as I said it's still early days). I ran into three major issues while using the SwiftPM, even in this small example.
SR-2866: The SwiftPM currently does not have a way to specify resources (such as assets, test files, etc.) to be included in the package. Mockingbird works around this by adding a
COPYcommand in the
Dockerfileand providing an absolute path to the resources in the code. To provide better support in Xcode, part of the
xcodeproj_after_install.rbscript adds the resources to a Copy Files Build Script Phase for the
ResourceKithas three ways for generating the appropriate file url for a given resource – absolute path using Linux (Docker) file layout when building on Linux, absolute path using the macOS file layout when using the Swift compiler on a Mac (i.e.
swift test), or a resource bundle when using Xcode on a Mac. Not having resource bundling would also prevent some of ustwo's open source libraries, such as FormValidator-Swift, from supporting SwiftPM at this time.
SR-3033: The SwiftPM currently cannot test an executable package (i.e. one with a
main.swiftfile). Mockingbird works around this by placing as much code as possible within a library (
MockServerKit) and only a minimal
main.swiftfile in the executable (
SR-3583: The SwiftPM creates a
.buildfolder when building. No distinction is made for the operating system when building. So if the
.buildfolder is copied from a macOS build to a Linux server and run, it may or may not compile or test correctly. This GitHub PR seeks to either warn the user or place the build artifacts inside a top-level folder specifying the operating system. Mockingbird works around this by adding the
.buildfolder to the
.dockerignorefile and doing a clean build on the Linux server. (For more history on this issue, see GitHub PR #807).
Another quirk of SwiftPM is that all top-level folders of the
Tests directories define modules. It was a bit disconcerting at first to have my namespaces defined by my folder structure rather than by a configuration file or a declaration within the source files. While this is not a bad thing nor a challenge, it was different than what I have experienced developing for Apple's platforms in the past.
Testing and, in particular, continuous integration was also a challenge. Due to the challenges outlined and the distinctions of the frameworks on the various platforms, setting up the Travis CI support took a bit of additional effort. Many thanks to the SwiftLint project on whose implementation Mockingbird's is based.
Overall, I really enjoyed spending time with Swift on Linux and taking the SwiftPM out for a test drive. I feel that Swift and the various frameworks that support our development are starting to mature to the point where you could comfortably build servers as an indie developer. There will still be growing pains, but it may be more accessible to you than having to learn another language and all of its standard libraries to get your server up and running.
Further Reading and Viewing
Below are additional talks and blog posts about building a Linux server with Swift. Each of us have encountered different challenges, so they are worth a read.
Jeff Bergier's Building a Production Server Swift App: Lessons Learned: This is a great little talk about Bergier's experience building a Swift server. There are some additional examples of the differences between Foundation on Darwin versus Linux.
WWDC 2016: Going Server-side with Swift Open Source: A WWDC talk from this year on using Swift on the server.