Services¶
Vapor's Application
and Request
are built to be extended by your application and third-party packages. New functionality added to these types are often called services.
Read Only¶
The simplest type of service is read-only. These services consist of computed variables or methods added to either application or request.
import Vapor
struct MyAPI {
let client: Client
func foos() async throws -> [String] { ... }
}
extension Request {
var myAPI: MyAPI {
.init(client: self.client)
}
}
Read-only services can depend on any pre-existing services, like client
in this example. Once the extension has been added, your custom service can be used like any other property on request.
req.myAPI.foos()
Writable¶
Services that need state or configuration can utilize Application
and Request
storage for storing data. Let's assume you want to add the following MyConfiguration
struct to your application.
struct MyConfiguration {
var apiKey: String
}
To use storage, you must declare a StorageKey
.
struct MyConfigurationKey: StorageKey {
typealias Value = MyConfiguration
}
This is an empty struct with a Value
typealias specifying which type is being stored. By using an empty type as the key, you can control what code is able to access your storage value. If the type is internal or private, only your code will be able to modify the associated value in storage.
Finally, add an extension to Application
for getting and setting the MyConfiguration
struct.
extension Application {
var myConfiguration: MyConfiguration? {
get {
self.storage[MyConfigurationKey.self]
}
set {
self.storage[MyConfigurationKey.self] = newValue
}
}
}
Once the extension is added, you can use myConfiguration
like a normal property on Application
.
app.myConfiguration = .init(apiKey: ...)
print(app.myConfiguration?.apiKey)
Lifecycle¶
Vapor's Application
allows you to register lifecycle handlers. These let you hook into events such as boot and shutdown.
// Prints hello during boot.
struct Hello: LifecycleHandler {
// Called before application boots.
func willBoot(_ app: Application) throws {
app.logger.info("Hello!")
}
// Called after application boots.
func didBoot(_ app: Application) throws {
app.logger.info("Server is running")
}
// Called before application shutdown.
func shutdown(_ app: Application) {
app.logger.info("Goodbye!")
}
}
// Add lifecycle handler.
app.lifecycle.use(Hello())
Locks¶
Vapor's Application
includes conveniences for synchronizing code using locks. By declaring a LockKey
, you can get a unique, shared lock to synchronize access to your code.
struct TestKey: LockKey { }
let test = app.locks.lock(for: TestKey.self)
test.withLock {
// Do something.
}
Each call to lock(for:)
with the same LockKey
will return the same lock. This method is thread-safe.
For an application-wide lock, you can use app.sync
.
app.sync.withLock {
// Do something.
}
Request¶
Services that are intended to be used in route handlers should be added to Request
. Request services should use the request's logger and event loop. It is important that a request stay on the same event loop or an assertion will be hit when the response is returned to Vapor.
If a service must leave the request's event loop to do work, it should make sure to return to the event loop before finishing. This can be done using the hop(to:)
on EventLoopFuture
.
Request services that need access to application services, such as configurations, can use req.application
. Take care to consider thread-safety when accessing the application from a route handler. Generally, only read operations should be performed by requests. Write operations must be protected by locks.