Vapor v4.0.0-beta.1 Release Notes

Release Date: 2019-10-24 // over 4 years ago
    • Application is now a global container. (#2079)

    ๐Ÿ‘€ See below for a more in-depth explanation of this change, but these code examples explain it best:

    Vapor 4 alpha:

    let app = Application { s in s.register(Foo.self) { ...} }defer { app.shutdown() }let container = try app.makeContainer().wait()defer { container.shutdown() }let foo = try container.make(Foo.self)
    

    Vapor 4 beta:

    let app = Application()defer { app.shutdown() } app.register(Foo.self) { ... }let foo = app.make(Foo.self)
    

    ๐Ÿ”€ In Vapor 3 and Vapor 4 alpha, Vapor's service architecture enforced a 1:1 relation between containers and event loops. This is a useful pattern for minimizing the amount of effort that needs to be spent on synchronizing concurrent access to services.

    ๐Ÿš€ However, as Swift on the server continues to evolve it is becoming evident that this pattern may hinder Vapor's ability to take full advantage of packages designed to be thread-safe. For example, the swift-server/async-http-client package which recently saw a 1.0.0 release. This HTTP client supports being used across many event loops on a given event loop group. To avoid inefficiencies caused by hopping between event loops, the API allows users to specify event loop preferences with varying degrees of strictness. By giving the API consumer the ability to communicate exactly what they need, the HTTP client can choose the most performant option. This decision may change depending on the current state of its internal connection pool and other parameters.

    For example, imagine the following scenario: Request A comes into your application on event loop 1 resulting in an external API request to foo.com. Request A completes and the HTTP client stores event loop 1's connection to foo.com into its pool. Request B comes into your application on event loop 2 and also wants to make an external API request to foo.com. In this situation, two things could happen:

    1: Request B could make a new connection to foo.com on event loop 2 at the cost of TCP handshake.
    2: Request B could re-use event loop 1's connection to foo.com at the cost of thread-hopping.

    In this case option 2 is the clear winner as establishing new TCP connections is much more time consuming.

    ๐Ÿ“ฆ Requiring distinct copies of each service on a per event loop basis prevents these kinds of optimizations. Exceptions could be made for things like HTTP client, but that would mean adding extra complexity to the already complex services architecture. Additionally, async-http-client was built as a model for what good server-side Swift packages should look like.

    • Application is now a RoutesBuilder. (#2079)

    ๐Ÿ— Now that services are global, Application can be a routes builder. This makes single-file Vapor applications a lot more concise and is more similar to Vapor 3.

    import Vaporlet app = try Application(environment: .detect()) app.get("hello") { req inreturn "Hello, world!"}try app.run()
    
    • Request now has access to the application. (#2079)

    Since all reference type, singleton services are now expected to be thread-safe, Request can safely use the Application to create services as needed. Where this is especially useful is in creating request-specific contexts into services. A good example of this is how Database works in Fluent 4 beta.

    app.get("todos") { req inreturn Todo.query(on: req.db).all() }
    

    This is powered by the following extension to Request:

    extension Request { var db: Database { return self.application.make(Database.self).with(req) } }
    

    The key here is that Database is passed a reference to the current Request for context. This enables database operations to do things like:

    • Delegate callbacks to the request event loop
    • ๐ŸŒฒ Log query information and errors to the request's logger
    • Report metrics information on a per-request basis

    ๐Ÿš‘ Having explicit access to the context provided by request may be critical in production use cases. (See http://asim-malik.com/the-perils-of-node-continuation-local-storage/)

    • Service creation methods no longer throw. (#2079)

    ๐Ÿ”ง Errors thrown during service creation indicate a configuration failure. These errors should only happen during development-time and to make them easier to track down, they will now result in fatalError. This also makes it easier to use providers to extend Application in ways that feel native.

    For example, this is how Application now conforms to RoutesBuilder:

    extension Application: RoutesBuilder { public var routes: Routes { self.make() } public func add(route: Route) { self.routes.add(route: route) } }
    
    • ๐ŸŽ macOS 10.14+ and Linux are now the only officially supported platforms. (#2067, #2070)
    • โœ… Multipart parsing and serialization was broken out into vapor/multipart-kit (#2080)
    • โœ… WebSocket client module was broken out into vapor/websocket-kit (#2074)
    • UUID is now LosslessStringConvertible.
    • XCTVapor is now exported as a product.
    • Vapor repos are moving to GitHub actions for CI. (#2072)
    • ๐Ÿ‘ HTTP body stream strategy .collect now supports an optional max size. (#2076)