Async/await and the future of Vapor

Hi all,

Now that Swift 5.5 is released and we're getting several questions about async/await in Vapor we thought we'd put a post up with our next steps and plans for the future.

Vapor 4

When there's an Xcode release that contains a macOS SDK to allow us to test and run async/await code locally we'll merge the async PR to add it to Vapor. We don't want to do this sooner to avoid any confusion. To be clear, Vapor 4 will get async/await APIs. These will be alongside all the current APIs to allow you to slowly migrate your code as you want. We're currently working on making sure the docs, Fluent and any other APIs have async versions to make switching easy. To start with we’ll release all the high level stuff and then work over the coming months to add it to the lower level APIs and packages. Several people have already been running tests using the branches and things seem good so we're excited to see even more interest in server-side Swift!

We're also keeping an eye on tracing and logging and their interactions with task local values to make sure we can integrate them properly.

Vapor 5

The SSWG have published guidelines for migrating to Swift’s new concurrency model. We know that there will be coming breaking changes in Swift with Swift 6 to ensure safety when using concurrency and others. Along with custom executors and a potential SwiftNIO 3.0 release we'll take the opportunity to release the next major version of Vapor, Vapor 5. We don't have any timescales for this yet as a lot depends on our dependencies. However we are going to set some goals and would like input from the community on them (these are all obviously subject to change):

  • Remove all EventLoopFuture references - Vapor 5 will be async/await throughout and our use of SwiftNIO will be an implementation detail to our users.
  • Remove all exports in Vapor - we currently export a number of dependencies. This makes sense for the packages that integrate our *-Kit packages with Vapor so you only need to import Fluent for example and not FluentKit as well. This was partly necessitated by exposing EventLoopFuture types but has also caused us several headaches. So Vapor 5 itself will not export everything and our other packages will only export packages we control
  • Commit to supporting Vapor 5 for 2 years - Vapor 3 was out for 2 years, and Vapor 4 will likely see a similar timetable but we're aware that there's a perception things change a lot and we want to ensure some stability for the community to help people plan
  • Migrate to DocC - we think DocC could be interesting and assuming the open source plans pan out we'll likely use it
  • OpenAPI support - this is a personal one for me, but I'd like to see first class support to make it easy to integrate
  • Related, we're investigating declarative routing. This would make integrating OpenAPI relatively simple but the initial investigations show it introduces a big performance hit. However it could change or be offered as an option
  • @graskind will post a follow up on the plans for Fluent 5

If you have any other ideas, let us know!

The Vapor Core Team

82 Likes

This all mostly sounds great! Thanks for the writeup and clarification. One question/point on my part:

I would definitely advocate for this to be strictly opt-in/an additive feature. In my mind it's not acceptable to meaningfully degrade performance for the sake of a different declaration syntax. I'd be perfectly happy with keeping the current routing infrastructure as-is and introducing this as an optional declaration syntax on top of it (i.e. your last suggestion).

8 Likes

I've been trying out the async/await Vapor branch and it makes using the framework much easier to pick up and reason about than EventLoopFutures. Betting this will help a lot with adoption! For Vapor 5 I think switching to the new concurrency features should improve readability and debuggability of the codebase as it would be possible to get rid of some the hacks that were necessary in a pre async/await Swift world. Overall very excited about the future of Vapor, Thanks Vapor Team!

10 Likes

I’ve been following the async/await PR, super excited for that to be available. Great work!

I’ll throw a +1 here, as well - it’s a super helpful technology, and I think the ease of integration with other stacks that it offers would be a big benefit.

Thanks to the Vapor Core Team for all your hard work, it’s much appreciated!

3 Likes

First of all, thanks for all the work with Vapor 4 and the future support of Swift 5.5 concurrency.

Are there any project to make drivers for Fluent to support commercial databases like Oracle or MS SQL Server?

I'm in love with Vapor and we have many clients in production so happy with its performance and versatility.

Thanks in advance.

There have been several discussions about drivers for these but I'm not aware of any implementations. Wrapping any C drivers will work otherwise you'd need to build it yourself.

If there were NIO based drivers for MSSQL or Oracle we could wrap them for Fluent but for now we're focusing on open source DBs

Sounds interesting. Maybe I could try to implement a NIO driver. Thanks for your reply.

