Skip to content

Latest commit

 

History

History
217 lines (166 loc) · 7.11 KB

getting-started.md

File metadata and controls

217 lines (166 loc) · 7.11 KB

Getting Started

The easiest way to get started is to copy the Misk exemplar project. This exemplar contains a Misk web app with the requisite dependencies.

Prerequisites

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.

Start the service

Run ExemplarService#main from your IDE, or use gradle to run:

gradle run

Set up bindings

A Misk application is wired together using Guice. Features of Misk are managed by Guava Services, provided by Guice Modules, and configured using Misk Configs. 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 main function

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)
}

Set up configuration

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

Config resolution

Configs are loaded using the app’s resource loader. The config loader looks for files in the following order by default:

  1. $SERVICE_NAME-common.yaml
  2. $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.

Write an endpoint

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.

Test the endpoint

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

Create services

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.