-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HealthCheck endpoint and service #630
Conversation
On one terminal: sbt health-server/run On other terminal: sbt health-client/run Fails with exception: com.google.protobuf.InvalidProtocolBufferException: While parsing a protocol message, the input ended unexpectedly in the middle of a field. This could mean either that the input has been truncated or that an embedded message misreported its own length.
- Due to there’s a bug on protobuf services, we can’t use enum types. Instead of them, we use ServerStatus as case class. - Empty.type doesn’t work on cleanAll and checkAll methods. Pending to fix -
Pending: refactor unary calls into a common part
Codecov Report
@@ Coverage Diff @@
## master #630 +/- ##
==========================================
+ Coverage 82.45% 82.93% +0.47%
==========================================
Files 62 69 +7
Lines 969 996 +27
Branches 11 11
==========================================
+ Hits 799 826 +27
Misses 170 170
Continue to review full report at Codecov.
|
modules/examples/todolist/server/src/main/resources/application.conf
Outdated
Show resolved
Hide resolved
...alth-check/health-check-unary/src/main/scala/higherkindness/mu/rpc/healthcheck/service.scala
Outdated
Show resolved
Hide resolved
+ application.conf of todolist example restored
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @mrtmmr! This is looking neat, I left some comments though.
...ck-fs2/health-client/src/main/scala/higherkindness/mu/rpc/healthcheck/client/ClientApp.scala
Outdated
Show resolved
Hide resolved
...lient/src/main/scala/higherkindness/mu/rpc/healthcheck/client/HealthCheckClientHandler.scala
Outdated
Show resolved
Hide resolved
...-monix/health-client/src/main/scala/higherkindness/mu/rpc/healthcheck/client/ClientApp.scala
Outdated
Show resolved
Hide resolved
...eck-monix/health-server/src/main/scala/higherkindness/mu/rpc/healthcheck/CommonRuntime.scala
Outdated
Show resolved
Hide resolved
...h-check-fs2/src/main/scala/higherkindness/mu/rpc/healthcheck/handler/HealthServiceImpl.scala
Outdated
Show resolved
Hide resolved
...h-check-fs2/src/main/scala/higherkindness/mu/rpc/healthcheck/handler/HealthServiceImpl.scala
Outdated
Show resolved
Hide resolved
Stream.eval(watchTopic.publish1(newStatus)).compile.drain | ||
|
||
def watch(service: HealthCheck): Stream[F, HealthStatus] = | ||
watchTopic.subscribe(20).filter(hs => hs.hc == service) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why 20?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought 20 was enough elements to enqueue before blocking the subscription to the stream. I guess it depends on the project in which is used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense if we can provide it as a new function argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More or less, there is only one stream providing status information to everyone who ask for it. It just filters depending on the service you ask, so the number you pass will apply to every status (of every service added). The user needs beware of this to choose the correct number.
...check-monix/src/main/scala/higherkindness/mu/rpc/healthcheck/handler/HealthServiceImpl.scala
Outdated
Show resolved
Hide resolved
...check/health-check-unary/src/main/scala/higherkindness/mu/rpc/healthcheck/ServerStatus.scala
Outdated
Show resolved
Hide resolved
import higherkindness.mu.rpc.protocol.{service, Empty, Protobuf} | ||
object service { | ||
|
||
@service(Protobuf) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about providing this protocols as IDL files (.proto files), however, what we are providing here is also the implementation, so at the end, you will be adding the binary dependencies in any case. Am I right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but I think is better to provide it
- Monix: Debug messages dropped ExecutionContext dropped Effects fixed - FS2: Debug messages dropped Effects fixed Pending: refactor health-check modules into existing
Service: - unary service: server module - fs2 service: fs2 module - monix service: monix module Example: - health-client: all clients - health-server-fs2: fs2 server - health-server-monix: monix server
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking good, thanks @mrtmmr
...-check/health-client/src/main/scala/higherkindness/mu/rpc/healthcheck/client/ClientApp.scala
Show resolved
Hide resolved
...alth-client/src/main/scala/higherkindness/mu/rpc/healthcheck/client/monix/gclientMonix.scala
Outdated
Show resolved
Hide resolved
build.sbt
Outdated
.dependsOn(fs2 % "test->test") | ||
.dependsOn(`internal-monix`% "test->test") | ||
.dependsOn(`internal-fs2`% "test->test") | ||
.dependsOn(channel) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could it be .dependsOn(channel % "test->test")
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need it for the unary
service creation :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some more comments, LGTM once addressed or answered
Ref.of[F, Map[String, ServerStatus]](Map.empty[String, ServerStatus]) | ||
|
||
checkRef.map(c => new HealthCheckServiceUnaryImpl[F](c)) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def buildInstance[F[_]: Sync](implicit s: Scheduler): F[HealthCheckServiceUnary[F]] =
Ref.of[F, Map[String, ServerStatus]](Map.empty[String, ServerStatus])
.map(new HealthCheckServiceUnaryImpl[F](_))
modules/channel/src/main/scala/higherkindness/mu/rpc/healthcheck/HealthServiceImpl.scala
Outdated
Show resolved
Hide resolved
modules/channel/src/main/scala/higherkindness/mu/rpc/healthcheck/HealthServiceImpl.scala
Outdated
Show resolved
Hide resolved
|
||
class HealthCheckServiceMonixImpl[F[_]: Sync]( | ||
checkStatus: Ref[F, Map[String, ServerStatus]], | ||
pipe: (Observer.Sync[HealthStatus], Observable[HealthStatus]))(implicit s: Scheduler) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd change this to receive the types in two params:
object HealthServiceMonix {
def buildInstance[F[_]: Sync](implicit s: Scheduler): F[HealthCheckServiceMonix[F]] = {
val (observer, observable) =
Pipe(
MulticastStrategy.behavior(
HealthStatus(new HealthCheck("FirstStatus"), ServerStatus("UNKNOWN"))))
.concurrent(s)
Ref.of[F, Map[String, ServerStatus]](Map.empty[String, ServerStatus])
.map(new HealthCheckServiceMonixImpl[F](_, observer, observable))
}
}
class HealthCheckServiceMonixImpl[F[_]: Sync](
checkStatus: Ref[F, Map[String, ServerStatus]],
observer: Observer.Sync[HealthStatus],
observable: Observable[HealthStatus])(implicit s: Scheduler)
extends AbstractHealthService[F](checkStatus)
with HealthCheckServiceMonix[F] {
override def setStatus(newStatus: HealthStatus): F[Unit] =
checkStatus.update(_ + (newStatus.hc.nameService -> newStatus.status)) <*
Sync[F].delay(observer.onNext(newStatus))
override def watch(service: HealthCheck): Observable[HealthStatus] =
observable.filter(_.hc.nameService == service.nameService)
}
Sync[F].delay(pipe._1.onNext(newStatus)) | ||
|
||
override def watch(service: HealthCheck): Observable[HealthStatus] = | ||
pipe._2.filter(_.hc == service) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking about creating an implicit instance of cats.kernel.Order
for the type HealthCheck
, for example in a package object (mu/modules/channel/src/main/scala/higherkindness/mu/rpc/healthcheck/package.scala
)
package higherkindness.mu.rpc
import cats.kernel.Order
import cats.instances.string._
package object healthcheck {
implicit val orderForHealthCheck: Order[HealthCheck] = new HealthCheckOrder
class HealthCheckOrder(implicit O: Order[String]) extends Order[HealthCheck] {
override def eqv(x: HealthCheck, y: HealthCheck): Boolean =
O.eqv(x.nameService, y.nameService)
def compare(x: HealthCheck, y: HealthCheck): Int =
O.compare(x.nameService, y.nameService)
}
}
An then use the triple equals here:
pipe._2.filter(_.hc === service)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would this prevent? I'm not sure...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the triple equals will check the types at compilation time, avoiding some bugs when this code is evolved. i.e. if in the future the param service
is not of type HealthCheck
, the compilation will fail.
Now I'm seeing the errors in the build it looks we need to provide the protocol in a different module. We could have the following modules:
We could also include the We'd need something similar for monix and fs2. @mrtmmr @juanpedromoreno thoughts? |
Sounds good to me @fedefernandez @mrtmmr :) |
@juanpedromoreno after a talk with @mrtmmr we could create the unary service in its own single module, right now only with |
build.sbt
Outdated
lazy val `health-check-unary` = project | ||
.in(file("modules/health-check-unary")) | ||
.dependsOn(channel) | ||
.dependsOn(server) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you don't need server
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's right, I need it just on the examples
@@ -0,0 +1,70 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ops
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happen?
build.sbt
Outdated
.dependsOn(channel) | ||
.dependsOn(server) | ||
.settings(healthCheckSettings) | ||
.settings(moduleName := "mu-rpc-example-health-check-unary") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.settings(moduleName := "mu-rpc-example-health-check-unary") | |
.settings(moduleName := "mu-rpc-health-check-unary") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ops, too much changes on build.sbt
@@ -0,0 +1,12 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file seems to be related to your IDE. Could we remove it and ignore it from the .gitignore file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, no problem. I can't understand why it didn't happen before.
What should I do with code coverage test? It fails
@@ -0,0 +1,70 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same for this file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @mrtmmr ! Outstanding job 👍
|
||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏 👏 👏
|
||
class OrderingTest extends WordSpec with Matchers { | ||
"Ordering" should { | ||
"works" in { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"works" in { | |
"work" in { |
assert(new HealthCheck("example") === new HealthCheck("example")) | ||
assert(new HealthCheck("example") !== new HealthCheck("not example")) | ||
} | ||
"works with boolean comparison" in { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"works with boolean comparison" in { | |
"work with boolean comparison" in { |
What this does?
It imitates gRPC's health checking protocol .
Due to some problems of accessibility at grpc health check module, a new scala version has been developed.
It contains:
Related to #626