Hurray for all these changes. Having just started a large API server in Vapor 4 (which I am loving). The futures can be painful at first. Can't wait for async/wait and OpenAPI.

I am going to guess this is a 2022 Swift 6 release? No 4.5 with async/wait?

@scottandrew Vapor 4 will get async/await APIs soon as mentioned here

2 Likes

While my work on it has gone stale, I have made quite a bit of progress on an implementation of the TDS (Tabular Data Stream) protocol, the underlying wire protocol for SQL Server Client/Server communication. You can find my implementation here.

My recent efforts are in introducing a large refactor to match an implementation similar to that of the changes that @fabianfett had made to recently to the Postgres driver.

I’m hoping I can make meaningful progress here soon.

4 Likes

Thank you for the update! I’ve been a bit out of the loop when it comes to Vapor’s async/await adoption, but will there be any performance hits that warrant extra considerations once the branch is merged?

Thanks a lot for the update! Great news!

The performance difference is still a bit unknown at the moment and changes depending on platform etc. There will likely be a small hit until custom executors is implemented but the benefits of async/await will outweigh it for the majority of people

6 Likes

This is great news! I think the async/await APIs will make Vapor a lot simpler and more approachable for iOS devs :slight_smile:

1 Like

Please prioritize performance. Specifically it would be really good if vapor was at least demonstrably faster than node. At least in the techempower benchmarks.

We need more devs using vapor and swift and performance is key to achieving that. Good luck

7 Likes

The foremost I would ask is to remove all db related code from models. Make it (Plain Old Swift Objects) The DB ( an any other serialization) should be outside of the data models.

2 Likes

You're conflating database models and data models. By definition the database models need to know about the database properties so Fluent can write correct queries. For most use cases the database models can also be used as data models, but there are definitely use cases where it doesn't work. For example, if writing database models for a single table design in DynamoDB using different models with t[n properties of pk, sk, data1 etc isn't helpful. Or sharing data models across iOS and the server doesn't work with the Fluent property wrappers. In those cases you can define separate data models and map from the database model to a data model. Where you do the mapping is up to you, but at the API layer or database layer is a good start

I agree with what you say, but it's difficult to blame the user when the API and the documentation works very actively in order to have the user perform this confusion. Vapor examples use database models as plain data models, so users do the same. In the same vein, managed objects in Core Data look like they can be used as plain models... when they probably should not [1].

This is a consequence of design choices.

The confusion is unavoidable, because no database library would ship with unusable database models. Hence users will use database models as plain data models. It is not very nice to blame the users after they have fallen in this honey pot, so a design goal can be to make that unavoidable confusion harmless.

In GRDB, for example, database models don't have any requirement at all (no property wrappers, no base class, no mandatory initializer, etc):

struct Player {
  var id: String
  var name: String
  var score: Int
}

extension Player: Codable { }
extension Player: Identifiable { }

// 100% optional database powers
#if canImport(GRDB)
import GRDB
extension Player: FetchableRecord, PersistableRecord { }
#endif

I can understand why Vapor has chosen to encode the schema in the model definition itself - but there are other possible design choices.


  1. The Laws of Core Data | Dave DeLong by @davedelong, chapter 8: "Use an abstraction layer" ↩︎

5 Likes

It is a bad definition. C# and Java both went through this teething state and both agreed that POCO (plain old clr object) and POJO ( plain old java object) is much more elegant approach and easier to work with.
Whenever a data model (including database data model) has a serialization (or any other code for that matter) it starts being messy.
For example how would you mock such object for unit testing ? It cannot live without DB and therefore any logic in your code even very high level like api controllers are not unit testable.
You can only test it with the presence of a database, which is not a unit test. Or it is very difficult and again messy to implement database serialization uniformly among different database implementation as the model must know about db details.
It really would help to look at for example Entity framework.

As for View Models I am not talking about those. That's a completely different story,

2 Likes

This is untrue. DB models don't require a DB in order to be instantiated or exist. So mocking and unit testing them as entities is very much possible.

As for testing controllers, like you mentioned that falls in the category of integration testing. Vapor provides a whole suite of tools just for that, emulating real server conditions with the flexibility of a local environment — this includes DB operations.