Skip to content

Instantly share code, notes, and snippets.

@bambuchaAdm
Last active March 8, 2019 15:22
Show Gist options
  • Save bambuchaAdm/6e5969b325cff75af6c96ea59235a02f to your computer and use it in GitHub Desktop.
Save bambuchaAdm/6e5969b325cff75af6c96ea59235a02f to your computer and use it in GitHub Desktop.
Schedule Jobs POC

Scheduled Jobs new design POC

Usage in application

  1. Job implementation
class MyJob extends Job {
  // require method here 
}
  1. Register in DI mechanism
object ApplicationModule extends AbstractModule with JobSyntax {

  override def configure(): Unit = {
    install(job[MyJob].named("my-awsome-job")
  }
}
  1. Enable for scheduling
jobs.enabled  += "my-awsome-job"

Compatibility

Old GlobalSettings would use injected JobManager as addiction to legacy mechanic. This would allow teams move smothly from old implementation to new implementation, and support both solution for some time.

Open questions

  • Should we expose configuration of scheduling interval into configuration
  • Hooks for auditing
  • Metrics over execution
  • Dynamicly created scheduled jobs
  • Usage of Guice - maybe multibindings is what we need here
  • What if job execution exceed scheduling interval
  • Non fixed scheduling - job could have control over deplays he wants
jobs.enabled = []
jobs.enabled += "my"
jobs.enabled += "other"
import com.google.inject.AbstractModule
object ApplicationModule extends AbstractModule with JobSyntax {
override def configure(): Unit = {
install(job[MyJob].named("my"))
install(job[OtherJob].named("other"))
}
}
name := "guice-by-name"
scalaVersion := "2.12.8"
// https://mvnrepository.com/artifact/com.google.inject/guice
libraryDependencies += "com.google.inject" % "guice" % "4.2.2"
// https://mvnrepository.com/artifact/com.typesafe/config
libraryDependencies += "com.typesafe" % "config" % "1.3.3"
import javax.inject.Named
import scala.concurrent.{ExecutionContext, Future}
trait Job {
//execution related methods
def execute(implicit executionContext: ExecutionContext): Future[Unit]
}
class MyJob extends Job {
def execute(implicit executionContext: ExecutionContext): Future[Unit] = {
???
}
}
class OtherJob extends Job {
def execute(implicit executionContext: ExecutionContext): Future[Unit] = Future.successful((): Unit)
}
import com.google.inject.{Injector, Key}
import com.google.inject.name.Names
import javax.inject.{Inject, Singleton}
@Singleton
class JobManager @Inject()(enabled: Set[String], injector: Injector) {
val jobs = enabled.map { name =>
injector.getInstance(Key.get(classOf[Job], Names.named(name)))
}
}
import com.google.inject.{PrivateModule, Module}
import com.google.inject.name.Names
import scala.reflect.ClassTag
import javax.inject.Named
class JobBuilder[T <: Job](tag: ClassTag[T]){
def named(name: String): Module = {
new PrivateModule(){
protected override def configure(): Unit = {
val annotation = Names.named(name)
bind(classOf[Job])
.annotatedWith(annotation)
.to(tag.runtimeClass.asInstanceOf[Class[T]]) // Limitation of ClassTag API and enforcement of Guice API
expose(classOf[Job]).annotatedWith(annotation)
}
}
}
}
trait JobSyntax {
def job[T <: Job](implicit tag: ClassTag[T]): JobBuilder[T] = new JobBuilder(tag)
}
import com.google.inject.{AbstractModule, Provides}
import com.typesafe.config.{Config, ConfigFactory}
import scala.collection.JavaConverters._
import java.io.File
object LibraryModule extends AbstractModule {
@Provides
def enabledJobs(config: Config) : Set[String] = {
config.getStringList("jobs.enabled").asScala.toSet
}
override def configure(): Unit = {
bind(classOf[Config]).toInstance(
ConfigFactory.parseFile(new File("application.conf")).resolve() // HACK FOR GIST
)
bind(classOf[JobManager]).asEagerSingleton()
}
}
import com.google.inject.Guice
object Main extends App {
val injector = Guice.createInjector(LibraryModule, ApplicationModule)
val instance = injector.getInstance(classOf[JobManager])
println(instance.jobs)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment