Flight Framework - Looking for feedback on a different approach to server-side Swift

I've been working on a server-side Swift framework called Flight for a while now and I think it's finally at the point where continuing to work on it by myself is probably less useful than getting other people to tear it apart.

Repo: Flight-Framework · GitHub

This came from two different angles. The first was spending four years developing with Spring and really enjoying a lot of what it had to offer (while still struggling with quite a bit as well). The second was some frustrations in developing large projects in Vapor because it lacked the niceties Spring had. So I wanted something that combined the strengths of both and tried to dump the not-so-great parts.

I know this is late now that Vapor release the beta of v5, but it's nice to know we landed on similar things. A basic controller in Flight looks like this:

@Controller
struct BlogPostController {
    @Inject var postService: PostService

    @GetRoute("/posts/:id")
    func show(_ context: RequestContext) async throws -> Post {
        guard let idText = context.pathParam("id"), let id = UUID(uuidString: idText)     else {
            throw HTTPError(.badRequest, "malformed id")
        }
        guard let post = try await postService.get(idText) else {
            throw HTTPError(.notFound, "no such post")
        }
        return post
    }
}

Pretty basic stuff, but the @Inject isn't part of a service locator. Controllers are created per request, so postService gets injected in real time ensuring the scope of anything in the request lives and dies there. This was a deliberate decision to keep DI "purity". What this gives you is compile time checking that your routes all make sense, your controllers are available, if you try to inject a service that doesn't exist you hear about it before you can build. There is also the concept of @Repository for the query layer.

You can build your own modules if you want, but everything gets to stay a Swift type.

After building this part out it did start expanding into other areas that I enjoyed from other frameworks like Elixir's Phoenix and Ecto. So there are also modules for websockets, channels, presence, configuration, scheduling, caching, and a handful of other things. For the Ecto side, Hangar was born.

Here's a multi-join query that selects into a Swift struct:

let report = try await repo.all(
    Order.query { q, order in
        let customer = q.join(Customer.self) { $0.id == order.customerID }
        let item = q.join(OrderItem.self) { $0.orderID == order.id }
        let product = q.join(Product.self) { $0.id == item.productID }
        q.where(customer.active)
        return q.select(into: OrderReport.self) {
            (id: order.id, customer: customer.name, product: product.name)
        }
    })

Hangar is a type-safe PostgreSQL query layer. It's completely usable without Flight and Flight doesn't depend on it. I've been trying to keep the ecosystem modular enough that using one library doesn't mean signing up for all of them. It even adopts a similar sandboxing paradigm for testing queries that allows for parallel testing against a real Postgres instance with transactions and rollbacks so you don't have to dip into SQLite to test your queries. Preloads, dynamic queries, Multi for branching transaction calls, and changesets are all available on without Flight.

AI

A substantial amount of the implementation has been written with coding agents.

I figure that's worth saying up front because it's true and because anyone digging through the history is going to figure it out pretty quickly anyway.

I was still responsible for designing the framework, weighing options, cutting suggestions, and trying to build projects against what was produced. The initial version of "DI" was basically a prettied up version of service location that required context resolution to check if services were around or scoped properly. This was one of the reasons I opted for per-request controller building. The DI there is actually DI and it removed a few issues around scope and dependency lifecycle management.

Hangar had its own hiccups that I corrected too, but again, just wanted to be up front about how the code hit the repo. I'm definitely not presenting "AI wrote a lot of it" as evidence that it works.

If anything, it has made me more paranoid about testing whether the implementation actually has the semantics I think it has.

What I'm looking for

At this point I'm mostly interested in finding out where the design falls apart when other people look at it.

In particular, I'd be interested in criticism around:

  • the component and dependency model
  • lifecycle and scoping
  • concurrency assumptions
  • constructing controllers per request
  • where macros are helping versus hiding too much
  • the boundary between Flight and ordinary Swift libraries
  • Channels and Presence
  • whether any of this is actually a better way to structure a server application or whether I've just rebuilt concepts I already knew from Spring
  • People to break it and/or tear it a new one

I think Vapor and Hummingbird have their place and have done amazing work.

Flight is just exploring a different set of tradeoffs. It leans much harder into application structure, compile-time wiring, and making dependencies explicit rather than pulling services out of Request/Application objects.

If anyone wants to dig through it, build something small with it, or just tell me which parts of the architecture are questionable, I'd appreciate the feedback.