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 aRoutesBuilder
. (#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 theapplication
. (#2079)
Since all reference type, singleton services are now expected to be thread-safe,
Request
can safely use theApplication
to create services as needed. Where this is especially useful is in creating request-specific contexts into services. A good example of this is howDatabase
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 currentRequest
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 extendApplication
in ways that feel native.For example, this is how
Application
now conforms toRoutesBuilder
: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 nowLosslessStringConvertible
.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)