Structure MiskApplication to be testable
Every service implements a default MiskApplication interface by adding necessary modules.
Every service also creates injector tests so that the requirements of modules can be verified in tests, not only when the service is run.
interface MiskApplication {
fun modules(environment: Environment) : List<Module>
}
class UrlShortenerAppTest {
fun createsInjectorInProduction() {
val extraConfig = """
dajfdkafdslkaf:
"""
// If it fails to create the injector, we know the production is missing a module.
Guice.createInjector(UrlShortenerApp().modules(Environment.PRODUCTION))
}
fun createsInjectorInStaging() {
// If it fails to create the injector, we know the production is missing a module.
Guice.createInjector(UrlShortenerApp().modules(Environment.STAGING))
}
fun createsInjectorInTest() {
// If it fails to create the injector, we know the production is missing a module.
Guice.createInjector(UrlShortenerApp().modules(Environment.TESTING))
}
}
class UrlShortenerApp : com.squareup.cashurlshortener.service.MiskApplication {
override fun modules(environment: Environment): List<Module> {
SkimLogging.configure()
listOf(
CashUrlshortenerModule(environment),
CashUrlshortenerServiceModule()
)
}
}
fun main(args: Array<String>) {
MiskApplication(UrlShortenerApp()).run(args)
}
I'm pretty strongly against this. Square's internal service container works this way and introducing conditionality / modality into the already complicated process of Guice bindings creates an initialization system that is nearly impossible to reason about.
Other than starting the service, there doesn't seem to be another way to test that creating the Guice injector won't fail. Not having a way to test this has already caused a lot of headaches when deploying services with missing multibindings, new Misk or other module requirements, or other Guice issues.
This doesn't change the simplicity of running a service or introduce multiple injectors. It only tests that the injector creation will be successful in different environments.
I'm not opposed to the goal, just the solution. This both introduces and formalizes (almost as best practice) having conditional logic to install different bindings depending on the environment. We have a similar mechanism in the in-house service container which allows for creating different modules on a per-environment basis and it becomes almost impossible to know what is actually getting installed. I might be ok with this if we ensure that environment level switches only exist at the application layer, and that modules themselves never have logic that is conditional based on environment.