본문으로 건너뛰기

서비스(Services)#

Vapor의 ApplicationRequest는 여러분의 애플리케이션과 서드파티 패키지에 의해 확장될 수 있도록 만들어졌습니다. 이러한 타입에 추가되는 새로운 기능을 흔히 서비스라고 부릅니다.

읽기 전용(Read Only)#

가장 단순한 형태의 서비스는 읽기 전용입니다. 이러한 서비스는 application 또는 request에 추가된 계산 프로퍼티나 메서드로 구성됩니다.

import Vapor

struct MyAPI {
    let client: Client

    func foos() async throws -> [String] { ... }
}

extension Request {
    var myAPI: MyAPI {
        .init(client: self.client)
    }
}

읽기 전용 서비스는 이 예제의 client처럼 기존에 존재하는 서비스에 의존할 수 있습니다. extension이 추가되고 나면, 여러분의 커스텀 서비스는 request의 다른 프로퍼티와 마찬가지로 사용할 수 있습니다.

req.myAPI.foos()

쓰기 가능(Writable)#

상태나 설정이 필요한 서비스는 데이터를 저장하기 위해 ApplicationRequest의 storage를 활용할 수 있습니다. 다음과 같은 MyConfiguration 구조체를 애플리케이션에 추가하고 싶다고 가정해봅시다.

struct MyConfiguration {
    var apiKey: String
}

storage를 사용하려면 StorageKey를 선언해야 합니다.

struct MyConfigurationKey: StorageKey {
    typealias Value = MyConfiguration
}

이것은 어떤 타입이 저장되는지를 지정하는 Value typealias를 가진 빈 구조체입니다. 빈 타입을 키로 사용함으로써, 어떤 코드가 storage 값에 접근할 수 있는지를 제어할 수 있습니다. 타입이 internal이나 private이라면, 오직 여러분의 코드만이 storage에 있는 관련 값을 수정할 수 있습니다.

마지막으로, MyConfiguration 구조체를 가져오고 설정하기 위한 extension을 Application에 추가합니다.

extension Application {
    var myConfiguration: MyConfiguration? {
        get {
            self.storage[MyConfigurationKey.self]
        }
        set {
            self.storage[MyConfigurationKey.self] = newValue
        }
    }
}

extension이 추가되고 나면, myConfigurationApplication의 일반적인 프로퍼티처럼 사용할 수 있습니다.

app.myConfiguration = .init(apiKey: ...)
print(app.myConfiguration?.apiKey)

라이프사이클(Lifecycle)#

Vapor의 Application은 라이프사이클 핸들러를 등록할 수 있게 해줍니다. 이를 통해 boot, 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의 Application은 락을 사용해 코드를 동기화할 수 있는 편의 기능을 포함하고 있습니다. LockKey를 선언하면, 여러분의 코드에 대한 접근을 동기화하기 위한 고유한 공유 락을 얻을 수 있습니다.

struct TestKey: LockKey { }

let test = app.locks.lock(for: TestKey.self)
test.withLock {
    // Do something.
}

동일한 LockKeylock(for:)를 호출할 때마다 같은 락이 반환됩니다. 이 메서드는 스레드 안전(thread-safe)합니다.

애플리케이션 전역에서 사용할 락이 필요하다면 app.sync를 사용할 수 있습니다.

app.sync.withLock {
    // Do something.
}

Request#

라우트 핸들러에서 사용하도록 의도된 서비스는 Request에 추가되어야 합니다. Request 서비스는 request의 logger와 event loop를 사용해야 합니다. request가 동일한 event loop에 계속 머물러 있는 것이 중요한데, 그렇지 않으면 응답이 Vapor로 반환될 때 assertion에 걸리게 됩니다.

서비스가 작업을 수행하기 위해 request의 event loop를 벗어나야 한다면, 종료되기 전에 반드시 해당 event loop로 되돌아와야 합니다. 이는 EventLoopFuturehop(to:)를 사용해서 할 수 있습니다.

설정(configuration)과 같이 애플리케이션 서비스에 대한 접근이 필요한 request 서비스는 req.application을 사용할 수 있습니다. 라우트 핸들러에서 애플리케이션에 접근할 때는 스레드 안전성(thread-safety)을 고려하도록 주의하세요. 일반적으로 request는 읽기 작업만 수행해야 합니다. 쓰기 작업은 반드시 락으로 보호되어야 합니다.

이 페이지 편집