The easiest way to get started is to copy the Misk exemplar project. This exemplar contains a Misk web app with the requisite dependencies.
Misk relies on hermit to install tools required to develop and run the service. Please follow this to install and activate hermit.
Misk uses Gradle to build and run test locally.
Run ExemplarService#main
from your IDE, or use gradle to run:
gradle run
A Misk application is wired together using Guice. Features of
Misk are managed by Guava Services
,
provided by Guice Modules
, and configured
using Misk Config
s. For example, if your application needs a Redis cache, you would install
RedisModule
,
and add a corresponding RedisConfig
to your application’s config YAML.
Misk is unopinionated about which of its features your application chooses to use, and offers multiple alternatives for some common concerns.
The entry point to every Misk application is
MiskApplication
:
fun main(args: Array<String>) {
val environment = Environment.fromEnvironmentVariable()
val env = Env(environment.name)
val config = MiskConfig.load<ExemplarConfig>("exemplar", env)
MiskApplication(
MiskRealServiceModule(),
MiskWebModule(config.web),
ExemplarAccessModule(),
ExemplarWebActionsModule(),
// e.g. to add an admin dashboard:
AdminDashboardModule(isDevelopment = true)
).run(args)
}
Every Misk application has a top-level class that implements the Config
marker interface. This
Config
encapsulates all of the configuration for the app.
By default, configs are loaded from YAML files at the app’s resources root. Each value in the config must have a corresponding entry in the YAML file.
Modules in Misk that require configuration usually have their own Config
objects. If you want to
use the Module in your app, you should add them as properties of your app’s Config
object, for
example:
data class MyAppConfig(
val my_config_value: String,
val http_clients: HttpClientsConfig
) : Config
This then corresponds to a YAML file:
my_config_value: "this value"
http_clients:
# ... config
Configs are loaded using the app’s resource loader. The config loader looks for files in the following order by default:
$SERVICE_NAME-common.yaml
$SERVICE_NAME-$ENVIRONMENT.yaml
At least one of $SERVICE_NAME-common.yaml
or $SERVICE_NAME-$ENVIRONMENT.yaml
must exist.
Values from later files take precedence.
Actions are Misk's unit for an endpoint.
Actions inherit from WebAction
and have a @Get
/@Post
annotation:
@Singleton
class HelloWebAction @Inject constructor() : WebAction {
@Get("/hello/{name}")
@Unauthenticated
@ResponseContentType(MediaTypes.APPLICATION_JSON)
fun hello(
@PathParam name: String,
): HelloResponse {
return HelloResponse(name)
}
}
data class HelloResponse(val name: String)
Read more about this in Actions.
You can unit test directly:
class HelloWebActionTest {
@Test
fun `tests the unit`() {
assertThat(HelloWebAction().hello("sandy", headersOf(), null, null))
.isEqualTo(HelloResponse("sandy"))
}
}
Integration tests set up a module for you, and adds an injector to the test class.
You can use WebServerTestingModule
to set up a running web server and make WebTestClient
available.
@MiskTest(startService = true)
class HelloWebActionTest {
@MiskTestModule val module = TestModule()
@Inject private lateinit var webTestClient: WebTestClient
@Test
fun `tests a request being made`() {
val hello = webTestClient.get("/hello/sandy")
assertThat(hello.response.code).isEqualTo(200)
assertThat(hello.parseJson<HelloResponse>())
.isEqualTo(HelloResponse("sandy"))
}
class TestModule : KAbstractModule() {
override fun configure() {
install(WebServerTestingModule())
install(HelloModule())
}
}
}
Read more about this in Actions
The main function is just an entry point for wiring together components of Misk. Long-running
threads that do the real work are written as Services
using Guava’s Service
Framework.
A Service
is bound by installing a ServiceModule
, for example:
class MyServiceModule : KAbstractModule() {
override fun configure() {
install(ServiceModule<MyService>())
}
}
Notice that in this example we use
KAbstractModule()
,
Misk’s Kotlin wrapper for
AbstractModule
,
as our base Module class.
MiskApplication
will start all services installed by a ServiceModule
.
If there is a Service
that must be run after a other set of Services
have started, the
service dependency graph should be specified at the installation site.
For example, if you are operating a movie service, which needs a database:
class MovieServiceModule : KAbstractModule() {
override fun configure() {
// Note that DatabaseService does not have to be installed here.
// It could be installed in another KAbstractModule if preferred.
install(ServiceModule<DatabaseService>())
// Multiple dependencies can be added by chaining calls to `dependsOn`.
install(ServiceModule<MovieService>()
.dependsOn<DatabaseService>())
}
}
See ServiceModule
for more details about the
service graph.
When writing Services
, always prefer to inherit from one of the common base classes:
AbstractIdleService
,
AbstractScheduledService
,
or
AbstractExecutionThreadService
.
See Services Explained for details. If your
service is can make use of exponential backoff and scheduling, take a look at using
RepeatedTaskQueue
.