-
Notifications
You must be signed in to change notification settings - Fork 57
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
668 aspect oriented middleware #669
base: main
Are you sure you want to change the base?
Conversation
|
||
trait GeneratedCompanion[Service[*[_], _]] { | ||
|
||
implicit final def serviceCompanion: GeneratedCompanion[Service] = this | ||
|
||
///=== Client ========================================================================================================== | ||
|
||
def mkClientTrivial[F[_]: Async, A]( |
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.
protected?
@ValdemarGr I think this looks promising. Should |
I don't think it is possible to declare a generic implementation of UpdateI think it can be achieved by using an abstract type and some typeclass derivation gymnastics. The solution becomes messy however. //GeneratedCompanion.scala
trait GeneratedCompanion[Service[*[_], _]] {
type Doms[Dom[_]]
type Cods[Cod[_]]
implicit final def serviceCompanion: GeneratedCompanion[Service] = this
...
}
// TestServiceFs2Grpc.scala
object TestServiceFs2Grpc {
...
case class Doms[Dom[_]](
dom0: Dom[hello.world.TestMessage]
)
object Doms {
implicit def typeclassInstance[Dom[_]](implicit
dom0: Dom[hello.world.TestMessage]
): Doms[Dom] = Doms(
dom0
)
}
case class Cods[Cod[_]](
cod0: Cod[hello.world.TestMessage]
)
object Cods {
implicit def typeclassInstance[Cod[_]](implicit
cod0: Cod[hello.world.TestMessage]
): Cods[Cod] = Cods(
cod0
)
}
} Alternatively we could relax the solution by removing From my observation, users usually parse the protobuf structures into more idiomatic scala datatypes so maybe the |
I have also introduced a A usecase for this is for instance passing authorization type UserId = String
type Auth[F[_]] = cats.mtl.Ask[F, UserId]
type Authed[A] = Kleisli[IO, UserId, A]
...
trait MyService[F[_]] {
def myRequest(req: MyRequest, ctx: Metadata): F[MyResponse]
}
val myServiceAspect = new ServiceAspect[Authed, IO, Trivial, Trivial, Metadata] {
def visitUnaryToUnary[Req, Res](
callCtx: ServerCallContext[Req, Res, Dom, Cod],
req: Req,
next: (Req, Metadata) => Authed[Res]
): IO[Res] =
extractAuth(callCtx.metadata).flatMap(auth => next(req, callCtx.metadata).run(auth))
}
def makeMyService[F[_]](implicit auth: Auth[F]) = new MyService[F] {
def myRequest(req: MyRequest, ctx: Metadata): F[MyResponse] =
auth.ask[UserId].flatMap(userId => handleThings(userId, req))
}
Dispatcher[IO].use{ d =>
MyService.serviceTrivial[Authed, IO, Metadata](
d,
makeMyService[Authed],
myServiceAspect,
ServerOption.default
)
} |
I have also explored a solution that removed the Example output for TestServiceFs2Grpcpackage hello.world
import _root_.cats.syntax.all._
trait TestServiceFs2Grpc[F[_], A] {
def noStreaming(request: hello.world.TestMessage, ctx: A): F[hello.world.TestMessage]
def clientStreaming(request: _root_.fs2.Stream[F, hello.world.TestMessage], ctx: A): F[hello.world.TestMessage]
def serverStreaming(request: hello.world.TestMessage, ctx: A): _root_.fs2.Stream[F, hello.world.TestMessage]
def bothStreaming(request: _root_.fs2.Stream[F, hello.world.TestMessage], ctx: A): _root_.fs2.Stream[F, hello.world.TestMessage]
}
object TestServiceFs2Grpc extends _root_.fs2.grpc.GeneratedCompanion[TestServiceFs2Grpc] {
def mkClientFull[F[_], G[_]: _root_.cats.effect.Async, A](
dispatcher: _root_.cats.effect.std.Dispatcher[G],
channel: _root_.io.grpc.Channel,
clientAspect: _root_.fs2.grpc.client.ClientAspect[F, G, A],
clientOptions: _root_.fs2.grpc.client.ClientOptions
): TestServiceFs2Grpc[F, A] = new TestServiceFs2Grpc[F, A] {
def noStreaming(request: hello.world.TestMessage, ctx: A): F[hello.world.TestMessage] =
clientAspect.visitUnaryToUnary[hello.world.TestMessage, hello.world.TestMessage](
_root_.fs2.grpc.client.ClientCallContext(ctx, hello.world.TestServiceGrpc.METHOD_NO_STREAMING),
request,
(req, m) => _root_.fs2.grpc.client.Fs2ClientCall[G](channel, hello.world.TestServiceGrpc.METHOD_NO_STREAMING, dispatcher, clientOptions).flatMap(_.unaryToUnaryCall(req, m))
)
def clientStreaming(request: _root_.fs2.Stream[F, hello.world.TestMessage], ctx: A): F[hello.world.TestMessage] =
clientAspect.visitStreamingToUnary[hello.world.TestMessage, hello.world.TestMessage](
_root_.fs2.grpc.client.ClientCallContext(ctx, hello.world.TestServiceGrpc.METHOD_CLIENT_STREAMING),
request,
(req, m) => _root_.fs2.grpc.client.Fs2ClientCall[G](channel, hello.world.TestServiceGrpc.METHOD_CLIENT_STREAMING, dispatcher, clientOptions).flatMap(_.streamingToUnaryCall(req, m))
)
def serverStreaming(request: hello.world.TestMessage, ctx: A): _root_.fs2.Stream[F, hello.world.TestMessage] =
clientAspect.visitUnaryToStreaming[hello.world.TestMessage, hello.world.TestMessage](
_root_.fs2.grpc.client.ClientCallContext(ctx, hello.world.TestServiceGrpc.METHOD_SERVER_STREAMING),
request,
(req, m) => _root_.fs2.Stream.eval(_root_.fs2.grpc.client.Fs2ClientCall[G](channel, hello.world.TestServiceGrpc.METHOD_SERVER_STREAMING, dispatcher, clientOptions)).flatMap(_.unaryToStreamingCall(req, m))
)
def bothStreaming(request: _root_.fs2.Stream[F, hello.world.TestMessage], ctx: A): _root_.fs2.Stream[F, hello.world.TestMessage] =
clientAspect.visitStreamingToStreaming[hello.world.TestMessage, hello.world.TestMessage](
_root_.fs2.grpc.client.ClientCallContext(ctx, hello.world.TestServiceGrpc.METHOD_BOTH_STREAMING),
request,
(req, m) => _root_.fs2.Stream.eval(_root_.fs2.grpc.client.Fs2ClientCall[G](channel, hello.world.TestServiceGrpc.METHOD_BOTH_STREAMING, dispatcher, clientOptions)).flatMap(_.streamingToStreamingCall(req, m))
)
}
protected def serviceBindingFull[F[_], G[_]: _root_.cats.effect.Async, A](
dispatcher: _root_.cats.effect.std.Dispatcher[G],
serviceImpl: TestServiceFs2Grpc[F, A],
serviceAspect: _root_.fs2.grpc.server.ServiceAspect[F, G, A],
serverOptions: _root_.fs2.grpc.server.ServerOptions
) = {
_root_.io.grpc.ServerServiceDefinition
.builder(hello.world.TestServiceGrpc.SERVICE)
.addMethod(
hello.world.TestServiceGrpc.METHOD_NO_STREAMING,
_root_.fs2.grpc.server.Fs2ServerCallHandler[G](dispatcher, serverOptions).unaryToUnaryCall[hello.world.TestMessage, hello.world.TestMessage]{ (r, m) =>
serviceAspect.visitUnaryToUnary[hello.world.TestMessage, hello.world.TestMessage](
_root_.fs2.grpc.server.ServerCallContext(m, hello.world.TestServiceGrpc.METHOD_NO_STREAMING),
r,
(r, m) => serviceImpl.noStreaming(r, m)
)
}
)
.addMethod(
hello.world.TestServiceGrpc.METHOD_CLIENT_STREAMING,
_root_.fs2.grpc.server.Fs2ServerCallHandler[G](dispatcher, serverOptions).streamingToUnaryCall[hello.world.TestMessage, hello.world.TestMessage]{ (r, m) =>
serviceAspect.visitStreamingToUnary[hello.world.TestMessage, hello.world.TestMessage](
_root_.fs2.grpc.server.ServerCallContext(m, hello.world.TestServiceGrpc.METHOD_CLIENT_STREAMING),
r,
(r, m) => serviceImpl.clientStreaming(r, m)
)
}
)
.addMethod(
hello.world.TestServiceGrpc.METHOD_SERVER_STREAMING,
_root_.fs2.grpc.server.Fs2ServerCallHandler[G](dispatcher, serverOptions).unaryToStreamingCall[hello.world.TestMessage, hello.world.TestMessage]{ (r, m) =>
serviceAspect.visitUnaryToStreaming[hello.world.TestMessage, hello.world.TestMessage](
_root_.fs2.grpc.server.ServerCallContext(m, hello.world.TestServiceGrpc.METHOD_SERVER_STREAMING),
r,
(r, m) => serviceImpl.serverStreaming(r, m)
)
}
)
.addMethod(
hello.world.TestServiceGrpc.METHOD_BOTH_STREAMING,
_root_.fs2.grpc.server.Fs2ServerCallHandler[G](dispatcher, serverOptions).streamingToStreamingCall[hello.world.TestMessage, hello.world.TestMessage]{ (r, m) =>
serviceAspect.visitStreamingToStreaming[hello.world.TestMessage, hello.world.TestMessage](
_root_.fs2.grpc.server.ServerCallContext(m, hello.world.TestServiceGrpc.METHOD_BOTH_STREAMING),
r,
(r, m) => serviceImpl.bothStreaming(r, m)
)
}
)
.build()
}
} |
In our codebases we often convert protobuf structures into more idiomatic scala datatypes, either by hand or have typemappers in ScalaPB for request/response payload types. What does the removal of I think your latest changes are less intrusive and if they solve the issue with regards to tracing and auth, then it is perhaps better with this simpler solution? |
I don't think any features that people need (from scanning the related issues) are impacted by removing these.
Yes let's move forward with this. |
Sound like a plan 👍 |
@ValdemarGr Perhaps getting more eyes from typelevel on the PR would be a good idea? |
Yes that would be awesome. |
@rossabaker @armanbilge @fiadliel What do you guys think about this change? :) |
quite excited about this change, hoping it will make reusable |
Is there any update on this PR's progress? Is something blocking it or does it need reviewing? |
I think the last event in this PR was tagging some typelevel maintainers for reviewing the code. |
@ahjohannessen is there anyone else that can be asked to review this? Would be great to progress this |
I haven't used gRPC in a long time, so I don't have strong opinions here. |
Has there been any progress on this? This functionality would be very useful for observe-ability functionality. |
Initial draft for #668
Example output for TestServiceFs2Grpc