Helps launch a Javalin server - get it?
Augments the excellent Javalin web framework with:
- Modules to encapsulate configuration, logic, APIs, etc
- Dependency injection to promote decoupled design
- Request processor leveraging dependency injection to reduce boilerplate and ease API implementation
Important: Ballista might not be suitable for production purposes. The primary motivation is to experiment and learn a thing or two.
- Kotlin language
- Gradle build system
- Javalin web/api framework
- Pick dependency injection
- SLF4J logging
- Exposed database ORM/DAO/DSL
- PostgreSQL & SQLite database drivers
Ballista is an extraction and refinement of Katapult, an experimental full-stack (kitchen sink) project. Ballista includes only the Javalin web framework augmentation portion of Katapult.
The following is a rough (pseudocode) guide to give the gist of using Ballista with dependency injection request handling.
class InfoApi: BallistaModule {
override fun config(config: JavalinConfig) {
config.router.apiBuilder {
get("/info/{id}") { it.process(::handleInfo) }
}
}
/**
* Ballista Context processor will provide the request Context, consume the request body
* and deserialize into requested @Body type, and inject other configured dependencies
* (InfoService in this case). Return type is serialized into json response body.
*/
fun handleInfo(ctx: Context, @Body request: InfoRequest, infoService: InfoService): Info {
val id = ctx.pathParam("id") // TODO: @Param
if (!validId(id)) throw BadRequestResponse()
return infoService.lookup(id) ?: throw NotFoundResponse()
}
}
/**
* Very rudimentary incomplete example of configuring dependency injection and launching Ballista
*/
class YourApp {
fun main() {
DiBuilder()
.also { di ->
when (dbConf) { // config parsed from command-line, etc
is SqliteConfig -> di.registerSingleton(DbDriver::class) { SqliteDriver(dbConf) }
is PostgresConfig -> di.registerSingleton(DbDriver::class) { PostgresDriver(dbConf) }
}
}
.registerSingleton { ExposedDb(it.get()) } // depends on driver
.registerSingleton { InfoDao(it.get()) } // depends on db
.registerSingleton { InfoService(it.get()) } // depends on InfoDao
.register(InfoApi()) // no construction dependencies, register instance
.registerSingleton { HttpModule(it.get()) } // for example
.registerSingleton(Processor::class) { DiProcessor(it) } // injection-based request processor
.registerSingleton { Ballista(it.getAll(), it.get()) } // Ballista service
.get(Ballista::class).start() // dependencies injected
}
}
Versioning:
- Intention is to follow semver
- ... but anything goes for the initial 0.X.X releases
Branch strategy:
- Work is done in
develop
branch - Feature development may branch from (and back into)
develop
- Merging to
master
implies a release, which must be tagged, and may trigger CI/CD
Release process:
- Merge any ready feature branches into
develop
- Update
version
inbuild.gradle.kt
toX.Y.Z
git commit -am "vX.Y.Z"
git checkout master && git merge develop && git tag X.Y.Z
(nov
)git push --all && git push --tags
git checkout develop
# back to work
- Clean up initial (rough) copy from external project
- Documentation and example usage
- Unit tests
- Bundle into a library jar
- Publish library (eg, Maven Central)
- Separate modules (with their underlying dependencies) into optional bundles
- Remove Log class from this library
MIT License © Nathaniel Baughman