segment-of-one / microservice

Easily create a akka-http based microservice

Version Matrix

Microservice

Microservice library allows you to easily create an akka-http based service.

The library exposes for you two important pieces of information required by microservice-based architecture, which are:

  • Service health status which can be used by proxy, service discovery or your monitoring system
  • Service version

Data is being exposed by convenient endpoints created by library:

  • .../health
  • .../version

with almost no work required from you.

Usage

Add library dependency to your sbt definition file:

libraryDependencies += "net.so1" %% "microservice" % "0.0.3"

Next extend your application stating point with Microservice:

object Boot extends Microservice {...}

and provide actor system enviroment on which your microservice will work:

override implicit val actorSystem: ActorSystem = ActorSystem("so1microservice-example")
override implicit val materializer: ActorMaterializer = ActorMaterializer()
override implicit val dispacher: ExecutionContextExecutor = actorSystem.dispatcher

Of course you need to be able to query your service health & version. In order to build proper endpoints you need to define a BasePath:

override lazy val basePath: BasePath = BasePath("microservice", "example")

which will create following endpoints:

  • /microservice/example/health
  • /microservice/example/version

If you want to expose some other functionalities via endpoints, just create routing:

override lazy val serviceRoute: Option[Route] = Some(exampleRoute)

Additional routing is optional, so if you have a service which is, for instance, only processing kafka events without exposing any endpoints you can still monitor it in a consistent way.

Your service is ready to start, so please start it with a configuration:

start(Config.serverConfig) onComplete {
  case Success(_) => logger.info(s"Everything is ok...")
  case Failure(ex) => logger.error("Something is wrong...", ex); actorSystem.terminate()
}

Library leaves up to you how do you handle service start giving you a full control of what is happening. Important service won't start if service is not healthy.

Health system

Health system is build on two concepts:

  • Checkable - simple trait that tells the executor how to check health status of particular part of the service
  • HealthService - health check executor

In short you can implement any check, which is relevant for your service and pass it to the health check executor. The check will be executed every time you query .../health endpoint. The Microservice library doesn't provide any checks, as they highly depend on your needs and middlewear / client libraries you are using. We successfuly monitor db connection status, state of kafka events processing, availability of some external services etc.

Checkable

trait Checkable {
  def check(): Checkable.CheckResult
}

Simplest check implementation:

class AlwaysSuccessCheck extends Checkable {
  override def check(): CheckResult = Checkable.Success("always-success-check")
}

Notice that some string is being passed in a response - it is check name visible in .../health response. In case of check failure in addition to the check name you can also pass string describing an error.

HealthService

Health service is kind of an executor for a set of Checkable. You can implement your own or use one of provided:

  • DummyHealthCheckService is a default one and returns ok every time, so when you use it health request behaves like a ping
  • SyncHealthCheckService synchronously executes a set of checkable
  • AsyncHealthCheckService asynchronously executes a set of checkable

You can change an implementation simply overriding it on top level:

override val healthCheckService: HealthCheckService = new SyncHealthCheckService(checks)

When you query .../health endpoint you will receive response in a following format:

{  
   "status":"unhealthy",
   "checks":[  
      {  
         "isOk":true,
         "name":"always-success-check"
      },
      {  
         "isOk":false,
         "name":"always-failure-check",
         "reason":"You can put here a message from the exception..."
      }
   ]
}

Version

Version service simply returns the current version of your service. Currently there are two implementations provided:

  • DummyVersionService returns given string
  • ManifestBasedVersionService returns MANIFEST.MF file content

You can change implementation by simply overriding it on top level:

override val versionService: VersionService = new ManifestBasedVersionService()

Full example

object Boot extends Microservice with ExampleRouting {

  override lazy val basePath: BasePath = BasePath("so1microservice", "example")
  override lazy val serviceRoute: Option[Route] = Some(exampleRoute)

  override implicit val actorSystem: ActorSystem = ActorSystem("so1microservice-example")
  override implicit val materializer: ActorMaterializer = ActorMaterializer()
  override implicit val dispacher: ExecutionContextExecutor = actorSystem.dispatcher

  val alwaysSuccessCheck: Checkable = new AlwaysSuccessCheck
  val alwaysFailureCheck: Checkable = new AlwaysFailureCheck
  override val healthCheckService: HealthCheckService = new SyncHealthCheckService(
    Set(
      alwaysSuccessCheck
//      , alwaysFailureCheck //Comment it if you want to be able to start the service
    )
  )

  start(Config.serverConfig) onComplete {
    case Success(_) => logger.info(s"Everything is ok...")
    case Failure(ex) => logger.error("Something is wrong...", ex); actorSystem.terminate()
  }

}