Thursday, October 29, 2026
HomeiOS DevelopmentThe repository sample for Vapor 4

The repository sample for Vapor 4

[ad_1]

Fluent is actually damaged


The extra I exploit the Fluent ORM framework the extra I notice how exhausting it’s to work with it. I am speaking a few specific design subject that I additionally talked about within the way forward for server aspect 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 start with, the elective ID property is complicated. For instance you do not have to offer an identifier while you insert a report, it may be an nil worth and the ORM system can create a singular identifier (underneath the hood utilizing a generator) for you. So why do we have now an id for create operations in any respect? Sure, you may say that it’s potential to specify a customized identifier, however truthfully what number of occasions do we’d like that? If you wish to establish a report 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 for those who’re positive that the identifier already exists, however this isn’t a secure strategy in any respect.


My different subject is expounded to initializers, for those who outline a customized mannequin you at all times have to offer an empty init() {} technique for it, in any other case the compiler will complain, as a result of fashions need to be courses. BUT WHY? IMHO the rationale pertains to this subject: you may question the database fashions utilizing the mannequin itself. So the mannequin acts like a repository that you should use to question the fields, and it additionally represents the the report itself. Is not this in opposition to the clear rules? 🤔


Okay, one very last thing. Property wrappers, subject keys and migrations. The core members at Vapor informed us that this strategy will present a secure method to question my fashions and I can make certain that subject keys will not be tousled, however I am really battling versioning on this case. I needed to introduce a v1, v2, vN construction each for the sector 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 nicely.


Sorry people, I actually respect the hassle that you’ve got put into Fluent, however these points are actual and I do know you could repair them on the long run and make the developer expertise so much higher.


Easy methods to 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 known 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 subject.


Anyway, the primary thought is that you simply wrap the Request object right into a customized repository, it is normally a struct, you then solely name database associated queries inside this particular object. If we check out on the default challenge 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 checklist() async throws -> [Todo] {
        attempt await question().all()
    }
    
    
    func get(_ id: Todo.IDValue) async throws -> Todo? {
        attempt await get([id]).first
    }

    
    func get(_ ids: [Todo.IDValue]) async throws -> [Todo] {
        attempt await question(ids).all()
    }

    
    func create(_ mannequin: Todo) async throws -> Todo {
        attempt await mannequin.create(on: req.db)
        return mannequin
    }
    
    
    func replace(_ mannequin: Todo) async throws -> Todo {
        attempt await mannequin.replace(on: req.db)
        return mannequin
    }

    
    func delete(_ id: Todo.IDValue) async throws {
        attempt await delete([id])
    }

    
    func delete(_ ids: [Todo.IDValue]) async throws {
        attempt await question(ids).delete()
    }
}


That is how we’re can manipulate Todo fashions, to any extent further you do not have to make use of the static strategies on the mannequin itself, however you should use an occasion of the repository to change your database rows. The repository may be hooked as much as the Request object through the use of a typical sample. The most straightforward manner is to return a service each time you want it.


import Vapor

extension Request {
    
    var todo: TodoRepository {
        .init(req: self)
    }
}


After all it is a very fundamental resolution and it pollutes the namespace underneath the Request object, I imply, you probably have plenty of repositories this could be a drawback, however first let me present you methods to refactor the controller through the use of this straightforward technique. 🤓


import Vapor

struct TodoController: RouteCollection {

    func boot(routes: RoutesBuilder) throws {
        let todos = routes.grouped("todos")
        todos.get(use: index)
        todos.submit(use: create)
        todos.group(":todoID") { todo in
            todo.delete(use: delete)
        }
    }

    func index(req: Request) async throws -> [Todo] {
        attempt await req.todo.checklist()
    }

    func create(req: Request) async throws -> Todo {
        let todo = attempt req.content material.decode(Todo.self)
        return attempt 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)
        }
        attempt await req.todo.delete(id)
        return .okay
    }
}


As you may see this fashion we had been in a position to get rid of the Fluent dependency from the controller, and we are able to merely name the suitable technique utilizing the repository occasion. Nonetheless if you wish to unit check the controller it’s not potential to mock the repository, so we have now to determine one thing about that subject. 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 checklist() 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 permit us to register repositories for given identifiers, we’ll use the RepositoryId struct for this goal. The RepositoryRegistry will have the ability to return a manufacturing unit 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 primarily based on the identifier. After all this complete ceremony may be averted, however I needed to give you 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 closing 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)` just isn't 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 give you a brand new distinctive identifier and lengthen the RepositoryFactory together with your getter on 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 just isn't 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 means of the req.repositories.todo property. You do not have to alter anything 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.submit(use: create)
        todos.group(":todoID") { todo in
            todo.delete(use: delete)
        }
    }

    func index(req: Request) async throws -> [Todo] {
        attempt await req.repositories.todo.checklist()
    }

    func create(req: Request) async throws -> Todo {
        let todo = attempt req.content material.decode(Todo.self)
        return attempt 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)
        }
        attempt await req.repositories.todo.delete(id)
        return .okay
    }
}


One of the best a part of this strategy is you could merely substitute 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 possibly can give you a generic DatabaseRepository protocol with an related database Mannequin sort, then you could possibly implement some fundamental options as a protocol extension for the Fluent fashions. I am utilizing this strategy and I am fairly pleased with it thus far, what do you assume? Ought to the Vapor core group add higher help for repositories? Let me know on Twitter. ☺️




[ad_2]

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments