Skip to content
Permalink
Browse files

split out misk-service module for misk's ServiceManager (#1216)

  • Loading branch information...
mightyguava committed Oct 3, 2019
1 parent 16ebfbe commit 6b213774481b9f28352457bd3fdc52e99b2e71e8
@@ -8,9 +8,9 @@ dependencies {
compile dep.guice
compile dep.kotlinStdLib
compile dep.kotlinReflection
compile project(':misk')
compile project(':misk-feature')
compile project(':misk-inject')
compile project(':misk-service')

testCompile project(':misk-testing')
}
@@ -0,0 +1,23 @@
buildscript {
dependencies {
classpath dep.kotlinNoArgPlugin
}
}

dependencies {
compile dep.kotlinStdLib
compile dep.javaxInject
compile dep.guava
compile dep.guice
compile dep.loggingApi
compile project(':misk-inject')

testCompile project(':misk-testing')
}

afterEvaluate { project ->
project.tasks.dokka {
outputDirectory = "$rootDir/docs/0.x"
outputFormat = 'gfm'
}
}
@@ -0,0 +1,4 @@
POM_ARTIFACT_ID=misk-service
POM_NAME=misk-service
POM_DESCRIPTION=A Misk module for service lifecycle management
POM_PACKAGING=jar
@@ -9,9 +9,9 @@ import javax.inject.Provider

