Skip to content

Commit 56dd038

Browse files
authored
Add ZIO client-server example (#178)
1 parent 4d465f1 commit 56dd038

File tree

17 files changed

+429
-172
lines changed

17 files changed

+429
-172
lines changed

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ Built-in [GRPC Transcoding](https://cloud.google.com/endpoints/docs/grpc/transco
148148

149149
![Maven Central](https://img.shields.io/maven-central/v/me.ivovk/connect-rpc-scala-core_3?style=flat-square&color=green)
150150

151-
Check [examples](https://github.com/igor-vovk/connect-rpc-scala/tree/main/example) directory for some examples of using
151+
Check [examples](https://github.com/igor-vovk/connect-rpc-scala/tree/main/examples) directory for some examples of using
152152
the library.
153153

154154
In case of http4s frontend, you will also need one of `http4s` server implementations, Ember in this case:
@@ -249,7 +249,6 @@ How-tos that go beyond the basic usage:
249249

250250
* [How to support CORS-requests](docs/supporting-cors.md)
251251
* [How to integrate with OpenTelemetry](docs/integrating-with-otel.md)
252-
* [How to work with ZIO](docs/integrating-with-zio.md)
253252
* [How to implement Kubernetes health checks](docs/kubernetes-health-checks.md)
254253

255254
## Development

build.sbt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ lazy val examples = project.in(file("examples"))
110110
.aggregate(
111111
example_connectrpc_grpc_servers,
112112
example_client_server,
113+
example_zio_client_server,
113114
)
114115
.settings(noPublish)
115116

@@ -125,7 +126,6 @@ lazy val example_connectrpc_grpc_servers = project.in(file("examples/connectrpc_
125126
"org.http4s" %% "http4s-ember-server" % Versions.http4s,
126127
"ch.qos.logback" % "logback-classic" % Versions.logback % Runtime,
127128
),
128-
Compile / mainClass := Some("examples.connectrpc_grpc_servers.Main"),
129129
)
130130

131131
lazy val example_client_server = project.in(file("examples/client_server"))
@@ -140,7 +140,24 @@ lazy val example_client_server = project.in(file("examples/client_server"))
140140
"org.http4s" %% "http4s-ember-client" % Versions.http4s,
141141
"ch.qos.logback" % "logback-classic" % Versions.logback % Runtime,
142142
),
143-
Compile / mainClass := Some("examples.client_server.Main"),
143+
)
144+
145+
lazy val example_zio_client_server = project.in(file("examples/zio_client_server"))
146+
.dependsOn(http4s)
147+
.settings(
148+
noPublish,
149+
commonDeps,
150+
Compile / PB.targets := Seq(
151+
scalapb.gen() -> (Compile / sourceManaged).value / "scalapb",
152+
scalapb.zio_grpc.ZioCodeGenerator -> (Compile / sourceManaged).value / "scalapb",
153+
),
154+
libraryDependencies ++= Seq(
155+
"org.http4s" %% "http4s-ember-server" % Versions.http4s,
156+
"org.http4s" %% "http4s-ember-client" % Versions.http4s,
157+
"dev.zio" %% "zio" % "2.1.19",
158+
"dev.zio" %% "zio-interop-cats" % "23.1.0.5",
159+
"ch.qos.logback" % "logback-classic" % Versions.logback % Runtime,
160+
),
144161
)
145162

146163
lazy val root = (project in file("."))

docs/integrating-with-zio.md

Lines changed: 0 additions & 7 deletions
This file was deleted.

examples/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22

33
- **client_server**: Shows how to create a simple client-server application communicating over HTTP using ConnectRPC.
44
- **connectrpc_grpc_servers**: Example of starting both a gRPC server and a ConnectRPC server in the same component on
5-
different ports.
5+
different ports.
6+
- **zio_client_server**: Demonstrates how to create a client-server application using ZIO and ConnectRPC.

examples/client_server/src/main/scala/examples/client_server/Dependencies.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package examples.client_server
33
import cats.effect.{IO, Resource}
44
import com.comcast.ip4s.*
55
import examples.v1.eliza.ElizaServiceFs2Grpc
6-
import io.grpc.{Channel, Metadata, ServerServiceDefinition}
6+
import io.grpc.{Metadata, ServerServiceDefinition}
77
import me.ivovk.cedi.Allocator
88
import me.ivovk.cedi.syntax.*
99
import org.http4s.HttpApp
@@ -12,6 +12,7 @@ import org.http4s.ember.client.EmberClientBuilder
1212
import org.http4s.ember.server.EmberServerBuilder
1313
import org.http4s.implicits.uri
1414
import org.ivovk.connect_rpc_scala.http4s.{ConnectHttp4sChannelBuilder, ConnectHttp4sRouteBuilder}
15+
1516
import scala.concurrent.duration.*
1617

1718
object Dependencies {

examples/zio_client_server/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# ConnectRPC ZIO client-server communication example
2+
3+
This example demonstrates launching a ConnectRPC server and establishing communication with it via a ConnectRPC client with ZIO.
4+
5+
6+
To run the app execute the following command in the root of the project:
7+
8+
```bash
9+
sbt example_zio_client_server/run
10+
```
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
syntax = "proto3";
2+
3+
package examples.v1;
4+
5+
message SayRequest {
6+
string sentence = 1;
7+
}
8+
9+
message SayResponse {
10+
string sentence = 1;
11+
}
12+
13+
service ElizaService {
14+
rpc Say(SayRequest) returns (SayResponse) {}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
syntax = "proto3";
2+
3+
package examples.v1;
4+
5+
import "scalapb/scalapb.proto";
6+
7+
option (scalapb.options) = {
8+
scope: PACKAGE
9+
flat_package: false
10+
lenses: false
11+
enum_value_naming: CAMEL_CASE
12+
enum_strip_prefix: true
13+
preserve_unknown_fields: false
14+
scala3_sources: true
15+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<!DOCTYPE configuration>
3+
4+
<configuration>
5+
6+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
7+
<encoder>
8+
<pattern>%-4relative %-5level %logger{5} -%kvp- %msg%n</pattern>
9+
</encoder>
10+
</appender>
11+
12+
<root level="INFO">
13+
<appender-ref ref="STDOUT"/>
14+
</root>
15+
<logger name="org.ivovk" level="TRACE"/>
16+
</configuration>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package examples.zio
2+
3+
import com.comcast.ip4s.{host, port}
4+
import examples.v1.eliza.ZioEliza
5+
import fs2.io.net.Network
6+
import io.grpc.{ManagedChannel, ServerServiceDefinition, StatusException}
7+
import org.http4s.HttpApp
8+
import org.http4s.client.Client as HttpClient
9+
import org.http4s.ember.client.EmberClientBuilder
10+
import org.http4s.ember.server.EmberServerBuilder
11+
import org.http4s.implicits.uri
12+
import org.http4s.server.Server as HttpServer
13+
import org.ivovk.connect_rpc_scala.http4s.{ConnectHttp4sChannelBuilder, ConnectHttp4sRouteBuilder}
14+
import scalapb.zio_grpc.ZChannel
15+
import zio.*
16+
import zio.interop.catz.*
17+
18+
import scala.concurrent.duration.*
19+
20+
object Dependencies {
21+
22+
given Network[Task] = Network.forAsync[Task]
23+
24+
// Layer for the Eliza service implementation
25+
val elizaServiceImpl: ULayer[ZioEliza.GElizaService[Any, StatusException]] =
26+
ZLayer.succeed(new ElizaService().asGeneric)
27+
28+
// Layer for the gRPC server service definition
29+
val elizaServiceGrpcServerDefinition
30+
: RLayer[ZioEliza.GElizaService[Any, StatusException], ServerServiceDefinition] =
31+
ZLayer.fromZIO {
32+
ZIO.service[ZioEliza.GElizaService[Any, StatusException]].flatMap { elizaServiceImpl =>
33+
ZioEliza.GElizaService.genericBindable.bind(elizaServiceImpl)
34+
}
35+
}
36+
37+
// Layer for the http4s app
38+
val httpServerApp: RLayer[ServerServiceDefinition, HttpApp[Task]] = ZLayer.scoped {
39+
ZIO.service[ServerServiceDefinition].flatMap { elizaServiceGrpcServerDefinition =>
40+
ConnectHttp4sRouteBuilder.forService[Task](elizaServiceGrpcServerDefinition).build.toScopedZIO
41+
}
42+
}
43+
44+
// Layer for the http4s server
45+
val connectRpcServer: RLayer[HttpApp[Task], HttpServer] = ZLayer.scoped {
46+
ZIO.service[HttpApp[Task]].flatMap { httpApp =>
47+
EmberServerBuilder.default[Task]
48+
.withHost(host"0.0.0.0")
49+
.withPort(port"8080")
50+
.withHttpApp(httpApp)
51+
.withShutdownTimeout(1.second)
52+
.build
53+
.toScopedZIO
54+
}
55+
}
56+
57+
// Layer for the http4s client
58+
val httpClient: Layer[Throwable, HttpClient[Task]] = ZLayer.scoped(
59+
EmberClientBuilder.default[Task].build.toScopedZIO
60+
)
61+
62+
// Layer for the connect-rpc channel
63+
val connectRpcChannel: RLayer[HttpClient[Task], ManagedChannel] = ZLayer.scoped {
64+
ZIO.service[HttpClient[Task]].flatMap { httpClient =>
65+
ConnectHttp4sChannelBuilder[Task](httpClient)
66+
.build(uri"http://localhost:8080/")
67+
.map(_.toManagedChannel)
68+
.toScopedZIO
69+
}
70+
}
71+
72+
// Layer for the Eliza client
73+
val elizaClient: RLayer[ManagedChannel, ZioEliza.ElizaServiceClient] = ZLayer.scoped {
74+
ZIO.service[ManagedChannel].flatMap { connectRpcChannel =>
75+
ZioEliza.ElizaServiceClient.scoped(
76+
ZIO.acquireRelease(
77+
ZIO.attempt(new ZChannel(connectRpcChannel, None, Seq.empty))
78+
)(_.shutdown().ignore)
79+
)
80+
}
81+
}
82+
}

0 commit comments

Comments
 (0)