HardKore is a set of Kotlin libraries that helps develop and maintain backend applications. It covers configuration, inversion of control, lifecycle, testing, and observability, and implements integration with some popular libraries like Ktor and GraphQL Java.
HardKore built on the next principles:
- Intensive utilization of Kotlin language features
- First-class Kotlin coroutines support
- Modularization and extensibility
- Incorporating widely recognized libraries
// build.gradle.kts
plugins {
kotlin("jvm")
kotlin("kapt")
}
dependencies {
// Enable processing @AutoService annotation
kapt("com.google.auto.service:auto-service")
implementation("io.github.alon-sage.hardkore:hardkore-ktor-server-starter:0.3.0")
testImplementation("io.github.alon-sage.hardkore:hardkore-ktor-server-testing:0.3.0")
}// Application.kt
@AutoService(DiModule::class)
@DiProfiles(KtorServerDiProfile::class)
class SampleDiModule : DiModule {
override fun Binder.install() {
bindKtorModule { sampleKtorModule() }
}
private fun sampleKtorModule() = KtorModule {
routing {
get("/greeting") {
val name = call.request.queryParameters["name"] ?: "World"
call.respondText("Hello $name!")
}
}
}
}
fun main(args: Array<String>) {
runApplication(args)
}Run application with web-server argument, then
curl http://localhost:8080/greetingIt will print Hello World!
Auto testing
// ApplicationTest.kt
class ApplicationTest {
@Test
fun `default greeting`() {
webApplicationTest(KtorServerDiProfile::class) { client ->
val response = client.get("/greeting")
assertEquals("Hello World!", response.bodyAsText())
}
}
@Test
fun `custom greeting`() {
webApplicationTest(KtorServerDiProfile::class) { client ->
val response = client.get("/greeting?name=Universe")
assertEquals("Hello Universe!", response.bodyAsText())
}
}
}// build.gradle.kts
plugins {
kotlin("jvm")
kotlin("kapt")
id("com.apollographql.apollo3")
}
dependencies {
kapt("com.google.auto.service:auto-service")
implementation("io.github.alon-sage.hardkore:hardkore-graphql-server-starter:0.3.0")
testImplementation("io.github.alon-sage.hardkore:hardkore-graphql-server-testing:0.3.0")
}
apollo {
service("service") {
schemaFiles.from("src/main/resources/schema.graphql")
packageName.set("io.github.alonsage.hardkore.samples.graphqlserver")
srcDir("src/test/graphql")
outputDirConnection {
connectToKotlinSourceSet("test")
}
generateOptionalOperationVariables = false
}
}# src/main/resources/schema.graphql
type Query {
user(id: ID): User
}
type Subscription {
userEvents(userId: ID): Event
}
type User {
id: ID
name: String
lastEvents(number: Int): [Event]
}
type Event {
id: ID
message: String
}// Application.kt
@AutoService(DiModule::class)
@DiProfiles(GraphQLServerDiProfile::class)
class SampleDiModule : DiModule {
override fun Binder.install() {
bindGraphQLDataClass<User>()
bindGraphQLDataClass<Event>()
bindGraphQLResolver { DummyResolver() }
}
}
data class User(val id: UUID, val name: String)
data class Event(val id: UUID, val message: String)
// A dummy resolver that mocks DB access
class DummyResolver {
suspend fun Query.user(id: UUID): User =
User(id, "foo")
suspend fun User.lastEvents(number: Int): List<Event> =
List(number) { Event(UUID.randomUUID(), "event-$it") }
fun Subscription.userEvents(userId: UUID): Flow<Event> =
flow {
var index = 0
while (true) {
emit(Event(UUID.randomUUID(), "event-$userId-$index"))
index += 1
delay(100)
}
}
}
fun main(args: Array<String>) {
runApplication(args)
}Run application with graphql-server argument, then
curl -X POST \
-H 'Content-Type: application/json' \
-d '{"query": "query { user(id: \"b13c7592-66de-405c-9fe5-98a8187a2760\") { id name lastEvents(number: 3) { id message } } }"}' \
http://localhost:8080/graphqlIt will print something like this
{
"data": {
"user": {
"id": "b13c7592-66de-405c-9fe5-98a8187a2760",
"name": "foo",
"lastEvents": [
{
"id": "d31f3978-0efc-475b-a14a-5915a7e7ce94",
"message": "event-0"
},
{
"id": "2b3a8713-9b78-40d5-a9a7-fad98c5f2064",
"message": "event-1"
},
{
"id": "b84fefcb-56c3-45d0-bee1-877db896d72d",
"message": "event-2"
}
]
}
}
}Auto testing
# src/test/graphql/operations.graphql
query GetUserWithEvents($userId: ID, $eventsNumber: Int) {
user(id: $userId) {
id
name
lastEvents(number: $eventsNumber) {
id
message
}
}
}
subscription WatchEvents($userId: ID) {
userEvents(userId: $userId) {
id
message
}
}// ApplicationTest.kt
class ApplicationTest {
@Test
fun `returns user with events`() {
webApplicationTest(GraphQLServerDiProfile::class) { client ->
val query = GetUserWithEventsQuery(
userId = UUID.randomUUID().toString(),
eventsNumber = 3
)
val data = client.graphql().query(query).execute().let { result ->
assertNull(result.exception)
assertNull(result.errors)
assertNotNull(result.data)
}
assertNotNull(data.user) { user ->
assertEquals(query.userId, user.id)
assertEquals(query.eventsNumber, user.lastEvents?.size)
}
}
}
@Test
fun `returns watched events`() {
webApplicationTest(GraphQLServerDiProfile::class) { baseClient ->
val client = baseClient.config { install(WebSockets) }
val subscription = WatchEventsSubscription(userId = UUID.randomUUID().toString())
val results = client.graphql().subscription(subscription).toFlow().take(10).toList()
results.forEach { result ->
assertNull(result.exception)
assertNull(result.errors)
assertNotNull(result.data) { data ->
assertNotNull(data.userEvents) { event ->
assertContains(event.message!!, subscription.userId!!)
}
}
}
}
}
}-
hardkore - implements dependency injection, configuration, lifecycles, observability, and some useful utilities
-
hardkore-ktor-server - a Ktor server integration
-
hardkore-ktor-server-starter - a small wrapper that automatically registers Ktor server CLI command
-
hardkore-ktor-server-testing - implements utility to test server
-
hardkore-graphql-server - a GraphQL server implementation on top of Ktor
-
hardkore-graphql-server-starter - a small wrapper that automatically registers GraphQL server CLI command
-
hardkore-graphql-server-testing - implements utility to test server