What I ❤️ About Server Side Swift
I should preface with, I’m a contributor Vapor, the leading server side Swift framework. However, this is an attempt to be objective about Swift on the server
I first got involved with server side Swift
just as Vapor 3 was in alpha. I was looking for a statically typed language that was just nice to use. I’d played around with iOS
before and I generally liked Swift, but my domain was definitely full stack web/api.
At the time, my main focus for several years had been a large custom reporting system built in Yii 2. But as the project had grown to be 30-40k lines, I was really missing that static guarantee and just the performance static languages usually bring.
So when I came to start a little side project and saw on Ray Wenderlich stuff about Vapor I jumped in. Spoiler alert, I do a lot of programming outside of work, to use languages, tools & approaches I might not necessarily get to use in work.
So What do I like about it
Amazing community
The Vapor community, is truly the absolute best tech community I’ve ever been a part of. There are some incredibly talented and kind people in there. You rarely see any harshness, egos, condescension etc! And if you do, it is never from any mods, contributors. Everyone is super supportive and helpful. It’s an absolute delight.
It’s a lovely and safe language
Swift is a really nice language first and foremost, it’s got a really nice blend of different paradigms and it’s very safe. Do you like functional style things like map
& filter
. Then you’re golden. Are you a rigid proponent of OOP, also fine. It’s very flexible in this regard, though there are more standard approaches to Swift style.
Multi-Paradigm
Although Swift supports many styles. It is a protocol orientated language. A thing I’ve noticed of major languages recently, Rust
, Go
, Swift
, Kotlin
. They’re not very OOP-y. That’s not to say you can’t use OOP. But it’s not the standard way. They all seem to focus on composition rather than inheritance. Which I think is a generally better approach, though I’m sure OOP is the better option for some use cases.
To this end, the Swift guide will openly say, use structs where you can.
Readability
Swift and vapor also reads very simply, somewhat like Python in many ways.
func index(req: Request) throws -> EventLoopFuture<[Todo]> {
return Todo.query(on: req.db).all()
}
Here things like Codable
help encode and decode objects very easily.
Memory Consumption
A Vapor app runs with about 7MB! Which is amazing, especially if you’re coming from something like Java
or even Node
or PHP
.
Its got massive backing
The best tech company in the world and pumping resources in to it! I don’t need to expand on that.
Enums
It’s got amazing support for enums, I was blown away with some of the matching you can do with it. Here’s a brief code snippet from my testrail-kit.
mport Foundation
public enum PlanResource: ConfigurationRepresentable {
/// Returns an existing test plan.
/// where `identifier` is either `planId` or `projectId` depending on a single or multiple object return
/// See https://www.gurock.com/testrail/docs/api/reference/plans#get_plan
case get(identifier: Int, type: RecordAction)
case add(type: AddPlanAction)
case update(type: UpdatePlanAction)
/// Closes an existing test plan and archives its test runs & results.
/// See https://www.gurock.com/testrail/docs/api/reference/plans#close_plan
case close(planId: Int)
case delete(type: DeleteAction)
public var request: RequestDetails {
switch self {
case .get(let identifier, let type):
guard case .one = type else {
return (uri: "get_plans/\(identifier)", .GET)
}
return (uri: "get_plan/\(identifier)", .GET)
case .add(.plan(let projectId)):
return (uri: "add_plan/\(projectId)", .POST)
case .add(.planEntry(let planId)):
return (uri: "add_plan_entry/\(planId)", .POST)
case .add(.runToPlanEntry(let planId, let entryId)):
return (uri: "add_run_to_plan_entry/\(planId)/\(entryId.uuidString.lowercased())", .POST)
case .update(type: .plan(let planId)):
return (uri: "update_plan/\(planId)", .POST)
case .update(type: .planEntry(let planId, let entryId)):
return (uri: "update_plan_entry/\(planId)/\(entryId.uuidString.lowercased())", .POST)
case .update(type: .runInPlanEntry(let runId)):
return (uri: "update_run_in_plan_entry/\(runId)", .POST)
case .close(let planId):
return (uri: "close_plan/\(planId)", .POST)
case .delete(.plan(let planId)):
return (uri: "delete_plan/\(planId)", .POST)
case .delete(type: .planEntry(let planId, let entryId)):
return (uri: "delete_plan_entry/\(planId)/\(entryId.uuidString.lowercased())", .POST)
case .delete(type: .runInPlanEntry(let runId)):
return (uri: "delete_run_from_plan_entry/\(runId)", .POST)
}
}
...
Strictness
Swift is a pretty strict language and I ❤️ that. Because I feel confident on my stuff not crashing in production. So conformance to protocols in Swift is explicit.
protocol Y {
do() -> Int
}
struct X {
do() -> Int {
return 5
}
}
// X does not conform to Y.
It has to explicit
struct X: Y {
do() -> Int {
return 5
}
}
// X conforms to Y
So What’s not so good about it?
Ecosystem
It’s kind of a double-edged sword, but as the ecosystem is fairly new. The kinds of things you might just expect to be able to import, very well might not exist. While this is annoying. It’s great if you want to be the one who creates it. Even greater if Apple make it, because you’re getting incredible engineers make it all whilst avoiding the problems that previous attempts in other languages/packages got wrong/sub-optimal.
Non Apple Platforms
At the time of writing this, I personally didn’t enjoy the Swift experience on Linux and Windows isn’t even an option. So if you’re working in enterprize Windows environments it will be hard to get people on board. XCode isn’t available either. So those who enjoy being able to download their favorite IDE and language on all platforms and just go are out of luck. Now, you can use VSCode, but for me I found the experience lacking. Though I’m sure this will improve over time.
Futures
Dealing with futures can be complex at times and it’s certainly not as friendly or clean as the async/await
syntax of JavaScript
, C#
, Python
etc. But this is coming to Swift! Yay!
Performance
The Swift docs say that it’s fast. But what is fast? It’s compiled, so you’d expect it to be faster than most dynamic languages. But compare it to Spring and other languages and it’s no where near as fast. You can check TechEmpower. Again, it’s what’s fast for you and comparing Apples to Apples.
Conclusion
Overall, most of the negative things will almost certainly all improve or potentially aren’t even an issue for you. But this article is meant to be as balanced as it can be. There’s certainly things that aren’t in here but maybe should be in either category. But it’s a decent starting place and an article I may revisit and tweak.