For each proto file that contains services definition, ZIO gRPC generates a Scala
object that will contain service definitions for all services in that file. The
object name would be the proto file name prefixed with
Zio. It would reside in the same Scala package that ScalaPB will use for definitions in that file.
You can read more on how ScalaPB determines the Scala package name and how can this be customized in ScalaPB's documentation.
Inside the object, for each service
ServiceName that is defined in a
.proto file, the following structure is generated:
ZServiceName is to be extended when implementing a server for this service. The trait takes two type parameters:
Rrepresentes the dependencies of the service. All the effects being returned by these methods depend on
Rto encode this dependency.
Contextrepresents any domain object that you would like your RPC methods to have available in the environment.
You can set both
Context to be
Any when implementing a service to indicate that the service does not have any dependencies or expectations from the environment. Since it is very common situation, especially when getting started, you can have your service implementation extends
ServiceName which is a type alias to
Learn more about using context and dependencies in the next section.
Any means that there are no dependencies? All Scala objects are instances of
Any. Therefore, any object that is provided as a dependency to our service would satisfy being of type
Any. In other words, there is no specific instance type required.
The easiest way to run a service, is to create an object that extends
You can also override
def port: Int to set a port number (by default port 9000 is used).
ServiceList contains additional methods to add services to the service list that can be used when the service must be created effectfully, or wrapped in a managed, or provided to you as a layer.
The generated client follows ZIO's module pattern:
We have two ways to use a client: through a managed resource, or through a layer. In both cases, we start by creating a
ZManagedChannel, which represents a communication channel to a gRPC server as a managed resource. Since it is wrapped in ZIO's
Managed, proper shutdown of the channel is guaranteed:
Creating a channel:
ZManagedChannel represent a virtual connection to a conceptual endpoint to perform RPCs. A channel can have many actual connection to the endpoint. Therefore, it is very common to have a single service client for each RPC service you need to connect to. You can create a
ZLayer to provide this service using the
live method on the client companion object. Then simply write your logic using the accessor methods. Finally, inject the layer using
provideCustomLayer at the top of your app:
Here the application is broken to multiple value assignments so you can see the types.
The first effect
myAppLogicNeedsEnv uses accessor functions, which makes it depend on an environment of type
ServiceNameClient. It chains the
unary RPC with printing the result to the console, and hence the final inferred effect type is
ServiceNameClient with Console. Once we provide our custom layer, the effect type is
ZEnv, which we can use with ZIO's run method.
As an alternative to using ZLayer, you can use the client through a managed resource:
Since the service acquistion (through the ZManaged) can fail with a
Throwable, and the RPC effects of ZIO gRPC can fail with
Status (which is not a subtype of
Throwable), we use
mapError to map the RPC error to a
StatusRuntimeException. This way, the resulting effect can fail with a