Kairo Features
Features are the core building block of Kairo applications. Each Feature defines an encapsulated piece of functionality.
There are 2 types of Features.
- Framework Features: Support things like REST, SQL, various GCP services, etc. Many of these are defined by Kairo, but you can create your own too.
- Domain Features: These make up your application code, each representing a concern like users, businesses, billing, etc.
Why Features?
Section titled “Why Features?”- Encapsulation: Each Feature owns its lifecycle and configuration. Clear boundaries mean more maintainable code.
- Composability: Servers are composed of Features. No hidden assumptions means you’re not tied to patterns that don’t make sense for your application.
Installation
Section titled “Installation”Install kairo-feature.
But if you’ve already installed kairo-server or kairo-application,
you don’t need kairo-feature too.
dependencies { implementation("software.airborne.kairo:kairo-feature")}Framework Features
Section titled “Framework Features”The following Framework Features are currently available.
- ClientFeature: Ktor-native outgoing HTTP requests from your Kairo application.
- DependencyInjectionFeature: Uses Koin for reflection-free, Kotlin-friendly dependency injection.
- HealthCheckFeature: Adds configurable health check REST endpoints to your application.
- MailersendFeature for sending emails.
- RestFeature: Declarative REST endpoints wired through Ktor.
- SlackFeature for sending messages to Slack channels.
- SqlFeature: Type-safe SQL with async I/O.
- StytchFeature for identity management.
You can also create your own — most of these are fairly straightforward.
Creating a Domain Feature
Section titled “Creating a Domain Feature”These make up your application code, each representing a concern like users, businesses, billing, etc.
class UserFeature( private val koin: Koin,) : Feature(), HasRouting { override val name: String = "User"
private val userHandler: UserHandler get() = koin.get()
override fun Application.routing() { with(userHandler) { routing() } }}Lifecycle handlers
Section titled “Lifecycle handlers”Features can hook into Server lifecycle events by implementing lifecycle handlers. Lifecycle handlers define a priority (most Features should use the default priority). All handlers with the same priority run in parallel, so they must be thread/coroutine-safe.
class UserFeature( private val koin: Koin,) : Feature(), HasRouting { override val name: String = "User"
private val userHandler: UserHandler get() = koin.get()
override val lifecycle: List<LifecycleHandler> = lifecycle { handler { start { // ... } stop { // ... } } }
override fun Application.routing() { with(userHandler) { routing() } }}Putting it all together
Section titled “Putting it all together”Combine your Features into a Server.
val koinApplication = koinApplication()
val healthChecks = mapOf( "sql" to HealthCheck { SqlFeature.healthCheck(koinApplication.koin.get()) },)
val features = listOf( // Framework Features. DependencyInjectionFeature(koinApplication), HealthCheckFeature(healthChecks), RestFeature(config.rest), SqlFeature( config = config.sql, configureDatabase = { explicitDialect = PostgreSQLDialect() }, ),
// Domain Feature. UserFeature(koinApplication.koin),)
val server = Server( name = "...", features = features,)