Fluent is actually damaged
The extra I take advantage of the Fluent ORM framework the extra I understand how laborious it’s to work with it. I am speaking a few explicit design difficulty that I additionally talked about within the future of server side Swift article. I actually don’t love the concept of property wrappers and summary database fashions.
What’s the issue with the present database mannequin abstraction? To begin with, the elective ID property is complicated. For instance you do not have to offer an identifier whenever you insert a file, it may be an nil
worth and the ORM system can create a novel identifier (underneath the hood utilizing a generator) for you. So why do we now have an id for create operations in any respect? Sure, you may say that it’s attainable to specify a customized identifier, however actually what number of occasions do we’d like that? If you wish to determine a file that is going to be one thing like a key, not an id subject. 🙃
Additionally this elective property may cause another points, when utilizing fluent you may require an id, which is a throwing operation, alternatively you may unwrap the elective property in case you’re positive that the identifier already exists, however this isn’t a protected strategy in any respect.
My different difficulty is expounded to initializers, in case you outline a customized mannequin you at all times have to offer an empty init()
methodology for it, in any other case the compiler will complain, as a result of fashions need to be courses. BUT WHY? IMHO the explanation pertains to this difficulty: you may question the database fashions utilizing the mannequin itself. So the mannequin acts like a repository that you need to use to question the fields, and it additionally represents the the file itself. Is not this towards the clear ideas? 🤔
Okay, one final thing. Property wrappers, subject keys and migrations. The core members at Vapor instructed us that this strategy will present a protected technique to question my fashions and I can ensure that subject keys will not be tousled, however I am really combating versioning on this case. I needed to introduce a v1, v2, vN construction each for the sphere keys and the migration, which really feels a bit worse than utilizing uncooked strings. It’s over-complicated for positive, and it feels just like the schema definition is blended up with the precise question mechanism and the mannequin layer as effectively.
Sorry of us, I actually recognize the trouble that you have put into Fluent, however these points are actual and I do know which you could repair them on the long run and make the developer expertise quite a bit higher.
How one can make Fluent a bit higher?
On the brief time period I am making an attempt to repair these points and happily there’s a good strategy to separate the question mechanism from the mannequin layer. It’s referred to as the repository sample and I might like to provide an enormous credit score to 0xTim once more, as a result of he made a cool reply on StackOverlow about this matter.
Anyway, the primary concept is that you simply wrap the Request
object right into a customized repository, it is normally a struct, then you definitely solely name database associated queries inside this particular object. If we check out on the default venture template (you may generate one through the use of the vapor toolbox), we are able to simply create a brand new repository for the Todo fashions.
import Vapor
import Fluent
struct TodoRepository
var req: Request
init(req: Request)
self.req = req
func question() -> QueryBuilder<Todo>
Todo.question(on: req.db)
func question(_ id: Todo.IDValue) -> QueryBuilder<Todo>
question().filter(.$id == id)
func question(_ ids: [Todo.IDValue]) -> QueryBuilder<Todo>
question().filter(.$id ~~ ids)
func listing() async throws -> [Todo]
strive await question().all()
func get(_ id: Todo.IDValue) async throws -> Todo?
strive await get([id]).first
func get(_ ids: [Todo.IDValue]) async throws -> [Todo]
strive await question(ids).all()
func create(_ mannequin: Todo) async throws -> Todo
strive await mannequin.create(on: req.db)
return mannequin
func replace(_ mannequin: Todo) async throws -> Todo
strive await mannequin.replace(on: req.db)
return mannequin
func delete(_ id: Todo.IDValue) async throws
strive await delete([id])
func delete(_ ids: [Todo.IDValue]) async throws
strive await question(ids).delete()
That is how we’re can manipulate Todo fashions, any more you do not have to make use of the static strategies on the mannequin itself, however you need to use an occasion of the repository to change your database rows. The repository will be hooked as much as the Request object through the use of a standard sample. The most straightforward means is to return a service each time you want it.
import Vapor
extension Request
var todo: TodoRepository
.init(req: self)
After all this can be a very fundamental resolution and it pollutes the namespace underneath the Request object, I imply, when you’ve got a number of repositories this generally is a downside, however first let me present you learn how to refactor the controller through the use of this easy methodology. 🤓
import Vapor
struct TodoController: RouteCollection
func boot(routes: RoutesBuilder) throws
let todos = routes.grouped("todos")
todos.get(use: index)
todos.put up(use: create)
todos.group(":todoID") todo in
todo.delete(use: delete)
func index(req: Request) async throws -> [Todo]
strive await req.todo.listing()
func create(req: Request) async throws -> Todo
let todo = strive req.content material.decode(Todo.self)
return strive await req.todo.create(todo)
func delete(req: Request) async throws -> HTTPStatus
guard let id = req.parameters.get("todoID", as: Todo.IDValue.self) else
throw Abort(.notFound)
strive await req.todo.delete(id)
return .okay
As you may see this fashion we had been capable of eradicate the Fluent dependency from the controller, and we are able to merely name the suitable methodology utilizing the repository occasion. Nonetheless if you wish to unit check the controller it isn’t attainable to mock the repository, so we now have to determine one thing about that difficulty. First we’d like some new protocols.
public protocol Repository
init(_ req: Request)
public protocol TodoRepository: Repository
func question() -> QueryBuilder<Todo>
func question(_ id: Todo.IDValue) -> QueryBuilder<Todo>
func question(_ ids: [Todo.IDValue]) -> QueryBuilder<Todo>
func listing() async throws -> [Todo]
func get(_ ids: [Todo.IDValue]) async throws -> [Todo]
func get(_ id: Todo.IDValue) async throws -> Todo?
func create(_ mannequin: Todo) async throws -> Todo
func replace(_ mannequin: Todo) async throws -> Todo
func delete(_ ids: [Todo.IDValue]) async throws
func delete(_ id: Todo.IDValue) async throws
Subsequent we will outline a shared repository registry utilizing the Software
extension. This registry will enable us to register repositories for given identifiers, we’ll use the RepositoryId
struct for this goal. The RepositoryRegistry
will be capable to return a manufacturing facility occasion with a reference to the required request and registry service, this fashion we’re going to have the ability to create an precise Repository
based mostly on the identifier. After all this entire ceremony will be averted, however I wished to provide you with a generic resolution to retailer repositories underneath the req.repository
namespace. 😅
public struct RepositoryId: Hashable, Codable
public let string: String
public init(_ string: String)
self.string = string
public last class RepositoryRegistry
personal let app: Software
personal var builders: [RepositoryId: ((Request) -> Repository)]
fileprivate init(_ app: Software)
self.app = app
self.builders = [:]
fileprivate func builder(_ req: Request) -> RepositoryFactory
.init(req, self)
fileprivate func make(_ id: RepositoryId, _ req: Request) -> Repository
guard let builder = builders[id] else
fatalError("Repository for id `(id.string)` shouldn't be configured.")
return builder(req)
public func register(_ id: RepositoryId, _ builder: @escaping (Request) -> Repository)
builders[id] = builder
public struct RepositoryFactory
personal var registry: RepositoryRegistry
personal var req: Request
fileprivate init(_ req: Request, _ registry: RepositoryRegistry)
self.req = req
self.registry = registry
public func make(_ id: RepositoryId) -> Repository
registry.make(id, req)
public extension Software
personal struct Key: StorageKey
typealias Worth = RepositoryRegistry
var repositories: RepositoryRegistry
if storage[Key.self] == nil
storage[Key.self] = .init(self)
return storage[Key.self]!
public extension Request
var repositories: RepositoryFactory
utility.repositories.builder(self)
As a developer you simply need to provide you with a brand new distinctive identifier and prolong the RepositoryFactory along with your getter to your personal repository sort.
public extension RepositoryId
static let todo = RepositoryId("todo")
public extension RepositoryFactory
var todo: TodoRepository
guard let outcome = make(.todo) as? TodoRepository else
fatalError("Todo repository shouldn't be configured")
return outcome
We will now register the FluentTodoRepository
object, we simply need to rename the unique TodoRepository struct and conform to the protocol as a substitute.
public struct FluentTodoRepository: TodoRepository
var req: Request
public init(_ req: Request)
self.req = req
func question() -> QueryBuilder<Todo>
Todo.question(on: req.db)
app.repositories.register(.todo) req in
FluentTodoRepository(req)
We’re going to have the ability to get the repository by way of the req.repositories.todo
property. You do not have to vary the rest contained in the controller file.
import Vapor
struct TodoController: RouteCollection
func boot(routes: RoutesBuilder) throws
let todos = routes.grouped("todos")
todos.get(use: index)
todos.put up(use: create)
todos.group(":todoID") todo in
todo.delete(use: delete)
func index(req: Request) async throws -> [Todo]
strive await req.repositories.todo.listing()
func create(req: Request) async throws -> Todo
let todo = strive req.content material.decode(Todo.self)
return strive await req.repositories.todo.create(todo)
func delete(req: Request) async throws -> HTTPStatus
guard let id = req.parameters.get("todoID", as: Todo.IDValue.self) else
throw Abort(.notFound)
strive await req.repositories.todo.delete(id)
return .okay
The very best a part of this strategy is which you could merely exchange the FluentTodoRepository
with a MockTodoRepository
for testing functions. I additionally like the truth that we do not pollute the req.*
namespace, however each single repository has its personal variable underneath the repositories key.
You’ll be able to provide you with a generic DatabaseRepository
protocol with an related database Mannequin sort, then you can implement some fundamental options as a protocol extension for the Fluent fashions. I am utilizing this strategy and I am fairly pleased with it to date, what do you assume? Ought to the Vapor core crew add higher help for repositories? Let me know on Twitter. ☺️