Week 4

New Features

Services Created With Reference Types Are Always Singletons

In previous betas of Vapor 3, the service framework supported an isSingleton property for services which controlled their behavior when requested from different containers and/or from different requesting interfaces (the for: parameter of Container.make()). Singletons would in theory have only the single instance across all invocations. However, this only held true for services whose underlying implementations were classes; struct-based services were never singletons even when the flag was specified. In addition, the flag offered a manual control for a behavior already baked into Swift via the difference between value and reference types. Therefore, the singleton flag on service types was removed entirely. Now, to get the “singleton” behavior, use a class type to implement your service. struct-based services now receive the previously default “instanced” behavior (a new instance of the service per request).

TL;DR: The redundant singleton flag on services was removed, and the behavior when requesting a given service more than once now corresponds to Swift’s standard value vs. reference semantics.

vapor/service#14

SQLite Models Are More Convenient

Neither typealias Database = ... nor static let idKey = ... are required any longer. This is true for both struct- and class-based models.

struct Foo: SQLiteModel {
    var id: Int?
    var name: String
}

final class Bar: SQLiteModel {
    var id: Int?
    var name: String
}

vapor/fluent#380

Debuggable

Debuggable now conforms to Foundation’s LocalizedError protocol, significantly improving debuggability across the board, especially including much improved log messages when an error is thrown from a unit test.

vapor/core#92

Custom URLSessions for FoundationClient

It is now possible to provide a pre-configured URLSession when using FoundationClient. This especially allows proxying via HTTPS, along with other parameters configurable via URLSessionConfiguration. (Vapor 2)

vapor/engine#237

Client Redirection

EngineClient will now properly follow redirects, as indicated by various HTTP status codes in the 3xx series. Redirection can be configured by invoking EngineClient’s respond(to:maxRedirects:) method with the desired maxium redirection count (including zero).

vapor/vapor#1480

Under The Hood

Lazy Middleware

router.grouped(DateMiddleware.self).get("datetest") { req in
    return HTTPStatus.ok
}

In the above snippet, DateMiddleware won’t be initialized until the first time that route is hit. It will subsequently be initialized for each event loop, until all event loops have one cached.

vapor/vapor#1507

Help Wanted

Leaf Syntax Highlighting For InteliJ

Any new framework is nothing without good tools, and Vapor is no different. While Swift syntax highlighting is fairly easy to come by, Leaf is not.

vapor/leaf#76

Community Contributions

Fix an Xcode Crash In API Template

Anyone who’s used Vapor knows that Xcode tends to experience problems during Vapor development that you won’t see when using Xcode only for iOS development. For example, if an error is raised to the top level in iOS, it gets caught by AppDelegate. However, Vapor apps don’t have an AppDelegate equivalent, so instead, errors raised to to the top level crash Xcode (please file bug reports with Apple when you see this happen!). This tends to trip up newer users, so in their joint PR, @twof and @0xTim fixed Vapor 3’s API template to catch top level errors, preventing these crashes.

vapor/api-template#45

MySQL Fixes To More Closely Conform To Client-Server Protocol

@SandorDobi noticed a few places where Vapor’s MySQL package wasn’t quite conforming to MySQL’s protocols and fixed them.

vapor/mysql#127

Dependency Updates

Same as last week. Tracking which dependencies need updates is hard!

vapor/auth#24

Updated RoutingGroups For Better Handling of Middleware

After some discussion, @MaximBazarov made updates to Vapor’s routing groups.

let users = router.group("user").using(SomeMiddleware())
users.get("auth", use: userAuthHandler)

vapor/vapor#1388

Use Cached Connection For Parameter Lookup

Fluent makes it easy to fetch a Model from its ID in a routing parameter (e.g. GET /photos/87). Just conform the Model to Parameter, and it can be used in the route and retrieved from the request:

final class PhotoController: RouteCollection {
    func boot(router: Router) throws {
        router.get("photos", Photo.parameter, use: getPhoto)
    }

    func getPhoto(_ req: Request) throws -> Future<Photo> {
        return try req.parameter(Photo.self)
    }
}

This PR (vapor/fluent#385) makes that lookup slightly more efficient by using an existing cached connection, or creating a new pooled connection.

vapor/fluent#385

Built For Vapor

This week we saw quite a few libraries spring up to ease Vapor 3 development. It’s super exciting to see people building off the basic types that Vapor provides, and I can’t wait to see what else people come up with!

Terse

Anyone who’s done any asynchronous programming in the past 20 years knows all too much about callback hell. While Vapor 3 attempts to limit nested callbacks by encouraging the use of Promise chains, sometimes you just can’t get around it. Terse, created by @John-Connolly attempts to further alleviate callback hell by introducing functional programming-inspired custom infix operators.

Syntax example:

func get(with client: RedisClient, for data: RedisData) -> Future<RedisData> {
        return client.command("GET", [data])
}

private func terse(_ req: Request) throws -> Future<RedisResponse> {
        let client = try req.make(RedisClient.self)
        return get(with: client, for: "KEY")
            >>-  curry(get)(client)
            >>-  curry(get)(client)
            >>-  curry(get)(client)
            <^> RedisResponse.init
}

https://github.com/John-Connolly/terse

Awesome Vapor

Speaking of things built for Vapor, this next entry should make you happy if you ever needed to complete a task and wondered for a while whether there wasn’t an existing library that would help you reach your goal more easily. Well, wonder no more! The Awesome Vapor document aims to compile everything awesome that works out of the box with Vapor. Besides libraries, the catalogue also contains useful resources for learning more about Vapor and lists well-done open-source projects that might inspire you.

As is the case with all existing “awesome-” listings for other software projects, Awesome Vapor focuses on quality of its entries, not quantity, so if you decide to follow the list’s recommendation, you know you made the right choice.

https://github.com/Cellane/awesome-vapor

Vapor-Community Extensions

The community has been coming up with all sorts of useful extensions to core Vapor types, but not all of them belong in Vapor itself for one reason or another. @gperdomor has opened up extensions repos for a few packages to fill this void. By adding these to your project, you’ll get access to a slew of helper methods! If you’re looking to contribute, these are a good opportunity.

https://github.com/vapor-community/vapor-extensions
https://github.com/vapor-community/engine-extensions
https://github.com/vapor-community/async-extensions
https://github.com/vapor-community/fluent-extensions

Microtutorial

Standard promise chains are perfectly fine if only one Future needs to complete before you can move onto the next step, but what if you need the results from two Futures? One of the problems run into here is that you often need to start embedding promise callbacks and all of a sudden you’re back to callback hell.

Let’s say you have a route that creates a User. That user has some metadata that is connected to it, but you store it in a different database table to normalize your database. You need to create a Pivot so you can get the user’s metadata. Because a pivot is based on the models ID’s, both the models have to be saved in the database first. To make sure the models are saved before you create the pivot, you can use Async’s global flatMap method, which takes in a return type, and two or three futures. There is then a completion handler that passes in the results of the futures where you can create your pivot:

let user = User(name: "Jamie", age: 30)
let metadata = Metadata(dateCreated: Date())

flatMap(to: User.self, user.save(on: request), metadata.save(on: request)) { user, metadata in
    return UserMetadata(user, metadata).save(on: request).transform(to: user)
}

Credits:

@twof
@Cellane
@bensyverson
@gwynne
@calebkleveter