internal class CoordinatedService(
private val serviceProvider: Provider<out Service>
) : AbstractService() {
) : AbstractService(), DelegatingService {

val service: Service by lazy {
override val service: Service by lazy {
serviceProvider.get()
}

@@ -0,0 +1,10 @@
package misk

import com.google.common.util.concurrent.Service

/**
* Interface to retrieve the underlying [Service] of a wrapper [Service]
*/
interface DelegatingService : Service {
val service: Service
}
@@ -11,7 +11,7 @@ import javax.inject.Provider
* Builds a graph of [CoordinatedService]s which defer start up and shut down until their dependent
* services are ready.
*/
class ServiceGraphBuilder {
internal class ServiceGraphBuilder {
private var serviceMap = mutableMapOf<Key<*>, CoordinatedService>()
private val dependencyMap = LinkedHashMultimap.create<Key<*>, Key<*>>()
private val enhancementMap = mutableMapOf<Key<*>, Key<*>>()
@@ -0,0 +1,78 @@
package misk

import com.google.common.util.concurrent.Service
import com.google.common.util.concurrent.ServiceManager
import com.google.inject.Injector
import com.google.inject.Provides
import com.google.inject.Scopes
import misk.inject.KAbstractModule
import misk.inject.asSingleton
import mu.KotlinLogging
import javax.inject.Provider
import javax.inject.Singleton

class ServiceManagerModule : KAbstractModule() {
companion object {
private val log = KotlinLogging.logger {}
}

override fun configure() {
newMultibinder<Service>()
newMultibinder<ServiceManager.Listener>()

multibind<ServiceManager.Listener>().toProvider(Provider<ServiceManager.Listener> {
object : ServiceManager.Listener() {
override fun failure(service: Service) {
log.error(service.failureCause()) { "Service $service failed" }
}
}
}).asSingleton()
newMultibinder<ServiceEntry>()
newMultibinder<DependencyEdge>()
newMultibinder<EnhancementEdge>()
}
@Provides
@Singleton
internal fun provideServiceManager(
injector: Injector,
services: List<Service>,
listeners: List<ServiceManager.Listener>,
serviceEntries: List<ServiceEntry>,
dependencies: List<DependencyEdge>,
enhancements: List<EnhancementEdge>
): ServiceManager {
val invalidServices = mutableListOf<String>()
val builder = ServiceGraphBuilder()

// Support the new ServiceModule API.
for (entry in serviceEntries) {
if (!Scopes.isSingleton(injector.getBinding(entry.key))) {
invalidServices += entry.key.typeLiteral.type.typeName
}
builder.addService(entry.key, injector.getProvider(entry.key))
}
for (edge in dependencies) {
builder.addDependency(dependent = edge.dependent, dependsOn = edge.dependsOn)
}
for (edge in enhancements) {
builder.enhanceService(toBeEnhanced = edge.toBeEnhanced, enhancement = edge.enhancement)
}

// Confirm all services have been registered as singleton. If they aren't singletons,
// _readiness checks will fail
check(invalidServices.isEmpty()) {
"the following services are not marked as @Singleton: ${invalidServices.joinToString(", ")}"
}

// Enforce that services are installed via a ServiceModule.
check(services.isEmpty()) {
"This doesn't work anymore! " +
"Instead of using `multibind<Service>().to(${services.first()::class.simpleName})`, " +
"use `install(ServiceModule<${services.first()::class.simpleName}>())`."
}

val serviceManager = builder.build()
listeners.forEach { serviceManager.addListener(it) }
return serviceManager
}
}
@@ -135,7 +135,7 @@ class ServiceModule(
* ```
*/
inline fun <reified T : Service> ServiceModule(qualifier: KClass<out Annotation>? = null) =
ServiceModule(T::class.toKey(qualifier))
misk.ServiceModule(T::class.toKey(qualifier))

internal data class EnhancementEdge(val toBeEnhanced: Key<*>, val enhancement: Key<*>)
internal data class DependencyEdge(val dependent: Key<*>, val dependsOn: Key<*>)
@@ -349,7 +349,8 @@ class ServiceGraphBuilderTest {
): ServiceGraphBuilder {
val builder = ServiceGraphBuilder()
for (service in services) {
builder.addService(service, AppendingService(target, service.name))
builder.addService(service,
AppendingService(target, service.name))
}
return builder
}
@@ -14,7 +14,7 @@ import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import kotlin.test.assertFailsWith

internal class MiskServiceModuleTest {
internal class ServiceManagerModuleTest {
@com.google.inject.Singleton
class SingletonService1 : AbstractIdleService() {
override fun startUp() {}
@@ -98,16 +98,19 @@ internal class MiskServiceModuleTest {
install(ServiceModule<SingletonService2>())
install(ServiceModule<ProvidesMethodService>())
install(ServiceModule<InstanceService>())
bind(keyOf<InstanceService>()).toInstance(InstanceService())
install(ServiceModule<ExplicitEagerSingletonService>())
bind(keyOf<InstanceService>()).toInstance(
InstanceService())
install(
ServiceModule<ExplicitEagerSingletonService>())
bind(keyOf<ExplicitEagerSingletonService>()).asEagerSingleton()
install(ServiceModule<SingletonScopeService>())
bind(keyOf<SingletonScopeService>())
.`in`(Scopes.SINGLETON)
install(ServiceModule<SingletonAnnotationService>())
bind(keyOf<SingletonAnnotationService>())
.`in`(com.google.inject.Singleton::class.java)
install(ServiceModule<GoogleSingletonAnnotationService>())
install(
ServiceModule<GoogleSingletonAnnotationService>())
bind(keyOf<GoogleSingletonAnnotationService>())
.`in`(com.google.inject.Singleton::class.java)

@@ -123,7 +126,7 @@ internal class MiskServiceModuleTest {

injector.getInstance<ServiceManager>()
}.message).contains("the following services are not marked as @Singleton: " +
"misk.MiskServiceModuleTest\$NonSingletonService1, " +
"misk.MiskServiceModuleTest\$NonSingletonService2")
"misk.ServiceManagerModuleTest\$NonSingletonService1, " +
"misk.ServiceManagerModuleTest\$NonSingletonService2")
}
}
@@ -50,6 +50,7 @@ dependencies {
compile dep.prometheusHotspot
compile dep.jnrUnixsocket
compile project(':misk-inject')
compile project(':misk-service')

testCompile dep.kotlinxCoroutines
testCompile dep.mockitoCore
@@ -46,74 +46,13 @@ class MiskCommonServiceModule : KAbstractModule() {
override fun configure() {
binder().disableCircularProxies()
binder().requireExactBindingAnnotations()
install(ServiceManagerModule())
install(MetricsModule())
install(MoshiModule())
install(TokenGeneratorModule())
install(PrometheusHistogramRegistryModule())

// Initialize empty sets for our multibindings.
newMultibinder<HealthCheck>()
newMultibinder<Service>()
newMultibinder<ServiceManager.Listener>()

multibind<ServiceManager.Listener>().toProvider(Provider<ServiceManager.Listener> {
object : ServiceManager.Listener() {
override fun failure(service: Service) {
log.error(service.failureCause()) { "Service $service failed" }
}
}
}).asSingleton()
newMultibinder<ServiceEntry>()
newMultibinder<DependencyEdge>()
newMultibinder<EnhancementEdge>()
}

@Provides
@Singleton
internal fun provideServiceManager(
injector: Injector,
services: List<Service>,
listeners: List<ServiceManager.Listener>,
serviceEntries: List<ServiceEntry>,
dependencies: List<DependencyEdge>,
enhancements: List<EnhancementEdge>
): ServiceManager {
val invalidServices = mutableListOf<String>()
val builder = ServiceGraphBuilder()

// Support the new ServiceModule API.
for (entry in serviceEntries) {
if (!Scopes.isSingleton(injector.getBinding(entry.key))) {
invalidServices += entry.key.typeLiteral.type.typeName
}
builder.addService(entry.key, injector.getProvider(entry.key))
}
for (edge in dependencies) {
builder.addDependency(dependent = edge.dependent, dependsOn = edge.dependsOn)
}
for (edge in enhancements) {
builder.enhanceService(toBeEnhanced = edge.toBeEnhanced, enhancement = edge.enhancement)
}

// Confirm all services have been registered as singleton. If they aren't singletons,
// _readiness checks will fail
check(invalidServices.isEmpty()) {
"the following services are not marked as @Singleton: ${invalidServices.joinToString(", ")}"
}

// Enforce that services are installed via a ServiceModule.
check(services.isEmpty()) {
"This doesn't work anymore! " +
"Instead of using `multibind<Service>().to(${services.first()::class.simpleName})`, " +
"use `install(ServiceModule<${services.first()::class.simpleName}>())`."
}

val serviceManager = builder.build()
listeners.forEach { serviceManager.addListener(it) }
return serviceManager
}

companion object {
private val log = KotlinLogging.logger {}
}
}
@@ -2,7 +2,7 @@ package misk.web.actions

import com.google.common.util.concurrent.Service
import com.google.common.util.concurrent.ServiceManager
import misk.CoordinatedService
import misk.DelegatingService
import misk.healthchecks.HealthCheck
import misk.healthchecks.HealthStatus
import misk.security.authz.Unauthenticated
@@ -30,7 +30,7 @@ class StatusAction @Inject internal constructor(
val services = serviceManagerProvider.get().servicesByState().values().asList()
val serviceStatus = services.map {
when (it) {
is CoordinatedService -> it.service.javaClass.simpleName to it.state()
is DelegatingService -> it.service.javaClass.simpleName to it.state()
else -> it.javaClass.simpleName to it.state()
}
}.toMap()
@@ -20,6 +20,7 @@ include ':misk-launchdarkly-core'
include ':misk-metrics-digester'
include ':misk-prometheus'
include ':misk-redis'
include ':misk-service'
include ':misk-slack'
include ':misk-testing'
include ':misk-zipkin'

0 comments on commit 6b21377

Please sign in to comment.
You can’t perform that action at this time.