mirror of https://github.com/vapor/docs.git
807 lines
25 KiB
Markdown
807 lines
25 KiB
Markdown
# Upgraden naar 4.0
|
|
|
|
Deze handleiding laat zien hoe u een bestaand Vapor 3.x project kunt opwaarderen naar 4.x. Deze handleiding probeert alle officiële pakketten van Vapor te behandelen, evenals enkele veelgebruikte leveranciers. Als u merkt dat er iets ontbreekt, dan is [Vapor's team chat](https://discord.gg/vapor) een goede plaats om om hulp te vragen. Issues en pull requests worden ook op prijs gesteld.
|
|
|
|
## Afhankelijkheden
|
|
|
|
Om Vapor 4 te gebruiken, hebt u Xcode 11.4 en macOS 10.15 of hoger nodig.
|
|
|
|
De Install sectie van de docs gaat over het installeren van afhankelijkheden.
|
|
|
|
## Package.swift
|
|
|
|
De eerste stap bij het upgraden naar Vapor 4 is het bijwerken van de afhankelijkheden van uw pakket. Hieronder staat een voorbeeld van een bijgewerkt Package.swift bestand. U kunt ook het bijgewerkte [sjabloon Package.swift](https://github.com/vapor/template/blob/master/Package.swift) bekijken.
|
|
|
|
```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-rc"),
|
|
+ .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0-rc"),
|
|
- .package(url: "https://github.com/vapor/jwt.git", from: "3.0.0"),
|
|
+ .package(url: "https://github.com/vapor/jwt.git", from: "4.0.0-rc"),
|
|
- .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"),
|
|
+ ])
|
|
]
|
|
)
|
|
```
|
|
|
|
Van alle pakketten die opgewaardeerd zijn voor Vapor 4 zal het hoofdversienummer met één verhoogd worden.
|
|
|
|
!!! warning "Waarschuwing"
|
|
De `-rc` pre-release aanduiding wordt gebruikt omdat sommige pakketten van Vapor 4 nog niet officieel zijn uitgebracht.
|
|
|
|
### Oude Pakketten
|
|
|
|
Sommige pakketten zijn misschien nog niet geüpgraded. Als u er tegenkomt, dien dan een probleem in om de auteur te laten weten.
|
|
|
|
Sommige Vapor 3 pakketten zijn afgeschreven, zoals:
|
|
|
|
- `vapor/auth`: Nu opgenomen in Vapor.
|
|
- `vapor/core`: Opgenomen in verschillende modules.
|
|
- `vapor/crypto`: Vervangen door SwiftCrypto.
|
|
- `vapor/multipart`: Nu opgenomen in Vapor.
|
|
- `vapor/url-encoded-form`: Nu opgenomen in Vapor.
|
|
- `vapor-community/vapor-ext`: Nu opgenomen in Vapor.
|
|
- `vapor-community/pagination`: Nu onderdeel van Fluent.
|
|
- `IBM-Swift/LoggerAPI`: Vervangen door SwiftLog.
|
|
|
|
### Fluent Afhankelijkheid
|
|
|
|
`vapor/fluent` moet nu als een aparte dependency worden toegevoegd aan uw dependencies lijst en targets. Alle database-specifieke pakketten zijn aangevuld met `-driver` om de eis voor `vapor/fluent` duidelijk te maken.
|
|
|
|
```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-rc"),
|
|
+ .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0-rc"),
|
|
```
|
|
|
|
### Platvormen
|
|
|
|
Vapor's package manifests ondersteunen nu expliciet macOS 10.15 en hoger. Dit betekent dat uw pakket ook platformondersteuning moet specificeren.
|
|
|
|
```diff
|
|
+ platforms: [
|
|
+ .macOS(.v10_15),
|
|
+ ],
|
|
```
|
|
|
|
Vapor kan in de toekomst extra ondersteunde platforms toevoegen. Uw pakket mag een subset van deze platformen ondersteunen zolang het versienummer gelijk is aan of hoger is dan de minimum versie-eisen van Vapor.
|
|
|
|
### Xcode
|
|
|
|
Vapor 4 maakt gebruik van Xcode 11's eigen SPM ondersteuning. Dit betekent dat u niet langer `.xcodeproj` bestanden hoeft te genereren. Het openen van de map van uw project in Xcode zal automatisch SPM herkennen en de afhankelijkheden binnenhalen.
|
|
|
|
U kunt uw project openen in Xcode met `vapor xcode` of `open Package.swift`.
|
|
|
|
Nadat u Package.swift hebt bijgewerkt, moet u wellicht Xcode sluiten en de volgende mappen uit de hoofdmap verwijderen:
|
|
|
|
- `Package.resolved`
|
|
- `.build`
|
|
- `.swiftpm`
|
|
- `*.xcodeproj`
|
|
|
|
Zodra uw bijgewerkte pakketten succesvol opgelost zijn, zou u compilerfouten moeten zien--waarschijnlijk heel wat. Maak u geen zorgen! We zullen u tonen hoe ze te herstellen.
|
|
|
|
## Run
|
|
|
|
De eerste opdracht is om het bestand `main.swift` van uw Run module aan te passen aan het nieuwe formaat.
|
|
|
|
```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()
|
|
```
|
|
|
|
De inhoud van het bestand `main.swift` vervangt het bestand `app.swift` van de App module, dus u kunt dat bestand verwijderen.
|
|
|
|
## App
|
|
|
|
Laten we eens kijken hoe we de basis App module structuur kunnen bijwerken.
|
|
|
|
### configure.swift
|
|
|
|
De `configure` methode moet veranderd worden om een instantie van `Application` te accepteren.
|
|
|
|
```diff
|
|
- public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws
|
|
+ public func configure(_ app: Application) throws
|
|
```
|
|
|
|
Hieronder staat een voorbeeld van een bijgewerkte configureermethode.
|
|
|
|
```swift
|
|
import Fluent
|
|
import FluentSQLiteDriver
|
|
import Vapor
|
|
|
|
// Wordt aangeroepen voordat uw toepassing initialiseert.
|
|
public func configure(_ app: Application) throws {
|
|
// Serveert bestanden uit `Public/` directory
|
|
// app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
|
|
// Configureer SQLite database
|
|
app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite)
|
|
|
|
// Migraties configureren
|
|
app.migrations.add(CreateTodo())
|
|
|
|
try routes(app)
|
|
}
|
|
```
|
|
|
|
Syntax veranderingen voor het configureren van zaken als routing, middleware, fluent, en meer staan hieronder vermeld.
|
|
|
|
### boot.swift
|
|
|
|
De inhoud van `boot` kan in de `configure` methode geplaatst worden, omdat deze nu de applicatie instantie accepteert.
|
|
|
|
### routes.swift
|
|
|
|
De `routes` methode moet veranderd worden om een instantie van `Application` te accepteren.
|
|
|
|
```diff
|
|
- public func routes(_ router: Router, _ container: Container) throws
|
|
+ public func routes(_ app: Application) throws
|
|
```
|
|
|
|
Meer informatie over wijzigingen in de routingsyntaxis wordt hieronder vermeld.
|
|
|
|
## Services
|
|
|
|
Vapor 4's services APIs zijn vereenvoudigd om het makkelijker te maken voor u om services te ontdekken en te gebruiken. Services worden nu getoond als methods en properties op `Application` en `Request` waardoor de compiler u kan helpen ze te gebruiken.
|
|
|
|
Laten we, om dit beter te begrijpen, eens naar een paar voorbeelden kijken.
|
|
|
|
```diff
|
|
// Verander de standaardpoort van de server in 8281
|
|
- services.register { container -> NIOServerConfig in
|
|
- return .default(port: 8281)
|
|
- }
|
|
+ app.http.server.configuration.port = 8281
|
|
```
|
|
|
|
In plaats van een `NIOServerConfig` te registreren bij services, wordt server configuratie nu getoond als eenvoudige eigenschappen op Application die kunnen worden overschreven.
|
|
|
|
```diff
|
|
// Registreer cors middleware
|
|
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)
|
|
```
|
|
|
|
In plaats van een `MiddlewareConfig` aan te maken en te registreren bij services, worden middleware nu blootgesteld als een eigenschap op Application waaraan toegevoegd kan worden.
|
|
|
|
```diff
|
|
// Doe een verzoek in een route handler.
|
|
- try req.make(Client.self).get("https://vapor.codes")
|
|
+ req.client.get("https://vapor.codes")
|
|
```
|
|
|
|
Net als Applicatie, stelt Request ook diensten bloot als eenvoudige eigenschappen en methoden. Request-specifieke services moeten altijd worden gebruikt binnen een routeafsluiting.
|
|
|
|
Dit nieuwe service patroon vervangt de `Container`, `Service`, en `Config` types uit Vapor 3.
|
|
|
|
### Providers
|
|
|
|
Providers zijn niet langer verplicht om pakketten van derden te configureren. Elk pakket breidt in plaats daarvan Applicatie en Verzoek uit met nieuwe eigenschappen en methoden voor configuratie.
|
|
|
|
Kijk eens hoe Leaf is geconfigureerd in Vapor 4.
|
|
|
|
```diff
|
|
// Gebruik Leaf voor het renderen van views.
|
|
- try services.register(LeafProvider())
|
|
- config.prefer(LeafRenderer.self, for: ViewRenderer.self)
|
|
+ app.views.use(.leaf)
|
|
```
|
|
|
|
Om Leaf te configureren, gebruik de `app.leaf` eigenschap.
|
|
|
|
```diff
|
|
// Uitschakelen van de caching van de bladweergave.
|
|
- services.register { container -> LeafConfig in
|
|
- return LeafConfig(tags: ..., viewsDir: ..., shouldCache: false)
|
|
- }
|
|
+ app.leaf.cache.isEnabled = false
|
|
```
|
|
|
|
### Environment
|
|
|
|
De huidige omgeving (productie, ontwikkeling, etc) kan worden opgevraagd via `app.environment`.
|
|
|
|
### Aangepaste Services
|
|
|
|
Aangepaste diensten die voldoen aan het `Service` protocol en geregistreerd zijn bij de container in Vapor 3 kunnen nu worden uitgedrukt als uitbreidingen op ofwel Applicatie of Verzoek.
|
|
|
|
```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)
|
|
+ }
|
|
+ }
|
|
```
|
|
|
|
Deze dienst kan dan worden benaderd met de extensie in plaats van `make`.
|
|
|
|
```diff
|
|
- try req.make(MyAPI.self).foo()
|
|
+ req.myAPI.foo()
|
|
```
|
|
|
|
### Aangepaste Providers
|
|
|
|
De meeste aangepaste diensten kunnen worden geïmplementeerd met behulp van extensies zoals getoond in de vorige sectie. Het kan echter nodig zijn om voor sommige geavanceerde diensten in te haken op de levenscyclus van de applicatie of gebruik te maken van opgeslagen eigenschappen.
|
|
|
|
Applicatie's nieuwe `Lifecycle` helper kan gebruikt worden om lifecycle handlers te registreren.
|
|
|
|
```swift
|
|
struct PrintHello: LifecycleHandler {
|
|
func willBoot(_ app: Application) throws {
|
|
print("Hello!")
|
|
}
|
|
}
|
|
|
|
app.lifecycle.use(PrintHello())
|
|
```
|
|
|
|
Om waarden op te slaan op Application, kun je de nieuwe `Storage` helper gebruiken.
|
|
|
|
```swift
|
|
struct MyNumber: StorageKey {
|
|
typealias Value = Int
|
|
}
|
|
app.storage[MyNumber.self] = 5
|
|
print(app.storage[MyNumber.self]) // 5
|
|
```
|
|
|
|
Toegang tot `app.storage` kan worden verpakt in een instelbare berekende eigenschap om een beknopte API te maken.
|
|
|
|
```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 stelt nu SwiftNIO's async APIs direct beschikbaar en probeert niet om methoden als `map` en `flatMap` te overloaden of alias types als `EventLoopFuture`. Vapor 3 voorzag in overloads en aliassen voor achterwaartse compatibiliteit met vroege beta versies die uitgebracht werden voordat SwiftNIO bestond. Deze zijn verwijderd om verwarring met andere SwiftNIO compatibele pakketten te verminderen en beter de best practice aanbevelingen van SwiftNIO te volgen.
|
|
|
|
### Async naamgeving veranderingen
|
|
|
|
De meest voor de hand liggende verandering is dat de `Future` typealias voor `EventLoopFuture` is verwijderd. Dit kan vrij eenvoudig worden opgelost met een find and replace.
|
|
|
|
Verder ondersteunt NIO niet de `to:` labels die Vapor 3 toevoegde. Gezien Swift 5.2's verbeterde type-inferentie, is `to:` nu toch minder nodig.
|
|
|
|
```diff
|
|
- futureA.map(to: String.self) { ... }
|
|
+ futureA.map { ... }
|
|
```
|
|
|
|
Methoden die voorafgegaan worden door `new`, zoals `newPromise` zijn veranderd in `make` om beter aan te sluiten bij de Swift stijl.
|
|
|
|
```diff
|
|
- let promise = eventLoop.newPromise(String.self)
|
|
+ let promise = eventLoop.makePromise(of: String.self)
|
|
```
|
|
|
|
`catchMap` is niet langer beschikbaar, maar NIO's methodes zoals `mapError` en `flatMapErrorThrowing` zullen in plaats daarvan werken.
|
|
|
|
Vapor 3's globale `flatMap` methode voor het combineren van meerdere futures is niet langer beschikbaar. Deze kan worden vervangen door gebruik te maken van NIO's `and` methode om veel futures samen te voegen.
|
|
|
|
```diff
|
|
- flatMap(futureA, futureB) { a, b in
|
|
+ futureA.and(futureB).flatMap { (a, b) in
|
|
// Doe iets met a en b.
|
|
}
|
|
```
|
|
|
|
### ByteBuffer
|
|
|
|
Veel methoden en eigenschappen die voorheen `Data` gebruikten, gebruiken nu NIO's `ByteBuffer`. Dit type is een krachtiger en performanter byte opslagtype. U kunt meer lezen over de API in [SwiftNIO's ByteBuffer docs](https://swiftpackageindex.com/apple/swift-nio/main/documentation/niocore/bytebuffer).
|
|
|
|
Om een `ByteBuffer` terug te converteren naar `Data`, gebruik je:
|
|
|
|
```swift
|
|
Data(buffer.readableBytesView)
|
|
```
|
|
|
|
### Throwing map / flatMap
|
|
|
|
De moeilijkste verandering is dat `map` en `flatMap` niet meer kunnen gooien. `map` heeft een werpversie met de (enigszins verwarrende) naam `flatMapThrowing`. `flatMap` heeft echter geen werpende tegenhanger. Dit kan betekenen dat je wat asynchrone code moet herstructureren.
|
|
|
|
Maps die _niet_ gooien zouden prima moeten blijven werken.
|
|
|
|
```swift
|
|
// Non-throwing map.
|
|
futureA.map { a in
|
|
return b
|
|
}
|
|
```
|
|
|
|
Maps die _wel_ gooien moeten worden hernoemd naar `flatMapThrowing`.
|
|
|
|
```diff
|
|
- futureA.map { a in
|
|
+ futureA.flatMapThrowing { a in
|
|
if ... {
|
|
throw SomeError()
|
|
} else {
|
|
return b
|
|
}
|
|
}
|
|
```
|
|
|
|
Flat-maps die _niet_ gooien zouden prima moeten blijven werken.
|
|
|
|
```swift
|
|
// Non-throwing flatMap.
|
|
futureA.flatMap { a in
|
|
return futureB
|
|
}
|
|
```
|
|
|
|
In plaats van het gooien van een fout in een flat-map, retourneer een toekomstige fout. Als de fout afkomstig is van een andere werpmethode, kan de fout worden opgevangen in een do / catch en geretourneerd als een future.
|
|
|
|
```swift
|
|
// Een gevangen fout als een toekomst teruggeven.
|
|
futureA.flatMap { a in
|
|
do {
|
|
try doSomething()
|
|
return futureB
|
|
} catch {
|
|
return eventLoop.makeFailedFuture(error)
|
|
}
|
|
}
|
|
```
|
|
|
|
Het aanroepen van methodes kan ook worden omgezet in een `flatMapThrowing` en geketend worden met behulp van tuples.
|
|
|
|
```swift
|
|
// Methode voor gooien omgebouwd tot flatMapThrowing met tuple-ketting.
|
|
futureA.flatMapThrowing { a in
|
|
try (a, doSomeThing())
|
|
}.flatMap { (a, result) in
|
|
// resultaat is de waarde van doSomething.
|
|
return futureB
|
|
}
|
|
```
|
|
|
|
## Routing
|
|
|
|
Routes worden nu rechtstreeks in de applicatie geregistreerd.
|
|
|
|
```swift
|
|
app.get("hello") { req in
|
|
return "Hello, world"
|
|
}
|
|
```
|
|
|
|
Dit betekent dat je niet langer een router hoeft te registreren bij services. Geef gewoon de applicatie door aan je `routes` methode en begin met het toevoegen van routes. Alle methodes die beschikbaar zijn op `RoutesBuilder` zijn beschikbaar op `Application`.
|
|
|
|
### Synchrone Content
|
|
|
|
Het decoderen van de inhoud van verzoeken verloopt nu synchroon.
|
|
|
|
```swift
|
|
let payload = try req.content.decode(MyPayload.self)
|
|
print(payload) // MyPayload
|
|
```
|
|
|
|
Dit gedrag kan worden opgeheven door register routes die gebruik maken van de `.stream` body collectie strategie.
|
|
|
|
```swift
|
|
app.on(.POST, "streaming", body: .stream) { req in
|
|
// Het verzoek is nu asynchroon.
|
|
req.body.collect().map { buffer in
|
|
HTTPStatus.ok
|
|
}
|
|
}
|
|
```
|
|
|
|
### Comma-separated paths
|
|
|
|
Paden moeten nu gescheiden zijn door komma's en mogen geen `/` bevatten voor consistentie.
|
|
|
|
```diff
|
|
- router.get("v1/users/", "posts", "/comments") { req in
|
|
+ app.get("v1", "users", "posts", "comments") { req in
|
|
// Handle request.
|
|
}
|
|
```
|
|
|
|
### Route parameters
|
|
|
|
Het `Parameter` protocol is verwijderd ten gunste van expliciet benoemde parameters. Dit voorkomt problemen met dubbele parameters en ongeordende fetching van parameters in middleware en route handlers.
|
|
|
|
```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)"
|
|
}
|
|
```
|
|
|
|
Het gebruik van routeparameters met modellen wordt vermeld in het Fluent-gedeelte.
|
|
|
|
## Middleware
|
|
|
|
`MiddlewareConfig` is hernoemd naar `MiddlewareConfiguration` en is nu een property op Application. U kunt middleware aan uw app toevoegen met `app.middleware`.
|
|
|
|
```diff
|
|
let corsMiddleware = CORSMiddleware(configuration: ...)
|
|
- var middleware = MiddlewareConfig()
|
|
- middleware.use(corsMiddleware)
|
|
+ app.middleware.use(corsMiddleware)
|
|
- services.register(middlewares)
|
|
```
|
|
|
|
Middleware kan niet langer geregistreerd worden op type-naam. Initialiseer de middleware eerst alvorens te registreren.
|
|
|
|
```diff
|
|
- middleware.use(ErrorMiddleware.self)
|
|
+ app.middleware.use(ErrorMiddleware.default(environment: app.environment))
|
|
```
|
|
|
|
Om alle standaard middleware te verwijderen, stel `app.middleware` in op een lege config met:
|
|
|
|
```swift
|
|
app.middleware = .init()
|
|
```
|
|
|
|
## Fluent
|
|
|
|
Fluent's API is nu database agnostisch. U kunt alleen `Fluent` importeren.
|
|
|
|
```diff
|
|
- import FluentMySQL
|
|
+ import Fluent
|
|
```
|
|
|
|
### Models
|
|
|
|
Alle modellen gebruiken nu het `Model` protocol en moeten klassen zijn.
|
|
|
|
```diff
|
|
- struct Planet: MySQLModel {
|
|
+ final class Planet: Model {
|
|
```
|
|
|
|
Alle velden worden gedeclareerd met `@Field` of `@OptionalField` property wrappers.
|
|
|
|
```diff
|
|
+ @Field(key: "name")
|
|
var name: String
|
|
|
|
+ @OptionalField(key: "age")
|
|
var age: Int?
|
|
```
|
|
|
|
Het ID van een model moet worden gedefinieerd met de `@ID` property wrapper.
|
|
|
|
```diff
|
|
+ @ID(key: .id)
|
|
var id: UUID?
|
|
```
|
|
|
|
Modellen die een identifier gebruiken met een aangepaste sleutel of type moeten `@ID(custom:)` gebruiken.
|
|
|
|
Van alle modellen moet de naam van hun tabel of verzameling statisch gedefinieerd zijn.
|
|
|
|
```diff
|
|
final class Planet: Model {
|
|
+ static let schema = "Planet"
|
|
}
|
|
```
|
|
|
|
Alle modellen moeten nu een lege initializer hebben. Aangezien alle eigenschappen property wrappers gebruiken, kan deze leeg zijn.
|
|
|
|
```diff
|
|
final class Planet: Model {
|
|
+ init() { }
|
|
}
|
|
```
|
|
|
|
Model's `save`, `update`, en `create` retourneren niet langer de model instantie.
|
|
|
|
```diff
|
|
- model.save(on: ...)
|
|
+ model.save(on: ...).map { model }
|
|
```
|
|
|
|
Modellen kunnen niet langer worden gebruikt als route path componenten. Gebruik in plaats daarvan `find` en `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` is hernoemd naar `Model.IDValue`.
|
|
|
|
Model tijdstempels worden nu aangegeven met de `@Timestamp` property wrapper.
|
|
|
|
```diff
|
|
- static var createdAtKey: TimestampKey? = \.createdAt
|
|
+ @Timestamp(key: "createdAt", on: .create)
|
|
var createdAt: Date?
|
|
```
|
|
|
|
### Relaties
|
|
|
|
Relaties worden nu gedefinieerd met property wrappers.
|
|
|
|
Parent relaties gebruiken de `@Parent` property wrapper en bevatten intern de field property. De sleutel die aan `@Parent` wordt doorgegeven moet de naam zijn van het veld dat de identifier in de database opslaat.
|
|
|
|
```diff
|
|
- var serverID: Int
|
|
- var server: Parent<App, Server> {
|
|
- parent(\.serverID)
|
|
- }
|
|
+ @Parent(key: "serverID")
|
|
+ var server: Server
|
|
```
|
|
|
|
Children relaties gebruiken de `@Children` eigenschap wrapper met een sleutelpad naar de gerelateerde `@Parent`.
|
|
|
|
```diff
|
|
- var apps: Children<Server, App> {
|
|
- children(\.serverID)
|
|
- }
|
|
+ @Children(for: \.$server)
|
|
+ var apps: [App]
|
|
```
|
|
|
|
Siblings relaties gebruiken de `@Siblings` property wrapper met sleutelpaden naar het pivot model.
|
|
|
|
```diff
|
|
- var users: Siblings<Company, User, Permission> {
|
|
- siblings()
|
|
- }
|
|
+ @Siblings(through: Permission.self, from: \.$user, to: \.$company)
|
|
+ var companies: [Company]
|
|
```
|
|
|
|
Pivots zijn nu normale modellen die voldoen aan `Model` met twee `@Parent` relaties en nul of meer extra velden.
|
|
|
|
### Query
|
|
|
|
De database context wordt nu benaderd via `req.db` in route handlers.
|
|
|
|
```diff
|
|
- Planet.query(on: req)
|
|
+ Planet.query(on: req.db)
|
|
```
|
|
|
|
`DatabaseConnectable` is hernoemd naar `Database`.
|
|
|
|
Sleutelpaden naar velden worden nu voorafgegaan door `$` om de eigenschap wrapper te specificeren in plaats van de veldwaarde.
|
|
|
|
```diff
|
|
- filter(\.foo == ...)
|
|
+ filter(\.$foo == ...)
|
|
```
|
|
|
|
### Migraties
|
|
|
|
Modellen ondersteunen niet langer op reflectie gebaseerde automatische migraties. Alle migraties moeten handmatig worden geschreven.
|
|
|
|
```diff
|
|
- extension Planet: Migration { }
|
|
+ struct CreatePlanet: Migration {
|
|
+ ...
|
|
+}
|
|
```
|
|
|
|
Migraties zijn nu stringly typed en losgekoppeld van modellen en gebruiken het `Migration` protocol.
|
|
|
|
```diff
|
|
- struct CreateGalaxy: <#Database#>Migration {
|
|
+ struct CreateGalaxy: Migration {
|
|
```
|
|
|
|
De `prepare` en `revert` methodes zijn niet langer statisch.
|
|
|
|
```diff
|
|
- static func prepare(on conn: <#Database#>Connection) -> Future<Void> {
|
|
+ func prepare(on database: Database) -> EventLoopFuture<Void>
|
|
```
|
|
|
|
Het aanmaken van een schema builder gebeurt via een instance methode op `Database`.
|
|
|
|
```diff
|
|
- <#Database#>Database.create(Galaxy.self, on: conn) { builder in
|
|
- // Use builder.
|
|
- }
|
|
+ var builder = database.schema("Galaxy")
|
|
+ // Use builder.
|
|
```
|
|
|
|
De `create`, `update`, en `delete` methods worden nu aangeroepen op de schema builder, vergelijkbaar met hoe query builder werkt.
|
|
|
|
Velddefinities zijn nu stringent getypeert en volgen het patroon:
|
|
|
|
```swift
|
|
field(<name>, <type>, <constraints>)
|
|
```
|
|
|
|
Zie onderstaand voorbeeld.
|
|
|
|
```diff
|
|
- builder.field(for: \.name)
|
|
+ builder.field("name", .string, .required)
|
|
```
|
|
|
|
Schema bouw kan nu aaneengeschakeld worden zoals query bouwer.
|
|
|
|
```swift
|
|
database.schema("Galaxy")
|
|
.id()
|
|
.field("name", .string, .required)
|
|
.create()
|
|
```
|
|
|
|
### Fluent Configuratie
|
|
|
|
`DatabasesConfig` is vervangen door `app.databases`.
|
|
|
|
```swift
|
|
try app.databases.use(.postgres(url: "postgres://..."), as: .psql)
|
|
```
|
|
|
|
`MigrationsConfig` is vervangen door `app.migrations`.
|
|
|
|
```swift
|
|
app.migrations.use(CreatePlanet(), on: .psql)
|
|
```
|
|
|
|
### Repositories
|
|
|
|
Omdat de manier waarop services werken in Vapor 4 is veranderd, betekent dat ook dat de manier om database repositories te maken is veranderd. Je hebt nog steeds een protocol nodig zoals `UserRepository`, maar in plaats van een `final class` te maken die voldoet aan dat protocol, moet je in plaats daarvan een `struct` maken.
|
|
|
|
```diff
|
|
- final class DatabaseUserRepository: UserRepository {
|
|
+ struct DatabaseUserRepository: UserRepository {
|
|
let database: Database
|
|
func all() -> EventLoopFuture<[User]> {
|
|
return User.query(on: database).all()
|
|
}
|
|
}
|
|
```
|
|
|
|
U moet ook de conformiteit van `ServiceType` verwijderen, omdat deze niet meer bestaat in Vapor 4.
|
|
```diff
|
|
- extension DatabaseUserRepository {
|
|
- static let serviceSupports: [Any.Type] = [Athlete.self]
|
|
- static func makeService(for worker: Container) throws -> Self {
|
|
- return .init()
|
|
- }
|
|
- }
|
|
```
|
|
|
|
In plaats daarvan moet je een `UserRepositoryFactory` maken:
|
|
```swift
|
|
struct UserRepositoryFactory {
|
|
var make: ((Request) -> UserRepository)?
|
|
mutating func use(_ make: @escaping ((Request) -> UserRepository)) {
|
|
self.make = make
|
|
}
|
|
}
|
|
```
|
|
Deze fabriek is verantwoordelijk voor het retourneren van een `UserRepository` voor een `Request`.
|
|
|
|
De volgende stap is het toevoegen van een extensie aan `Application` om uw fabriek te specificeren:
|
|
```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
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Om het eigenlijke repository te gebruiken in een `Request` voeg je deze extensie toe aan `Request`:
|
|
```swift
|
|
extension Request {
|
|
var users: UserRepository {
|
|
self.application.users.make!(self)
|
|
}
|
|
}
|
|
```
|
|
|
|
De laatste stap is het specificeren van de fabriek in `configure.swift`.
|
|
```swift
|
|
app.users.use { req in
|
|
DatabaseUserRepository(database: req.db)
|
|
}
|
|
```
|
|
|
|
Je kunt nu je repository benaderen in je route handlers met: `req.users.all()` en eenvoudig de factory binnen tests vervangen.
|
|
Als je een mocked repository in tests wilt gebruiken, maak dan eerst een `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)
|
|
}
|
|
}
|
|
```
|
|
|
|
Je kunt nu deze mocked repository als volgt gebruiken in je tests:
|
|
```swift
|
|
final class MyTests: XCTestCase {
|
|
func test() throws {
|
|
let users: [User] = []
|
|
app.users.use { TestUserRepository(users: users, eventLoop: $0.eventLoop) }
|
|
...
|
|
}
|
|
}
|
|
```
|