diff --git a/docs/index.it.md b/docs/index.it.md new file mode 100644 index 00000000..275fffae --- /dev/null +++ b/docs/index.it.md @@ -0,0 +1,24 @@ +Benvenuti alla documentazione di Vapor! Vapor è un framework web per Swift. Esso permette di creare backend, applicazioni web, API e server HTTP in Swift. Vapor è scritto in Swift, un linguaggio moderno, potente e sicuro che fornisce diversi benefici rispetto a linguaggi lato server tradizionali. + +## Inizio + +Se è la prima volta che utilizzate Vapor, dirigetevi a [Installazione → macOS](install/macos.md) per installare Swift e Vapor. + +Una volta installato Vapor, date un'occhiata a [Inizio → Ciao, mondo](getting-started/hello-world.md) per creare la vostra prima applicazione Vapor! + +## Altre Fonti + +Elenchiamo alcuni altri ottimi posti dove trovare informazioni su Vapor. + +| nome | descrizione | link | +|----------------|--------------------------------------------------|-------------------------------------------------------------------| +| Discord Vapor | Chiacchiera con migliaia di sviluppatori Vapor. | [visita →](https://vapor.team) | +| Documentazione API | Documentazione auto-generata dai commenti nel codice. | [visita →](https://api.vapor.codes) | +| Stack Overflow | Poni e rispondi a domande con il tag `vapor`. | [visita →](https://stackoverflow.com/questions/tagged/vapor) | +| Swift Forums | Pubblica nella sezione Vapor dei forum di Swift.org. | [visita →](https://forums.swift.org/c/related-projects/vapor) | +| Codice Sorgente | Scopri come funziona Vapor sotto il cofano. | [visita →](https://github.com/vapor/vapor) | +| Issues di GitHub | Segnala bug o richiedi funzionalità su GitHub. | [visita →](https://github.com/vapor/vapor/issues) | + +## Documentazione Vecchia + +La documentazione per le versioni deprecate di Vapor che non sono più manutenute sono su [https://legacy.docs.vapor.codes/](https://legacy.docs.vapor.codes/). diff --git a/docs/upgrading.it.md b/docs/upgrading.it.md new file mode 100644 index 00000000..5747f5db --- /dev/null +++ b/docs/upgrading.it.md @@ -0,0 +1,794 @@ +# Aggiornamento a 4.0 + +Questa guida vi mostrerà come aggiornare un progetto Vapor 3.x a Vapor 4.x. La guida cercherà di coprire tutti i cambiamenti riguardanti i pacchetti Vapor e anche alcuni dei più comuni pacchetti di terze parti. Se notate qualcosa di mancante, non esitate a chiedere aiuto nella [chat del team Vapor](https://discord.gg/vapor). Anche issues e pull requests su GitHub sono ben accette. + +## Dipendenze + +Per usare Vapor 4, avrete bisogno di almeno Xcode 11.4 e macOS 10.15. + +La sezione Installazione della documentazione contiene le istruzioni per installare le dipendenze. + +## Package.swift + +Il primo passo per aggiornare a Vapor 4 è aggiornare il file delle dipendenze del pacchetto. Qui è riportato un esempio di un `Package.swift` aggiornato. Potete anche visitare il [template Package.swift aggiornato](https://github.com/vapor/template/blob/main/Package.swift). + +```diff +-// swift-tools-version:4.0 ++// swift-tools-version:5.2 + import PackageDescription + + let package = Package( + name: "api", ++ platforms: [ ++ .macOS(.v10_15), ++ ], + dependencies: [ +- .package(url: "https://github.com/vapor/fluent-postgresql.git", from: "1.0.0"), ++ .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), ++ .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"), +- .package(url: "https://github.com/vapor/jwt.git", from: "3.0.0"), ++ .package(url: "https://github.com/vapor/jwt.git", from: "4.0.0"), +- .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"), ++ .package(url: "https://github.com/vapor/vapor.git", from: "4.3.0"), + ], + targets: [ + .target(name: "App", dependencies: [ +- "FluentPostgreSQL", ++ .product(name: "Fluent", package: "fluent"), ++ .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"), +- "Vapor", ++ .product(name: "Vapor", package: "vapor"), +- "JWT", ++ .product(name: "JWT", package: "jwt"), + ]), +- .target(name: "Run", dependencies: ["App"]), +- .testTarget(name: "AppTests", dependencies: ["App"]) ++ .target(name: "Run", dependencies: [ ++ .target(name: "App"), ++ ]), ++ .testTarget(name: "AppTests", dependencies: [ ++ .target(name: "App"), ++ ]) + ] + ) +``` + +Tutti i pacchetti che sono stati aggiornati a Vapor 4 avranno la versione major incrementata di 1. + +!!! warning + L'identificatore di pre-rilascio `-rc` indica che alcuni pacchetti di Vapor 4 non sono ancora stati rilasciati ufficialmente. + +### Vecchi Pacchetti + +Alcuni dei pacchetti di Vapor 3 sono stati deprecati: + +- `vapor/auth`: Ora incluso in Vapor. +- `vapor/core`: Assorbito in diversi moduli. +- `vapor/crypto`: Ora incluso in vapor tramite SwiftCrypto +- `vapor/multipart`: Ora incluso in Vapor. +- `vapor/url-encoded-form`: Ora incluso in Vapor. +- `vapor-community/vapor-ext`: Ora incluso in Vapor. +- `vapor-community/pagination`: Ora incluso in Fluent. +- `IBM-Swift/LoggerAPI`: Sostituito da SwiftLog. + +### Dipendenza Fluent + +Ora `vapor/fluent` dev'essere aggiunto come una dipendenza separata. Tutti i pacchetti specifici per un database hanno ottenuto il suffisso `-driver` per rendere chiara la dipendenza di `vapor/fluent`. + +```diff +- .package(url: "https://github.com/vapor/fluent-postgresql.git", from: "1.0.0"), ++ .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), ++ .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"), +``` + +### Piattaforme + +Ora il manifesto del pacchetto supporta esplicitamente macOS 10.15 o successivi. Questo implica che anche i progetti dovranno specificare quali piattaforme supportano. + +```diff ++ platforms: [ ++ .macOS(.v10_15), ++ ], +``` + +Vapor potrebbe eventualmente aggiungere il supporto per piattaforme aggiuntive in futuro. I pacchetti potrebbero supportare un qualsiasi sottoinsieme di tali piattaforme finché il numero di versione è uguale o maggiore rispetto al minimo richiesto da Vapor. + +### Xcode + +Vapor 4 utilizza il supporto nativo di SPM di Xcode 11. Ciò significa che non ci sarà più bisogno di generare il file `.xcodeproj`. Per aprire un progetto Vapor 4 in Xcode, basterà aprire il file `Package.swift` tramite `vapor xcode` o `open Package.swift`, Xcode poi procederà a scaricare le dipendenze. + +Una volta aggiornato il Package.swift, potreste dover chiudere Xcode e rimuovere i seguenti file dalla directory del progetto: + +- `Package.resolved` +- `.build` +- `.swiftpm` +- `*.xcodeproj` + +Una volta che le nuove dipendenze sono state scaricate, noterete errori di compilazione, probabilmente più di qualcuno. Non vi preoccupate! Vi mostreremo come risolverli. + +## Run + +Prima di tutto bisogna aggiornare il file `main.swift` del modulo Run al nuovo formato: + +```swift +import App +import Vapor + +var env = try Environment.detect() +try LoggingSystem.bootstrap(from: &env) +let app = Application(env) +defer { app.shutdown() } +try configure(app) +try app.run() +``` + +Il file `main.swift` andrà a sostituire il file `app.swift`, quindi potete rimuovere quel file. + +## App + +Diamo un'occhiata a come aggiornare la strutura di base di App. + +### configure.swift + +Il metodo `configure` ora accetta un'istanza di `Application`. + +```diff +- public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws ++ public func configure(_ app: Application) throws +``` + +Riportiamo un esempio di un file `configure.swift` aggiornato: + +```swift +import Fluent +import FluentSQLiteDriver +import Vapor + +// Called before your application initializes. +public func configure(_ app: Application) throws { + // Serves files from `Public/` directory + // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) + // Configure SQLite database + app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite) + + // Configure migrations + app.migrations.add(CreateTodo()) + + try routes(app) +} +``` + +Cambiamenti di sintassi per cose come routing, middleware, fluent ecc. sono menzionati nelle sezioni seguenti. + +### routes.swift + +Il contenuto di `boot` può essere inserito nel metodo `configure` dal momento che ora accetta un'istanza di `Application`. + +### routes.swift + +Il metodo `routes` ora accetta un'istanza di `Application`. + +```diff +- public func routes(_ router: Router, _ container: Container) throws ++ public func routes(_ app: Application) throws +``` + +Più avanti ci saranno altre informazioni sui cambiamenti della sintassi di routing. + +## Servizi + +La API dei servizi di Vapor 4 è stata semplificata in modo da rendere molto più facile l'aggiunta di nuovi servizi. Ora i servizi sono esposti come metodi e proprietà sulle istanze di `Application` e `Request`. + +Per capire meglio questo concetto, diamo un'occhiata a qualche esempio: + +```diff +// Cambiamento della porta di default del server a 8281 +- services.register { container -> NIOServerConfig in +- return .default(port: 8281) +- } ++ app.http.server.configuration.port = 8281 +``` + +Invece che registrare un un `NIOServerConfig` ai servizi, la configurazione del server è esposta come una proprietà su `Application` e può essere modificata direttamente. + +```diff +// Registrazione del middleware CORS +let corsConfiguration = CORSMiddleware.Configuration( + allowedOrigin: .all, + allowedMethods: [.POST, .GET, .PATCH, .PUT, .DELETE, .OPTIONS] +) +let corsMiddleware = CORSMiddleware(configuration: corsConfiguration) +- var middlewares = MiddlewareConfig() // Create _empty_ middleware config +- middlewares.use(corsMiddleware) +- services.register(middlewares) ++ app.middleware.use(corsMiddleware) +``` + +Invece che registrare un `MiddlewareConfig` ai servizi, i middleware possono essere aggiunti direttamente a `Application` tramite una proprietà + +```diff +// Fare una richiesta in un route handler +- try req.make(Client.self).get("https://vapor.codes") ++ req.client.get("https://vapor.codes") +``` + +Come Application, anche Request espone servizi come proprietà e metodi. È fortemente consigliato l'uso di servizi specifici alla Request da dentro le chiusure dei route handler. + +Questo nuovo pattern va a sostituire il vecchio pattern di `Container` e `Service` e `Config` che era usato in Vapor 3. + +### Providers + +I providers sono stati rimossi in Vapor 4. I providers erano usati per registrare servizi e configurazioni ai servizi. Ora i pacchetti possono estendere direttamente `Application` e `Request` per registrare servizi e configurazioni. + +Diamo un'occhiata a come è configurato Leaf in Vapor 4. + +```diff +// Usa Leaf per renderizzare le view +- try services.register(LeafProvider()) +- config.prefer(LeafRenderer.self, for: ViewRenderer.self) ++ app.views.use(.leaf) +``` + +Per usare Leaf, basta usare `app.leaf`. + +```diff +// Disabilita il caching delle view di Leaf +- services.register { container -> LeafConfig in +- return LeafConfig(tags: ..., viewsDir: ..., shouldCache: false) +- } ++ app.leaf.cache.isEnabled = false +``` + +### Ambiente + +Si può accedere all'ambiente attuale (produzione, sviluppo ecc) tramite la proprietà `app.environment`. + +### Servizi Personalizzati + +I servizi personalizzati che implementano il protocollo `Service` ed erano registrati al container in Vapor 3 vengono ora espressi come estensioni di Application o Request. + +```diff +struct MyAPI { + let client: Client + func foo() { ... } +} +- extension MyAPI: Service { } +- services.register { container -> MyAPI in +- return try MyAPI(client: container.make()) +- } ++ extension Request { ++ var myAPI: MyAPI { ++ .init(client: self.client) ++ } ++ } +``` + +Per accedere a questo servizio non c'è più bisogno di usare `make`: + +```diff +- try req.make(MyAPI.self).foo() ++ req.myAPI.foo() +``` + +### Provider Personalizzati + +La maggior parte dei servizi può essere implementata come indicato sopra, tuttavia se si deve poter accedere al `Lifecycle` dell'applicazione si può fare così: + +```swift +struct PrintHello: LifecycleHandler { + func willBoot(_ app: Application) throws { + print("Hello!") + } +} + +app.lifecycle.use(PrintHello()) +``` + +Per salvare dati su Application, si può utilizzare il nuovo `Application.storage`: + +```swift +struct MyNumber: StorageKey { + typealias Value = Int +} +app.storage[MyNumber.self] = 5 +print(app.storage[MyNumber.self]) // 5 +``` + +L'accsso a `Application.storage` può essere avvolto in una proprietà computata per rendere il codice più leggibile: + +```swift +extension Application { + var myNumber: Int? { + get { self.storage[MyNumber.self] } + set { self.storage[MyNumber.self] = newValue } + } +} + +app.myNumber = 42 +print(app.myNumber) // 42 +``` + +## NIO + +Vapor 4 utilizza le API asincrone di SwiftNIO direttamente senza fare l'overload di metodi come `map` e `flatMap` o tipi alias come `EventLoopFuture`. Vapor 3 forniva overload ed alias per retrocompatiblità con versioni di Vapor che non usavano SwiftNIO. + +### Cambi di nome + +Il cambiamento più ovvio è che il typealias `Future` di `EventLoopFuture` è stato rimosso. Si può risolvere questo problema semplicemente usando "trova e sostituisci". + +In più NIO non supporta il label `to:` che veniva usato da Vapor 3, che comunque dato il nuovo sistema di inferenza dei tipi di Swift 5.2, non è più necessario. + +```diff +- futureA.map(to: String.self) { ... } ++ futureA.map { ... } +``` + +Metodi con il prefisso `new`, come `newPromise`, sono stati rinominati in `make` per essere più coerenti con lo standard di Swift. + +```diff +- let promise = eventLoop.newPromise(String.self) ++ let promise = eventLoop.makePromise(of: String.self) +``` + +`catchMap` non è più disponibile. Si può usare `mapError` o `flatMapErrorThrowing` al suo posto. + +Il `flatMap` globale di Vapor 3 per combinare diversi futuri non è più disponibile. Si può utilizzare `and` al suo posto. + + +```diff +- flatMap(futureA, futureB) { a, b in ++ futureA.and(futureB).flatMap { (a, b) in + // Do something with a and b. +} +``` + +### ByteBuffer + +Molti metodi e proprietà che utilizzavano `Data` ora usano `ByteBuffer`, un tipo di storage di byte più potente e performante. Potete leggere di più su `ByteBuffer` nella [documentazione di SwiftNIO](https://swiftpackageindex.com/apple/swift-nio/main/documentation/niocore/bytebuffer). + +Per convertire un `ByteBuffer` a `Data` si può usare: + +```swift +Data(buffer.readableBytesView) +``` + +### Throwing map / flatMap + +La difficoltà maggiore è che `map` e `flatMap` non possono più lanciare errori. `map` ha una versione che può lanciare errori, `flatMapThrowing`. `flatMap` non ha una versione che può lanciare errori, questo potrebbe implicare il cambiamento della struttura del vostro codice asincrono. + +Map che _non_ lanciano errori dovrebbero continuare a funzionare senza problemi. + +```swift +// Non lancia errori +futureA.map { a in + return b +} +``` + +Map che lanciano errori devono essere aggiornate a `flatMapThrowing`: + +```diff +- futureA.map { a in ++ futureA.flatMapThrowing { a in + if ... { + throw SomeError() + } else { + return b + } +} +``` + +FlatMap che _non_ lanciano errori dovrebbero continuare a funzionare senza problemi. + +```swift +// Non lancia errori +futureA.flatMap { a in + return futureB +} +``` + +Invece di lanciare errori dentro una flatMap, si può ritornare un errore futuro. Se l'errore ha origine da un altro metodo, si può utilizzare il costrutto do / catch. + +```swift +// Ritorna un errore futuro: +futureA.flatMap { a in + do { + try doSomething() + return futureB + } catch { + return eventLoop.makeFailedFuture(error) + } +} +``` + +Se l'errore è generato direttamente dentro la flatMap, si può usare `flatMapThrowing`: + +```swift +// Metodo che lancia errori riscritto con flatMapThrowing +futureA.flatMapThrowing { a in + try (a, doSomeThing()) +}.flatMap { (a, result) in + // result è il valore ritornato da doSomething() + return futureB +} +``` + +## Routing + +Ora le routes sono registrate direttamente su Application. + +```swift +app.get("hello") { req in + return "Hello, world!" +} +``` + +Ciò significa che non c'è più bisogno di registrare un router ai servizi. Basta passare l'istanza di `Application` al metodo `routes` e si può cominciare ad aggiungere endpoints. Tutti i metodi disponibili sul `RoutesBuilder` sono disponibili su `Application`. + +### Content Sincrono + +La decodifica delle richieste è ora sincrona. + +```swift +let payload = try req.content.decode(MyPayload.self) +print(payload) // MyPayload +``` + +Si può fare l'override di questo comportamento utilizzando la strategia di collezione del body `.stream`. + +```swift +app.on(.POST, "streaming", body: .stream) { req in + // Il body della richiesta è ora asincrono. + req.body.collect().map { buffer in + HTTPStatus.ok + } +} +``` + +### URL divisi da virgole + +In Vapor 4 gli URL sono divisi da virgole e non devono contenere `/`. + +```diff +- router.get("v1/users/", "posts", "/comments") { req in ++ app.get("v1", "users", "posts", "comments") { req in + // Gestisci la richiesta +} +``` + +### Parametri di una route + +Il protocollo `Parameter` è stato rimosso per promuovere l'uso di parametri chiamati esplicitamente. In questo modo si evitano problemi di parametri duplicati e il fetching non ordinato dei parametri nei middleware e nei gestori delle routes. + +```diff +- router.get("planets", String.parameter) { req in +- let id = req.parameters.next(String.self) ++ app.get("planets", ":id") { req in ++ let id = req.parameters.get("id") + return "Planet id: \(id)" + } +``` + +Si tratta dell'utilizzo dei parametri nelle routes nella sezione di Fluent. + +## Middleware + +`MiddlewareConfig` è stato rinominato in `MiddlewareConfiguration` ed è ora una proprietà di `Application`. Si possono aggiungere dei middleware all'applicazione usando `app.middleware`. + +```diff +let corsMiddleware = CORSMiddleware(configuration: ...) +- var middleware = MiddlewareConfig() +- middleware.use(corsMiddleware) ++ app.middleware.use(corsMiddleware) +- services.register(middlewares) +``` + +Ora è obbligatorio inizializzare i Middleware prima di registrarli. + +```diff +- middleware.use(ErrorMiddleware.self) ++ app.middleware.use(ErrorMiddleware.default(environment: app.environment)) +``` + +Per rimuovere tutti i middleware di default si può utilizzare: + +```swift +app.middleware = .init() +``` + +## Fluent + +Ora l'API di Fluent è indipendente dal database su cui viene utilizzata. Basta importare `Fluent`. + +```diff +- import FluentMySQL ++ import Fluent +``` + +### Models + +I modelli utilizzano il protocollo `Model` e devono essere delle classi: + +```diff +- struct Planet: MySQLModel { ++ final class Planet: Model { +``` + +Tutti i campi sono dichiarati utilizzando `@Field` o `@OptionalField`. + +```diff ++ @Field(key: "name") +var name: String + ++ @OptionalField(key: "age") +var age: Int? +``` + +L'ID di un modello dev'essere definito utilizzando `@ID`. + +```diff ++ @ID(key: "id") +var id: UUID? +``` + +I modelli che utilizzano un ID personalizzato devono utilizzare `@ID(custom:)`. + +Tutti i modelli devono avere il nome della loro tabella definito staticamente. + +```diff +final class Planet: Model { ++ static let schema = "Planet" +``` + +I metodi `save`, `update` e `create` non ritornano più l'istanza del modello. + +```diff +- model.save(on: ...) ++ model.save(on: ...).map { model } +``` + +I modelli non possono più venire utilizzati come parametri del route path. Si può utilizzare `find` e `req.parameters.get`. + +```diff +- try req.parameters.next(ServerSize.self) ++ ServerSize.find(req.parameters.get("size"), on: req.db) ++ .unwrap(or: Abort(.notFound)) +``` + +`Model.ID` è stato rinominato in `Model.IDValue`. + +I timestamp sono ora dichiarati usando `@Timestamp`. + +```diff +- static var createdAtKey: TimestampKey? = \.createdAt ++ @Timestamp(key: "createdAt", on: .create) +var createdAt: Date? +``` + +### Relazioni + +Le relazioni sono ora dichiarate usando `@Parent`, `@Children` e `@Siblings`. + +Le relazioni `@Parent` contengono il campo internamente. La chiave passata a `@Parent` è il nome del campo che contiene la chiave esterna. + +```diff +- var serverID: Int +- var server: Parent { +- parent(\.serverID) +- } ++ @Parent(key: "serverID") ++ var server: Server +``` + +Le relazioni `@Children` hanno un key path al relativo `@Parent`. + +```diff +- var apps: Children { +- children(\.serverID) +- } ++ @Children(for: \.$server) ++ var apps: [App] +``` + +Le relazioni `@Siblings` hanno dei key path al relativo ad una tabella pivot. + +```diff +- var users: Siblings { +- siblings() +- } ++ @Siblings(through: Permission.self, from: \.$user, to: \.$company) ++ var companies: [Company] +``` + +Le tabelle pivot sono modelli normali che conformano a `Model` con due proprietà `@Parent` e zero o più campi aggiuntivi. + +### Query + +Si può accedere al contesto del database utilizzando `req.db` nei route handlers. + +```diff +- Planet.query(on: req) ++ Planet.query(on: req.db) +``` + +`DatabaseConnectable` è stato rinominato in `Database`. + +Ora i key path ai campi hanno il prefisso `$` per specificare che si tratta del wrapper e non del valore del campo. + +```diff +- filter(\.foo == ...) ++ filter(\.$foo == ...) +``` + +### Migrations + +Le migrazioni devono essere scritte manualmente e non si basano più sul concetto di reflection: + +```diff +- extension Planet: Migration { } ++ struct CreatePlanet: Migration { ++ ... ++} +``` + +Ora le migrazioni sono tipate tramite stringe e non sono direttamente collegate ai modelli, utilizzano infatti il protocollo `Migration`. + +```diff +- struct CreateGalaxy: <#Database#>Migration { ++ struct CreateGalaxy: Migration { +``` + +I metodi `prepare` e `revert` non sono più statici. + +```diff +- static func prepare(on conn: <#Database#>Connection) -> Future { ++ func prepare(on database: Database) -> EventLoopFuture +``` + +La creazione di uno schema builder è fatta tramite un metodo su `Database`. + +```diff +- <#Database#>Database.create(Galaxy.self, on: conn) { builder in +- // Use builder. +- } ++ var builder = database.schema("Galaxy") ++ // Use builder. +``` + +I metodi `create`, `update` e `delete` sono metodi dello schema builder e assomigliano al funzionamento di un query builder. + +La definizione dei campi è tipata tramite stringhe e segue il seguente pattern: + +```swift +field(, , ) +``` + +```diff +- builder.field(for: \.name) ++ builder.field("name", .string, .required) +``` + +La costruzione degli schemi può essere concatenata come un query builder: + +```swift +database.schema("Galaxy") + .id() + .field("name", .string, .required) + .create() +``` + +### Configurazione di Fluent + +`DatabasesConfig` è stato sostituito da `app.databases`. + +```swift +try app.databases.use(.postgres(url: "postgres://..."), as: .psql) +``` + +`MigrationsConfig` è stato sostituito da `app.migrations`. + +```swift +app.migrations.use(CreatePlanet(), on: .psql) +``` + +### Repository + +Dal momento che è cambiato il modo in cui funzionano i servizi, anche la struttura delle Repository è cambiata. Servirà comunque un protocollo come `UserRepository`, ma invece di creare una `final class` che implementi il protocollo, basta creare una `struct`. + +```diff +- final class DatabaseUserRepository: UserRepository { ++ struct DatabaseUserRepository: UserRepository { + let database: Database + func all() -> EventLoopFuture<[User]> { + return User.query(on: database).all() + } + } +``` + +Si può rimuovere l'utilizzo di `ServiceType`, dal momento che non esiste più. + +```diff +- extension DatabaseUserRepository { +- static let serviceSupports: [Any.Type] = [Athlete.self] +- static func makeService(for worker: Container) throws -> Self { +- return .init() +- } +- } +``` + +Si può invece creare una `UserRepositoryFactory`: +```swift +struct UserRepositoryFactory { + var make: ((Request) -> UserRepository)? + mutating func use(_ make: @escaping ((Request) -> UserRepository)) { + self.make = make + } +} +``` + +Questa struct ha la responsabilità di ritornare una `UserRepository` per una `Request`. + +Il prossimo passo è quello di estendere l'`Application` con una proprietà computata che ritorna una `UserRepository`: + +```swift +extension Application { + private struct UserRepositoryKey: StorageKey { + typealias Value = UserRepositoryFactory + } + + var users: UserRepositoryFactory { + get { + self.storage[UserRepositoryKey.self] ?? .init() + } + set { + self.storage[UserRepositoryKey.self] = newValue + } + } +} +``` + +Per utilizzare l'effettiva repository all'interno di un route handler: + +```swift +extension Request { + var users: UserRepository { + self.application.users.make!(self) + } +} +``` + +L'ultimo passo è quello di specificare la factory nel metodo `configure`: + +```swift +app.users.use { req in + DatabaseUserRepository(database: req.db) +} +``` + +Si può ora accedere alla repository nei route handlers con `req.users.all()` e si può facilmente sostituire la repository con una mock per i test. Basta creare un nuovo file `TestUserRepository`: +```swift +final class TestUserRepository: UserRepository { + var users: [User] + let eventLoop: EventLoop + + init(users: [User] = [], eventLoop: EventLoop) { + self.users = users + self.eventLoop = eventLoop + } + + func all() -> EventLoopFuture<[User]> { + eventLoop.makeSuccededFuture(self.users) + } +} +``` + +Si può usare la repository in questo modo: +```swift +final class MyTests: XCTestCase { + func test() throws { + let users: [User] = [] + app.users.use { TestUserRepository(users: users, eventLoop: $0.eventLoop) } + ... + } +} +```