Skip to content

Commit 7f6b8d5

Browse files
committed
RPC Unary Services: IsEmpty
1 parent ce5ed4d commit 7f6b8d5

File tree

13 files changed

+157
-116
lines changed

13 files changed

+157
-116
lines changed

README.md

+104
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
- [Client](#client)
1313
- [Evolving the Avro schema](#evolving-the-avro-schema)
1414
- [Protocol](#protocol-1)
15+
- [Unary RPC service: `IsEmpty`](#unary-rpc-service-isempty)
16+
- [Protocol](#protocol-2)
17+
- [Server](#server-1)
18+
- [Client](#client-1)
19+
- [Result](#result)
1520

1621
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
1722

@@ -353,6 +358,105 @@ INFO - PeopleService - Request: PeopleRequest(baz)
353358
INFO - PeopleService - Sending response: Person(baz,17,(206) 812-1984)
354359
```
355360

361+
## Unary RPC service: `IsEmpty`
362+
363+
Having said this, now it's the right moment to get started to develop the features of the SmartHome, and discard the `People` stuff. As we said above, we want also to build a unary RPC service to let clients know if there is somebody in the home or there is not.
364+
365+
### Protocol
366+
367+
In order to show another way to define protocols, we are going to express our models and services using directly Scala code, and using **ProtocolBuffer** as serialiser instead of **Avro**.
368+
369+
So the protocol module can adopt know this shape (of course we should also discard the `idlgens` references at `ProjectPlugin.scala` and `plugins.sbt`):
370+
371+
```scala
372+
├── protocol
373+
   └── src
374+
   └── main
375+
   └── scala
376+
   └── protocol
377+
   ├── Messages.scala
378+
   └── SmartHomeService.scala
379+
```
380+
381+
**_Messages.scala_**
382+
383+
Where we defined the messages flowing through the wire:
384+
385+
```scala
386+
@message
387+
final case class IsEmptyRequest()
388+
389+
@message
390+
final case class IsEmptyResponse(result: Boolean)
391+
```
392+
393+
**_SmartHomeService.scala_**
394+
395+
Where we defined interface of the RPC service:
396+
397+
```scala
398+
@service(Protobuf) trait SmartHomeService[F[_]] {
399+
400+
def isEmpty(request: IsEmptyRequest): F[IsEmptyResponse]
401+
402+
}
403+
```
404+
405+
### Server
406+
407+
Now, we have to implement an interpreter for the new service `SmartHomeService`:
408+
409+
```scala
410+
class SmartHomeServiceHandler[F[_]: Sync: Logger] extends SmartHomeService[F] {
411+
val serviceName = "SmartHomeService"
412+
413+
override def isEmpty(request: IsEmptyRequest): F[IsEmptyResponse] =
414+
Logger[F].info(s"$serviceName - Request: $request").as(IsEmptyResponse(true))
415+
416+
}
417+
```
418+
419+
And bind it to the gRPC server:
420+
421+
```scala
422+
val grpcConfigs: List[GrpcConfig] = List(AddService(SmartHomeService.bindService[F]))
423+
```
424+
425+
### Client
426+
427+
And the client, of course, needs an algebra to describe the same operation:
428+
429+
```scala
430+
trait SmartHomeServiceApi[F[_]] {
431+
def isEmpty(): F[Boolean]
432+
}
433+
```
434+
435+
That will be called when the app is running
436+
437+
```scala
438+
for {
439+
serviceApi <- SmartHomeServiceApi.createInstance(config.host.value, config.port.value)
440+
_ <- Stream.eval(serviceApi.isEmpty)
441+
} yield StreamApp.ExitCode.Success
442+
```
443+
444+
### Result
445+
446+
When we run the client now with `sbt runClient` we get:
447+
448+
```bash
449+
INFO - Created new RPC client for (localhost,19683)
450+
INFO - Result: IsEmptyResponse(true)
451+
INFO - Removed 1 RPC clients from cache.
452+
```
453+
454+
And the server log the request as expected:
455+
456+
```bash
457+
INFO - SmartHomeService - Request: IsEmptyRequest()
458+
```
459+
356460
<!-- DOCTOC SKIP -->
357461
# Copyright
358462

Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package com.fortyseven.client
22

33
import cats.effect.{Effect, IO, Timer}
4-
import cats.instances.list._
5-
import cats.syntax.traverse._
64
import com.fortyseven.commons._
75
import com.fortyseven.commons.config.ServiceConfig
8-
import com.fortyseven.protocol.Person
96
import fs2.{Stream, StreamApp}
107
import io.chrisdavenport.log4cats.Logger
118
import monix.execution.Scheduler
@@ -18,11 +15,9 @@ class ClientProgram[F[_]: Effect: Logger] extends AppBoot[F] {
1815

1916
override def appStream(config: ServiceConfig): fs2.Stream[F, StreamApp.ExitCode] =
2017
for {
21-
peopleApi <- PeopleServiceApi.createInstance(config.host.value, config.port.value)
22-
exitCode <- Stream
23-
.eval(List("foo", "bar", "baz").traverse[F, Person](peopleApi.getPersonByName))
24-
.as(StreamApp.ExitCode.Success)
25-
} yield exitCode
18+
serviceApi <- SmartHomeServiceApi.createInstance(config.host.value, config.port.value)
19+
_ <- Stream.eval(serviceApi.isEmpty)
20+
} yield StreamApp.ExitCode.Success
2621
}
2722

2823
object ClientApp extends ClientProgram[IO]

client/src/main/scala/client/PeopleServiceApi.scala renamed to client/src/main/scala/client/SmartHomeServiceApi.scala

+12-14
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,22 @@ import monix.execution.Scheduler
1111

1212
import scala.concurrent.duration._
1313

14-
trait PeopleServiceApi[F[_]] {
15-
16-
def getPersonByName(name: String): F[Person]
14+
trait SmartHomeServiceApi[F[_]] {
1715

16+
def isEmpty: F[Boolean]
1817
}
1918

20-
object PeopleServiceApi {
19+
object SmartHomeServiceApi {
2120

22-
def apply[F[_]: Effect](clientRPCF: F[PeopleService.Client[F]])(
21+
def apply[F[_]: Effect](clientRPCF: F[SmartHomeService.Client[F]])(
2322
implicit L: Logger[F]
24-
): PeopleServiceApi[F] = new PeopleServiceApi[F] {
25-
override def getPersonByName(name: String): F[Person] =
23+
): SmartHomeServiceApi[F] = new SmartHomeServiceApi[F] {
24+
override def isEmpty: F[Boolean] =
2625
for {
2726
clientRPC <- clientRPCF
28-
_ <- L.info(s"Request: $name")
29-
result <- clientRPC.getPerson(PeopleRequest(name))
27+
result <- clientRPC.isEmpty(IsEmptyRequest())
3028
_ <- L.info(s"Result: $result")
31-
} yield result.person
29+
} yield result.result
3230
}
3331

3432
def createInstance[F[_]: Effect](
@@ -39,10 +37,10 @@ object PeopleServiceApi {
3937
removeUnusedAfter: FiniteDuration = 1.hour)(
4038
implicit L: Logger[F],
4139
TM: Timer[F],
42-
S: Scheduler): fs2.Stream[F, PeopleServiceApi[F]] = {
40+
S: Scheduler): fs2.Stream[F, SmartHomeServiceApi[F]] = {
4341

44-
def fromChannel(channel: ManagedChannel): PeopleService.Client[F] =
45-
PeopleService.clientFromChannel(channel, CallOptions.DEFAULT)
42+
def fromChannel(channel: ManagedChannel): SmartHomeService.Client[F] =
43+
SmartHomeService.clientFromChannel(channel, CallOptions.DEFAULT)
4644

4745
ClientRPC
4846
.clientCache(
@@ -51,6 +49,6 @@ object PeopleServiceApi {
5149
tryToRemoveUnusedEvery,
5250
removeUnusedAfter,
5351
fromChannel)
54-
.map(cache => PeopleServiceApi(cache.getClient))
52+
.map(cache => SmartHomeServiceApi(cache.getClient))
5553
}
5654
}

project/ProjectPlugin.scala

+1-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import freestyle.rpc.idlgen.IdlGenPlugin.autoImport._
21
import org.scalafmt.sbt.ScalafmtPlugin.autoImport._
32
import sbt.Keys._
43
import sbt._
@@ -35,12 +34,7 @@ object ProjectPlugin extends AutoPlugin {
3534
lazy val commonsSettings: Seq[Def.Setting[_]] = logSettings ++ configSettings
3635

3736
lazy val rpcProtocolSettings: Seq[Def.Setting[_]] = Seq(
38-
idlType := "avro",
39-
srcGenSerializationType := "AvroWithSchema",
40-
sourceGenerators in Compile += (srcGen in Compile).taskValue,
41-
libraryDependencies ++= Seq(
42-
"io.frees" %% "frees-rpc-client-core" % V.freestyleRPC
43-
)
37+
libraryDependencies ++= Seq("io.frees" %% "frees-rpc-client-core" % V.freestyleRPC)
4438
)
4539

4640
lazy val rpcClientSettings: Seq[Def.Setting[_]] = logSettings ++ Seq(

project/plugins.sbt

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1")
2-
addSbtPlugin("de.heikoseeberger" % "sbt-groll" % "6.0.0")
3-
addSbtPlugin("io.frees" % "sbt-frees-rpc-idlgen" % "0.14.1")
1+
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1")
2+
addSbtPlugin("de.heikoseeberger" % "sbt-groll" % "6.0.0")

protocol/src/main/resources/People.avdl

-18
This file was deleted.

protocol/src/main/resources/PeopleService.avdl

-6
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.fortyseven.protocol
2+
3+
import freestyle.rpc.protocol.message
4+
5+
@message
6+
final case class IsEmptyRequest()
7+
8+
@message
9+
final case class IsEmptyResponse(result: Boolean)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.fortyseven.protocol
2+
3+
import freestyle.rpc.protocol.{service, Protobuf}
4+
5+
@service(Protobuf) trait SmartHomeService[F[_]] {
6+
7+
def isEmpty(request: IsEmptyRequest): F[IsEmptyResponse]
8+
9+
}

server/src/main/scala/server/PeopleRepository.scala

-39
This file was deleted.

server/src/main/scala/server/PeopleServiceHandler.scala

-18
This file was deleted.

server/src/main/scala/server/ServerApp.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import cats.syntax.flatMap._
55
import cats.syntax.functor._
66
import com.fortyseven.commons._
77
import com.fortyseven.commons.config.ServiceConfig
8-
import com.fortyseven.protocol.PeopleService
8+
import com.fortyseven.protocol.SmartHomeService
99
import freestyle.rpc.server.{AddService, GrpcConfig, GrpcServer}
1010
import fs2.{Stream, StreamApp}
1111
import io.chrisdavenport.log4cats.Logger
@@ -19,9 +19,9 @@ class ServerProgram[F[_]: Effect: Logger] extends AppBoot[F] {
1919

2020
override def appStream(config: ServiceConfig): fs2.Stream[F, StreamApp.ExitCode] = {
2121

22-
implicit val PS: PeopleService[F] = new PeopleServiceHandler[F]
22+
implicit val SHS: SmartHomeService[F] = new SmartHomeServiceHandler[F]
2323

24-
val grpcConfigs: List[GrpcConfig] = List(AddService(PeopleService.bindService[F]))
24+
val grpcConfigs: List[GrpcConfig] = List(AddService(SmartHomeService.bindService[F]))
2525

2626
Stream.eval(
2727
for {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.fortyseven.server
2+
3+
import cats.effect.Sync
4+
import cats.syntax.functor._
5+
import com.fortyseven.protocol.{IsEmptyRequest, IsEmptyResponse, SmartHomeService}
6+
import io.chrisdavenport.log4cats.Logger
7+
8+
class SmartHomeServiceHandler[F[_]: Sync: Logger] extends SmartHomeService[F] {
9+
val serviceName = "SmartHomeService"
10+
11+
override def isEmpty(request: IsEmptyRequest): F[IsEmptyResponse] =
12+
Logger[F].info(s"$serviceName - Request: $request").as(IsEmptyResponse(true))
13+
14+
}

0 commit comments

Comments
 (0)