diff --git a/404.html b/404.html index 826ef43b..406ada57 100644 --- a/404.html +++ b/404.html @@ -6,13 +6,13 @@ Page Not Found | ZIO gRPC - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/assets/js/2ed64f4e.37db4340.js b/assets/js/2ed64f4e.0ba90eb7.js similarity index 98% rename from assets/js/2ed64f4e.37db4340.js rename to assets/js/2ed64f4e.0ba90eb7.js index d2765913..8592af72 100644 --- a/assets/js/2ed64f4e.37db4340.js +++ b/assets/js/2ed64f4e.0ba90eb7.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[596],{3905:function(e,t,n){n.d(t,{Zo:function(){return u},kt:function(){return d}});var r=n(7294);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function c(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var s=r.createContext({}),p=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):c(c({},t),e)),n},u=function(e){var t=p(e.components);return r.createElement(s.Provider,{value:t},e.children)},l={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},m=r.forwardRef((function(e,t){var n=e.components,o=e.mdxType,a=e.originalType,s=e.parentName,u=i(e,["components","mdxType","originalType","parentName"]),m=p(n),d=o,f=m["".concat(s,".").concat(d)]||m[d]||l[d]||a;return n?r.createElement(f,c(c({ref:t},u),{},{components:n})):r.createElement(f,c({ref:t},u))}));function d(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var a=n.length,c=new Array(a);c[0]=m;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i.mdxType="string"==typeof e?e:o,c[1]=i;for(var p=2;p ZIO[Any, StatusException, A]): RequestContext => ZIO[Any, StatusException, A] = {\n rc => io(rc).zipLeft(accessLog(rc)).tapErrorCause(logCause(rc, _))\n }\n\n override def stream[A](\n io: Any => ZStream[Any, StatusException, A]): RequestContext => ZStream[Any, StatusException, A] = {\n rc => (io(rc) ++ ZStream.fromZIO(accessLog(rc)).drain).onError(logCause(rc, _))\n }\n}\n")),(0,a.kt)("p",null,"and then we apply it to our service:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-scala"},'import myexample.testservice.ZioTestservice._\nimport myexample.testservice.{Request, Response}\n\nobject MyService extends SimpleService {\n def sayHello(req: Request): ZIO[Any, StatusException, Response] =\n ZIO.succeed(Response(s"Hello user"))\n}\n\n// Note we now have a service with a RequestContext as context.\nval decoratedService: ZSimpleService[RequestContext] =\n MyService.transform(new LoggingTransform)\n// decoratedService: ZSimpleService[RequestContext] = myexample.testservice.ZioTestservice$GSimpleService$$anon$5@12bcec48\n')))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[596],{3905:function(e,t,n){n.d(t,{Zo:function(){return u},kt:function(){return d}});var r=n(7294);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function c(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var s=r.createContext({}),p=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):c(c({},t),e)),n},u=function(e){var t=p(e.components);return r.createElement(s.Provider,{value:t},e.children)},l={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},m=r.forwardRef((function(e,t){var n=e.components,o=e.mdxType,a=e.originalType,s=e.parentName,u=i(e,["components","mdxType","originalType","parentName"]),m=p(n),d=o,f=m["".concat(s,".").concat(d)]||m[d]||l[d]||a;return n?r.createElement(f,c(c({ref:t},u),{},{components:n})):r.createElement(f,c({ref:t},u))}));function d(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var a=n.length,c=new Array(a);c[0]=m;var i={};for(var s in t)hasOwnProperty.call(t,s)&&(i[s]=t[s]);i.originalType=e,i.mdxType="string"==typeof e?e:o,c[1]=i;for(var p=2;p ZIO[Any, StatusException, A]): RequestContext => ZIO[Any, StatusException, A] = {\n rc => io(rc).zipLeft(accessLog(rc)).tapErrorCause(logCause(rc, _))\n }\n\n override def stream[A](\n io: Any => ZStream[Any, StatusException, A]): RequestContext => ZStream[Any, StatusException, A] = {\n rc => (io(rc) ++ ZStream.fromZIO(accessLog(rc)).drain).onError(logCause(rc, _))\n }\n}\n")),(0,a.kt)("p",null,"and then we apply it to our service:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-scala"},'import myexample.testservice.ZioTestservice._\nimport myexample.testservice.{Request, Response}\n\nobject MyService extends SimpleService {\n def sayHello(req: Request): ZIO[Any, StatusException, Response] =\n ZIO.succeed(Response(s"Hello user"))\n}\n\n// Note we now have a service with a RequestContext as context.\nval decoratedService: ZSimpleService[RequestContext] =\n MyService.transform(new LoggingTransform)\n// decoratedService: ZSimpleService[RequestContext] = myexample.testservice.ZioTestservice$GSimpleService$$anon$5@14de2eea\n')))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/3a2a6d86.c60601d8.js b/assets/js/3a2a6d86.ffb0bfef.js similarity index 96% rename from assets/js/3a2a6d86.c60601d8.js rename to assets/js/3a2a6d86.ffb0bfef.js index 3fac457f..ee85c96b 100644 --- a/assets/js/3a2a6d86.c60601d8.js +++ b/assets/js/3a2a6d86.ffb0bfef.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[292],{3905:function(e,t,n){n.d(t,{Zo:function(){return l},kt:function(){return m}});var r=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var c=r.createContext({}),p=function(e){var t=r.useContext(c),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},l=function(e){var t=p(e.components);return r.createElement(c.Provider,{value:t},e.children)},d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},u=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,c=e.parentName,l=s(e,["components","mdxType","originalType","parentName"]),u=p(n),m=a,v=u["".concat(c,".").concat(m)]||u[m]||d[m]||i;return n?r.createElement(v,o(o({ref:t},l),{},{components:n})):r.createElement(v,o({ref:t},l))}));function m(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,o=new Array(i);o[0]=u;var s={};for(var c in t)hasOwnProperty.call(t,c)&&(s[c]=t[c]);s.originalType=e,s.mdxType="string"==typeof e?e:a,o[1]=s;for(var p=2;p IO[Status, User]")," to find the user."),(0,i.kt)("p",null,"For example, we can provide a function that returns an effect that always succeeds:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'val fixedUserService =\n MyService.transformContextZIO((rc: RequestContext) => ZIO.succeed(User("foo")))\n// fixedUserService: myexample.testservice.ZioTestservice.GSimpleService[RequestContext, StatusException] = myexample.testservice.ZioTestservice$GSimpleService$$anon$5@4ecec9b8\n')),(0,i.kt)("p",null,"and we got our service with context of type ",(0,i.kt)("inlineCode",{parentName:"p"},"RequestContext")," so it can be bound to a gRPC server."),(0,i.kt)("h3",{id:"accessing-metadata"},"Accessing metadata"),(0,i.kt)("p",null,"Here is how we would extract a user from a metadata header:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'import zio.IO\nimport scalapb.zio_grpc.{ServiceList, ServerMain}\n\nval UserKey = io.grpc.Metadata.Key.of(\n "user-key", io.grpc.Metadata.ASCII_STRING_MARSHALLER)\n// UserKey: io.grpc.Metadata.Key[String] = Key{name=\'user-key\'}\n\ndef findUser(rc: RequestContext): IO[StatusException, User] =\n rc.metadata.get(UserKey).flatMap {\n case Some(name) => ZIO.succeed(User(name))\n case _ => ZIO.fail(\n Status.UNAUTHENTICATED.withDescription("No access!").asException)\n }\n\nval rcService =\n MyService.transformContextZIO(findUser)\n// rcService: myexample.testservice.ZioTestservice.GSimpleService[RequestContext, StatusException] = myexample.testservice.ZioTestservice$GSimpleService$$anon$5@776b7085\n\nobject MyServer extends ServerMain {\n def services = ServiceList.add(rcService)\n}\n')),(0,i.kt)("h3",{id:"context-transformations-that-depends-on-a-service"},"Context transformations that depends on a service"),(0,i.kt)("p",null,"A context transformation may introduce a dependency on another service. For example, you\nmay want to organize your code such that there is a ",(0,i.kt)("inlineCode",{parentName:"p"},"UserDatabase")," service that provides\na ",(0,i.kt)("inlineCode",{parentName:"p"},"fetchUser")," effect that retrieves users from a database. Here is how you can do this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"trait UserDatabase {\n def fetchUser(name: String): IO[StatusException, User]\n}\n\nobject UserDatabase {\n val layer = zio.ZLayer.succeed(\n new UserDatabase {\n def fetchUser(name: String): IO[StatusException, User] =\n ZIO.succeed(User(name))\n })\n}\n")),(0,i.kt)("p",null,"Now, The context transformation effect we apply may introduce an additional environmental dependency to our service. For example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'import zio.Clock._\nimport zio.Duration._\n\nval myServiceAuthWithDatabase: ZIO[UserDatabase, Nothing, ZSimpleService[RequestContext]] =\n ZIO.serviceWith[UserDatabase](\n userDatabase =>\n MyService.transformContextZIO {\n (rc: RequestContext) =>\n rc.metadata.get(UserKey)\n .someOrFail(Status.UNAUTHENTICATED.asException)\n .flatMap(userDatabase.fetchUser(_))\n }\n )\n// myServiceAuthWithDatabase: ZIO[UserDatabase, Nothing, ZSimpleService[RequestContext]] = OnSuccess(\n// trace = "repl.MdocSession.MdocApp.myServiceAuthWithDatabase(context.md:104)",\n// first = Sync(\n// trace = "repl.MdocSession.MdocApp.myServiceAuthWithDatabase(context.md:104)",\n// eval = zio.ZIO$ServiceWithZIOPartiallyApplied$$$Lambda$13275/0x00000001031bf840@23f81348\n// ),\n// successK = zio.ZIO$$$Lambda$13266/0x00000001020fc040@75a1df8b\n// )\n')),(0,i.kt)("p",null,"Now our service can be built from an effect that depends on ",(0,i.kt)("inlineCode",{parentName:"p"},"UserDatabase"),". This effect can be\nadded to a ",(0,i.kt)("inlineCode",{parentName:"p"},"ServiceList")," using ",(0,i.kt)("inlineCode",{parentName:"p"},"addZIO"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"object MyServer2 extends ServerMain {\n def services = ServiceList\n .addZIO(myServiceAuthWithDatabase)\n .provide(UserDatabase.layer)\n}\n")),(0,i.kt)("h2",{id:"using-a-service-as-zlayer"},"Using a service as ZLayer"),(0,i.kt)("p",null,"If you require more flexibility than provided through ",(0,i.kt)("inlineCode",{parentName:"p"},"ServerMain"),", you can construct\nthe server directly."),(0,i.kt)("p",null,"We first turn our service into a ZLayer:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"val myServiceLayer = zio.ZLayer(myServiceAuthWithDatabase)\n// myServiceLayer: zio.ZLayer[UserDatabase, Nothing, ZSimpleService[RequestContext]] = Suspend(\n// self = zio.ZLayer$$$Lambda$13306/0x00000001031b2440@60eb0004\n// )\n")),(0,i.kt)("p",null,"Notice how the dependencies moved to the input side of the ",(0,i.kt)("inlineCode",{parentName:"p"},"ZLayer")," and the resulting layer is of\ntype ",(0,i.kt)("inlineCode",{parentName:"p"},"ZSimpleService[RequestContext]"),"."),(0,i.kt)("p",null,"To use this layer in an app, we can wire it like so:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"import scalapb.zio_grpc.ServerLayer\nimport scalapb.zio_grpc.Server\nimport zio.ZLayer\n\nval serviceList = ServiceList\n .addFromEnvironment[ZSimpleService[RequestContext]]\n// serviceList: ServiceList[Any with ZSimpleService[RequestContext]] = scalapb.zio_grpc.ServiceList@43c125b6\n\nval serverLayer =\n ServerLayer.fromServiceList(\n io.grpc.ServerBuilder.forPort(9000),\n serviceList\n )\n// serverLayer: ZLayer[Any with ZSimpleService[RequestContext], Throwable, Server] = Suspend(\n// self = zio.ZLayer$ScopedEnvironmentPartiallyApplied$$$Lambda$13327/0x00000001031a1040@15c81b6a\n// )\n\nval ourApp =\n ZLayer.make[Server](\n serverLayer,\n myServiceLayer,\n UserDatabase.layer\n )\n// ourApp: ZLayer[Any, Throwable, Server] = Suspend(\n// self = zio.ZLayer$ZLayerProvideSomeOps$$$Lambda$13329/0x00000001031a0840@4a7a82a0\n// )\n\nobject LayeredApp extends zio.ZIOAppDefault {\n def run = ourApp.launch.exitCode\n}\n")),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"serverLayer")," creates a ",(0,i.kt)("inlineCode",{parentName:"p"},"Server")," from a ",(0,i.kt)("inlineCode",{parentName:"p"},"ZSimpleService")," layer and still depends on a ",(0,i.kt)("inlineCode",{parentName:"p"},"UserDatabase"),". Then, ",(0,i.kt)("inlineCode",{parentName:"p"},"ourApp")," feeds a ",(0,i.kt)("inlineCode",{parentName:"p"},"UserDatabase.layer")," into ",(0,i.kt)("inlineCode",{parentName:"p"},"serverLayer")," to produce\na ",(0,i.kt)("inlineCode",{parentName:"p"},"Server")," that doesn't depend on anything. In the ",(0,i.kt)("inlineCode",{parentName:"p"},"run")," method we launch the server layer."),(0,i.kt)("h2",{id:"implementing-a-service-with-dependencies"},"Implementing a service with dependencies"),(0,i.kt)("p",null,"In this scenario, your service depends on two additional services, ",(0,i.kt)("inlineCode",{parentName:"p"},"DepA")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"DepB"),". Following ",(0,i.kt)("a",{parentName:"p",href:"https://zio.dev/reference/service-pattern/"},"ZIO's service pattern"),", we accept the (interaces of the ) dependencies as constructor parameters."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'trait DepA {\n def methodA(param: String): ZIO[Any, Nothing, Int]\n}\n\nobject DepA {\n val layer = ZLayer.succeed[DepA](new DepA {\n def methodA(param: String) = ???\n })\n}\n\nobject DepB {\n val layer = ZLayer.succeed[DepB](new DepB {\n def methodB(param: Float) = ???\n })\n}\n\ntrait DepB {\n def methodB(param: Float): ZIO[Any, Nothing, Double]\n}\n\ncase class MyService2(depA: DepA, depB: DepB) extends ZSimpleService[User] {\n def sayHello(req: Request, user: User): ZIO[Any, StatusException, Response] =\n for {\n num1 <- depA.methodA(user.name)\n num2 <- depB.methodB(12.3f)\n _ <- printLine("I am here $num1 $num2!").orDie\n } yield Response(s"Hello, ${user.name}")\n}\n\nobject MyService2 {\n val layer: ZLayer[DepA with DepB, Nothing, ZSimpleService[RequestContext]] =\n ZLayer.fromFunction {\n (depA: DepA, depB: DepB) =>\n MyService2(depA, depB).transformContextZIO(findUser(_))\n }\n}\n')),(0,i.kt)("p",null,"Our service layer now depends on the ",(0,i.kt)("inlineCode",{parentName:"p"},"DepA")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"DepB")," interfaces. A server can be created like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"object MyServer3 extends zio.ZIOAppDefault {\n\n val serverLayer =\n ServerLayer.fromServiceList(\n io.grpc.ServerBuilder.forPort(9000),\n ServiceList.addFromEnvironment[ZSimpleService[RequestContext]]\n )\n\n val appLayer = ZLayer.make[Server](\n serverLayer,\n DepA.layer,\n DepB.layer,\n MyService2.layer\n )\n\n def run = ourApp.launch.exitCode\n}\n")))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[292],{3905:function(e,t,n){n.d(t,{Zo:function(){return l},kt:function(){return m}});var r=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var c=r.createContext({}),p=function(e){var t=r.useContext(c),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},l=function(e){var t=p(e.components);return r.createElement(c.Provider,{value:t},e.children)},d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},u=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,i=e.originalType,c=e.parentName,l=s(e,["components","mdxType","originalType","parentName"]),u=p(n),m=a,v=u["".concat(c,".").concat(m)]||u[m]||d[m]||i;return n?r.createElement(v,o(o({ref:t},l),{},{components:n})):r.createElement(v,o({ref:t},l))}));function m(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=n.length,o=new Array(i);o[0]=u;var s={};for(var c in t)hasOwnProperty.call(t,c)&&(s[c]=t[c]);s.originalType=e,s.mdxType="string"==typeof e?e:a,o[1]=s;for(var p=2;p IO[Status, User]")," to find the user."),(0,i.kt)("p",null,"For example, we can provide a function that returns an effect that always succeeds:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'val fixedUserService =\n MyService.transformContextZIO((rc: RequestContext) => ZIO.succeed(User("foo")))\n// fixedUserService: myexample.testservice.ZioTestservice.GSimpleService[RequestContext, StatusException] = myexample.testservice.ZioTestservice$GSimpleService$$anon$5@23469e4b\n')),(0,i.kt)("p",null,"and we got our service with context of type ",(0,i.kt)("inlineCode",{parentName:"p"},"RequestContext")," so it can be bound to a gRPC server."),(0,i.kt)("h3",{id:"accessing-metadata"},"Accessing metadata"),(0,i.kt)("p",null,"Here is how we would extract a user from a metadata header:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'import zio.IO\nimport scalapb.zio_grpc.{ServiceList, ServerMain}\n\nval UserKey = io.grpc.Metadata.Key.of(\n "user-key", io.grpc.Metadata.ASCII_STRING_MARSHALLER)\n// UserKey: io.grpc.Metadata.Key[String] = Key{name=\'user-key\'}\n\ndef findUser(rc: RequestContext): IO[StatusException, User] =\n rc.metadata.get(UserKey).flatMap {\n case Some(name) => ZIO.succeed(User(name))\n case _ => ZIO.fail(\n Status.UNAUTHENTICATED.withDescription("No access!").asException)\n }\n\nval rcService =\n MyService.transformContextZIO(findUser)\n// rcService: myexample.testservice.ZioTestservice.GSimpleService[RequestContext, StatusException] = myexample.testservice.ZioTestservice$GSimpleService$$anon$5@75e421a3\n\nobject MyServer extends ServerMain {\n def services = ServiceList.add(rcService)\n}\n')),(0,i.kt)("h3",{id:"context-transformations-that-depends-on-a-service"},"Context transformations that depends on a service"),(0,i.kt)("p",null,"A context transformation may introduce a dependency on another service. For example, you\nmay want to organize your code such that there is a ",(0,i.kt)("inlineCode",{parentName:"p"},"UserDatabase")," service that provides\na ",(0,i.kt)("inlineCode",{parentName:"p"},"fetchUser")," effect that retrieves users from a database. Here is how you can do this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"trait UserDatabase {\n def fetchUser(name: String): IO[StatusException, User]\n}\n\nobject UserDatabase {\n val layer = zio.ZLayer.succeed(\n new UserDatabase {\n def fetchUser(name: String): IO[StatusException, User] =\n ZIO.succeed(User(name))\n })\n}\n")),(0,i.kt)("p",null,"Now, The context transformation effect we apply may introduce an additional environmental dependency to our service. For example:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'import zio.Clock._\nimport zio.Duration._\n\nval myServiceAuthWithDatabase: ZIO[UserDatabase, Nothing, ZSimpleService[RequestContext]] =\n ZIO.serviceWith[UserDatabase](\n userDatabase =>\n MyService.transformContextZIO {\n (rc: RequestContext) =>\n rc.metadata.get(UserKey)\n .someOrFail(Status.UNAUTHENTICATED.asException)\n .flatMap(userDatabase.fetchUser(_))\n }\n )\n// myServiceAuthWithDatabase: ZIO[UserDatabase, Nothing, ZSimpleService[RequestContext]] = OnSuccess(\n// trace = "repl.MdocSession.MdocApp.myServiceAuthWithDatabase(context.md:104)",\n// first = Sync(\n// trace = "repl.MdocSession.MdocApp.myServiceAuthWithDatabase(context.md:104)",\n// eval = zio.ZIO$ServiceWithZIOPartiallyApplied$$$Lambda$15009/0x00000001040ac840@4a9270b4\n// ),\n// successK = zio.ZIO$$$Lambda$15000/0x000000010409e840@7744254c\n// )\n')),(0,i.kt)("p",null,"Now our service can be built from an effect that depends on ",(0,i.kt)("inlineCode",{parentName:"p"},"UserDatabase"),". This effect can be\nadded to a ",(0,i.kt)("inlineCode",{parentName:"p"},"ServiceList")," using ",(0,i.kt)("inlineCode",{parentName:"p"},"addZIO"),":"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"object MyServer2 extends ServerMain {\n def services = ServiceList\n .addZIO(myServiceAuthWithDatabase)\n .provide(UserDatabase.layer)\n}\n")),(0,i.kt)("h2",{id:"using-a-service-as-zlayer"},"Using a service as ZLayer"),(0,i.kt)("p",null,"If you require more flexibility than provided through ",(0,i.kt)("inlineCode",{parentName:"p"},"ServerMain"),", you can construct\nthe server directly."),(0,i.kt)("p",null,"We first turn our service into a ZLayer:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"val myServiceLayer = zio.ZLayer(myServiceAuthWithDatabase)\n// myServiceLayer: zio.ZLayer[UserDatabase, Nothing, ZSimpleService[RequestContext]] = Suspend(\n// self = zio.ZLayer$$$Lambda$15040/0x00000001040c2440@60dd0320\n// )\n")),(0,i.kt)("p",null,"Notice how the dependencies moved to the input side of the ",(0,i.kt)("inlineCode",{parentName:"p"},"ZLayer")," and the resulting layer is of\ntype ",(0,i.kt)("inlineCode",{parentName:"p"},"ZSimpleService[RequestContext]"),"."),(0,i.kt)("p",null,"To use this layer in an app, we can wire it like so:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"import scalapb.zio_grpc.ServerLayer\nimport scalapb.zio_grpc.Server\nimport zio.ZLayer\n\nval serviceList = ServiceList\n .addFromEnvironment[ZSimpleService[RequestContext]]\n// serviceList: ServiceList[Any with ZSimpleService[RequestContext]] = scalapb.zio_grpc.ServiceList@5484cfb0\n\nval serverLayer =\n ServerLayer.fromServiceList(\n io.grpc.ServerBuilder.forPort(9000),\n serviceList\n )\n// serverLayer: ZLayer[Any with ZSimpleService[RequestContext], Throwable, Server] = Suspend(\n// self = zio.ZLayer$ScopedEnvironmentPartiallyApplied$$$Lambda$15061/0x00000001040cb040@37f7b2de\n// )\n\nval ourApp =\n ZLayer.make[Server](\n serverLayer,\n myServiceLayer,\n UserDatabase.layer\n )\n// ourApp: ZLayer[Any, Throwable, Server] = Suspend(\n// self = zio.ZLayer$ZLayerProvideSomeOps$$$Lambda$15063/0x00000001040cf840@1543ea94\n// )\n\nobject LayeredApp extends zio.ZIOAppDefault {\n def run = ourApp.launch.exitCode\n}\n")),(0,i.kt)("p",null,(0,i.kt)("inlineCode",{parentName:"p"},"serverLayer")," creates a ",(0,i.kt)("inlineCode",{parentName:"p"},"Server")," from a ",(0,i.kt)("inlineCode",{parentName:"p"},"ZSimpleService")," layer and still depends on a ",(0,i.kt)("inlineCode",{parentName:"p"},"UserDatabase"),". Then, ",(0,i.kt)("inlineCode",{parentName:"p"},"ourApp")," feeds a ",(0,i.kt)("inlineCode",{parentName:"p"},"UserDatabase.layer")," into ",(0,i.kt)("inlineCode",{parentName:"p"},"serverLayer")," to produce\na ",(0,i.kt)("inlineCode",{parentName:"p"},"Server")," that doesn't depend on anything. In the ",(0,i.kt)("inlineCode",{parentName:"p"},"run")," method we launch the server layer."),(0,i.kt)("h2",{id:"implementing-a-service-with-dependencies"},"Implementing a service with dependencies"),(0,i.kt)("p",null,"In this scenario, your service depends on two additional services, ",(0,i.kt)("inlineCode",{parentName:"p"},"DepA")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"DepB"),". Following ",(0,i.kt)("a",{parentName:"p",href:"https://zio.dev/reference/service-pattern/"},"ZIO's service pattern"),", we accept the (interaces of the ) dependencies as constructor parameters."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'trait DepA {\n def methodA(param: String): ZIO[Any, Nothing, Int]\n}\n\nobject DepA {\n val layer = ZLayer.succeed[DepA](new DepA {\n def methodA(param: String) = ???\n })\n}\n\nobject DepB {\n val layer = ZLayer.succeed[DepB](new DepB {\n def methodB(param: Float) = ???\n })\n}\n\ntrait DepB {\n def methodB(param: Float): ZIO[Any, Nothing, Double]\n}\n\ncase class MyService2(depA: DepA, depB: DepB) extends ZSimpleService[User] {\n def sayHello(req: Request, user: User): ZIO[Any, StatusException, Response] =\n for {\n num1 <- depA.methodA(user.name)\n num2 <- depB.methodB(12.3f)\n _ <- printLine("I am here $num1 $num2!").orDie\n } yield Response(s"Hello, ${user.name}")\n}\n\nobject MyService2 {\n val layer: ZLayer[DepA with DepB, Nothing, ZSimpleService[RequestContext]] =\n ZLayer.fromFunction {\n (depA: DepA, depB: DepB) =>\n MyService2(depA, depB).transformContextZIO(findUser(_))\n }\n}\n')),(0,i.kt)("p",null,"Our service layer now depends on the ",(0,i.kt)("inlineCode",{parentName:"p"},"DepA")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"DepB")," interfaces. A server can be created like this:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},"object MyServer3 extends zio.ZIOAppDefault {\n\n val serverLayer =\n ServerLayer.fromServiceList(\n io.grpc.ServerBuilder.forPort(9000),\n ServiceList.addFromEnvironment[ZSimpleService[RequestContext]]\n )\n\n val appLayer = ZLayer.make[Server](\n serverLayer,\n DepA.layer,\n DepB.layer,\n MyService2.layer\n )\n\n def run = ourApp.launch.exitCode\n}\n")))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/b484f81c.7c68257e.js b/assets/js/b484f81c.dd9b8cff.js similarity index 87% rename from assets/js/b484f81c.7c68257e.js rename to assets/js/b484f81c.dd9b8cff.js index caed622a..c24f6c3d 100644 --- a/assets/js/b484f81c.7c68257e.js +++ b/assets/js/b484f81c.dd9b8cff.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[622],{3905:function(e,n,t){t.d(n,{Zo:function(){return p},kt:function(){return m}});var a=t(7294);function r(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function o(e){for(var n=1;n=0||(r[t]=e[t]);return r}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(r[t]=e[t])}return r}var s=a.createContext({}),l=function(e){var n=a.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):o(o({},n),e)),t},p=function(e){var n=l(e.components);return a.createElement(s.Provider,{value:n},e.children)},d={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},u=a.forwardRef((function(e,n){var t=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,p=c(e,["components","mdxType","originalType","parentName"]),u=l(t),m=r,h=u["".concat(s,".").concat(m)]||u[m]||d[m]||i;return t?a.createElement(h,o(o({ref:n},p),{},{components:t})):a.createElement(h,o({ref:n},p))}));function m(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var i=t.length,o=new Array(i);o[0]=u;var c={};for(var s in n)hasOwnProperty.call(n,s)&&(c[s]=n[s]);c.originalType=e,c.mdxType="string"==typeof e?e:r,o[1]=c;for(var l=2;l\n// )\n\n// myAppLogicNeedsEnv needs access to a ServiceNameClient. We turn it into\n// a self-contained effect (IO) by providing the layer to it:\nval myAppLogic1 = myAppLogicNeedsEnv.provideLayer(clientLayer)\n// myAppLogic1: ZIO[Any, Throwable, Unit] = OnSuccess(\n// trace = "repl.MdocSession.MdocApp.myAppLogic1(generated-code.md:46)",\n// first = OnSuccess(\n// trace = "repl.MdocSession.MdocApp.myAppLogic1(generated-code.md:46)",\n// first = Sync(\n// trace = "repl.MdocSession.MdocApp.myAppLogic1(generated-code.md:46)",\n// eval = zio.Scope$ReleaseMap$$$Lambda$13366/0x0000000103bca040@55a243b4\n// ),\n// successK = zio.ZIO$$Lambda$13323/0x00000001031a2840@33eb6639\n// ),\n// successK = zio.ZIO$$$Lambda$13419/0x0000000103bf0840@48784349\n// )\n\nobject LayeredApp extends zio.ZIOAppDefault {\n def run: UIO[ExitCode] = myAppLogic1.exitCode\n}\n')),(0,i.kt)("p",null,"Here the application is broken to multiple value assignments so you can see the types.\nThe first effect ",(0,i.kt)("inlineCode",{parentName:"p"},"myAppLogicNeedsEnv")," uses accessor functions, which makes it depend on an environment of type ",(0,i.kt)("inlineCode",{parentName:"p"},"ServiceNameClient"),". It chains the ",(0,i.kt)("inlineCode",{parentName:"p"},"unary")," RPC with printing the result to the console, and hence the final inferred effect type is ",(0,i.kt)("inlineCode",{parentName:"p"},"ServiceNameClient"),". Once we provide our custom layer, the effect type is ",(0,i.kt)("inlineCode",{parentName:"p"},"ZEnv"),", which we can use with ZIO's ",(0,i.kt)("inlineCode",{parentName:"p"},"exit")," method."),(0,i.kt)("h3",{id:"using-a-scoped-client"},"Using a Scoped client"),(0,i.kt)("p",null,"As an alternative to using ZLayer, you can use the client as a scoped resource:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'import myexample.testservice.ZioTestservice.ServiceNameClient\nimport myexample.testservice.{Request, Response}\n\nval clientManaged = ServiceNameClient.scoped(channel)\n// clientManaged: ZIO[Scope, Throwable, ServiceNameClient] = OnSuccess(\n// trace = "myexample.testservice.ZioTestservice.ServiceNameClient.scoped(ZioTestservice.scala:109)",\n// first = OnSuccess(\n// trace = "myexample.testservice.ZioTestservice.ServiceNameClientWithResponseMetadata.scoped(ZioTestservice.scala:191)",\n// first = OnSuccess(\n// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",\n// first = Sync(\n// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",\n// eval = zio.ZIO$$$Lambda$13346/0x0000000103bb5840@6d240eb2\n// ),\n// successK = zio.ZIO$$$Lambda$13266/0x00000001020fc040@75a1df8b\n// ),\n// successK = zio.ZIO$$Lambda$13323/0x00000001031a2840@6c21286a\n// ),\n// successK = zio.ZIO$$Lambda$13323/0x00000001031a2840@4f3f7239\n// )\n\nval myAppLogic = ZIO.scoped {\n clientManaged.flatMap { client =>\n for {\n res <- client.unary(Request())\n } yield res\n }\n}\n// myAppLogic: ZIO[Any, Throwable, Response] = OnSuccess(\n// trace = "repl.MdocSession.MdocApp.myAppLogic(generated-code.md:66)",\n// first = OnSuccess(\n// trace = "repl.MdocSession.MdocApp.myAppLogic(generated-code.md:66)",\n// first = Sync(\n// trace = "repl.MdocSession.MdocApp.myAppLogic(generated-code.md:66)",\n// eval = zio.Scope$ReleaseMap$$$Lambda$13366/0x0000000103bca040@55a243b4\n// ),\n// successK = zio.ZIO$$Lambda$13323/0x00000001031a2840@2d6562fa\n// ),\n// successK = zio.ZIO$ScopedPartiallyApplied$$$Lambda$13368/0x0000000103bcb840@48626d44\n// )\n')))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[622],{3905:function(e,n,t){t.d(n,{Zo:function(){return p},kt:function(){return m}});var a=t(7294);function r(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function o(e){for(var n=1;n=0||(r[t]=e[t]);return r}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(r[t]=e[t])}return r}var s=a.createContext({}),l=function(e){var n=a.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):o(o({},n),e)),t},p=function(e){var n=l(e.components);return a.createElement(s.Provider,{value:n},e.children)},d={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},u=a.forwardRef((function(e,n){var t=e.components,r=e.mdxType,i=e.originalType,s=e.parentName,p=c(e,["components","mdxType","originalType","parentName"]),u=l(t),m=r,h=u["".concat(s,".").concat(m)]||u[m]||d[m]||i;return t?a.createElement(h,o(o({ref:n},p),{},{components:t})):a.createElement(h,o({ref:n},p))}));function m(e,n){var t=arguments,r=n&&n.mdxType;if("string"==typeof e||r){var i=t.length,o=new Array(i);o[0]=u;var c={};for(var s in n)hasOwnProperty.call(n,s)&&(c[s]=n[s]);c.originalType=e,c.mdxType="string"==typeof e?e:r,o[1]=c;for(var l=2;l\n// )\n\n// myAppLogicNeedsEnv needs access to a ServiceNameClient. We turn it into\n// a self-contained effect (IO) by providing the layer to it:\nval myAppLogic1 = myAppLogicNeedsEnv.provideLayer(clientLayer)\n// myAppLogic1: ZIO[Any, Throwable, Unit] = OnSuccess(\n// trace = "repl.MdocSession.MdocApp.myAppLogic1(generated-code.md:46)",\n// first = OnSuccess(\n// trace = "repl.MdocSession.MdocApp.myAppLogic1(generated-code.md:46)",\n// first = Sync(\n// trace = "repl.MdocSession.MdocApp.myAppLogic1(generated-code.md:46)",\n// eval = zio.Scope$ReleaseMap$$$Lambda$15100/0x0000000104123840@7681e017\n// ),\n// successK = zio.ZIO$$Lambda$15057/0x00000001040c9840@30ccca3f\n// ),\n// successK = zio.ZIO$$$Lambda$15153/0x000000010414a040@3a48cbbe\n// )\n\nobject LayeredApp extends zio.ZIOAppDefault {\n def run: UIO[ExitCode] = myAppLogic1.exitCode\n}\n')),(0,i.kt)("p",null,"Here the application is broken to multiple value assignments so you can see the types.\nThe first effect ",(0,i.kt)("inlineCode",{parentName:"p"},"myAppLogicNeedsEnv")," uses accessor functions, which makes it depend on an environment of type ",(0,i.kt)("inlineCode",{parentName:"p"},"ServiceNameClient"),". It chains the ",(0,i.kt)("inlineCode",{parentName:"p"},"unary")," RPC with printing the result to the console, and hence the final inferred effect type is ",(0,i.kt)("inlineCode",{parentName:"p"},"ServiceNameClient"),". Once we provide our custom layer, the effect type is ",(0,i.kt)("inlineCode",{parentName:"p"},"ZEnv"),", which we can use with ZIO's ",(0,i.kt)("inlineCode",{parentName:"p"},"exit")," method."),(0,i.kt)("h3",{id:"using-a-scoped-client"},"Using a Scoped client"),(0,i.kt)("p",null,"As an alternative to using ZLayer, you can use the client as a scoped resource:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-scala"},'import myexample.testservice.ZioTestservice.ServiceNameClient\nimport myexample.testservice.{Request, Response}\n\nval clientManaged = ServiceNameClient.scoped(channel)\n// clientManaged: ZIO[Scope, Throwable, ServiceNameClient] = OnSuccess(\n// trace = "myexample.testservice.ZioTestservice.ServiceNameClient.scoped(ZioTestservice.scala:109)",\n// first = OnSuccess(\n// trace = "myexample.testservice.ZioTestservice.ServiceNameClientWithResponseMetadata.scoped(ZioTestservice.scala:191)",\n// first = OnSuccess(\n// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",\n// first = Sync(\n// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",\n// eval = zio.ZIO$$$Lambda$15080/0x00000001040dd840@1f146475\n// ),\n// successK = zio.ZIO$$$Lambda$15000/0x000000010409e840@7744254c\n// ),\n// successK = zio.ZIO$$Lambda$15057/0x00000001040c9840@a58556c\n// ),\n// successK = zio.ZIO$$Lambda$15057/0x00000001040c9840@53e86cdc\n// )\n\nval myAppLogic = ZIO.scoped {\n clientManaged.flatMap { client =>\n for {\n res <- client.unary(Request())\n } yield res\n }\n}\n// myAppLogic: ZIO[Any, Throwable, Response] = OnSuccess(\n// trace = "repl.MdocSession.MdocApp.myAppLogic(generated-code.md:66)",\n// first = OnSuccess(\n// trace = "repl.MdocSession.MdocApp.myAppLogic(generated-code.md:66)",\n// first = Sync(\n// trace = "repl.MdocSession.MdocApp.myAppLogic(generated-code.md:66)",\n// eval = zio.Scope$ReleaseMap$$$Lambda$15100/0x0000000104123840@7681e017\n// ),\n// successK = zio.ZIO$$Lambda$15057/0x00000001040c9840@794af79d\n// ),\n// successK = zio.ZIO$ScopedPartiallyApplied$$$Lambda$15102/0x0000000104125040@31713eed\n// )\n')))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c445b332.ed27b49f.js b/assets/js/c445b332.1eeb8d44.js similarity index 52% rename from assets/js/c445b332.ed27b49f.js rename to assets/js/c445b332.1eeb8d44.js index c8a3c6e3..578f78f5 100644 --- a/assets/js/c445b332.ed27b49f.js +++ b/assets/js/c445b332.1eeb8d44.js @@ -1 +1 @@ -"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[869],{3905:function(e,n,t){t.d(n,{Zo:function(){return d},kt:function(){return m}});var a=t(7294);function i(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function r(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function s(e){for(var n=1;n=0||(i[t]=e[t]);return i}(e,n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(i[t]=e[t])}return i}var o=a.createContext({}),l=function(e){var n=a.useContext(o),t=n;return e&&(t="function"==typeof e?e(n):s(s({},n),e)),t},d=function(e){var n=l(e.components);return a.createElement(o.Provider,{value:n},e.children)},p={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},u=a.forwardRef((function(e,n){var t=e.components,i=e.mdxType,r=e.originalType,o=e.parentName,d=c(e,["components","mdxType","originalType","parentName"]),u=l(t),m=i,f=u["".concat(o,".").concat(m)]||u[m]||p[m]||r;return t?a.createElement(f,s(s({ref:n},d),{},{components:t})):a.createElement(f,s({ref:n},d))}));function m(e,n){var t=arguments,i=n&&n.mdxType;if("string"==typeof e||i){var r=t.length,s=new Array(r);s[0]=u;var c={};for(var o in n)hasOwnProperty.call(n,o)&&(c[o]=n[o]);c.originalType=e,c.mdxType="string"==typeof e?e:i,s[1]=c;for(var l=2;l\n// )\n')),(0,r.kt)("h2",{id:"setting-timeout-for-each-request"},"Setting timeout for each request"),(0,r.kt)("p",null,"As in the previous example, assuming there is a client in the environment, we can set the timeout\nfor each request like this:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'ServiceNameClient.withTimeoutMillis(3000).unary(Request())\n// res0: ZIO[ServiceNameClient, io.grpc.StatusException, Response] = OnSuccess(\n// trace = "myexample.testservice.ZioTestservice.ServiceNameAccessors.unary(ZioTestservice.scala:77)",\n// first = Sync(\n// trace = "myexample.testservice.ZioTestservice.ServiceNameAccessors.unary(ZioTestservice.scala:77)",\n// eval = zio.ZIO$ServiceWithZIOPartiallyApplied$$$Lambda$13275/0x00000001031bf840@28685251\n// ),\n// successK = zio.ZIO$$$Lambda$13266/0x00000001020fc040@75a1df8b\n// )\n')),(0,r.kt)("p",null,"Clients provide (through the ",(0,r.kt)("inlineCode",{parentName:"p"},"GeneratedClient")," trait) a number of methods that makes it possible to\nspecify a deadline or a timeout for each request:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"// Provide a new absolute deadline\ndef withDeadline(deadline: Deadline): Service\n\n// Sets a new timeout for this service\ndef withTimeout(duration: zio.duration.Duration): Service\n\n// Sets a new timeout in millis\ndef withTimeoutMillis(millis: Long): Service\n\n// Replace the call options with the provided call options\ndef withCallOptions(callOptions: CallOptions): Service\n\n// update the CallOptions for this service\ndef mapCallOptions(f: CallOptions => CallOptions): Service\n\n// update the request Metadata for this service\ndef mapMetadataZIO(f: SafeMetadata => UIO[SafeMetadata]): Service\n")),(0,r.kt)("p",null,"If you are using a client instance, the above methods are available to provide you with a new\nclient that has a modified ",(0,r.kt)("inlineCode",{parentName:"p"},"CallOptions")," effect. Making the copy of those clients is cheap and can\nbe safely done for each individual call:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'val clientScoped = ServiceNameClient.scoped(channel)\n// clientScoped: ZIO[Scope, Throwable, ServiceNameClient] = OnSuccess(\n// trace = "myexample.testservice.ZioTestservice.ServiceNameClient.scoped(ZioTestservice.scala:109)",\n// first = OnSuccess(\n// trace = "myexample.testservice.ZioTestservice.ServiceNameClientWithResponseMetadata.scoped(ZioTestservice.scala:191)",\n// first = OnSuccess(\n// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",\n// first = Sync(\n// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",\n// eval = zio.ZIO$$$Lambda$13346/0x0000000103bb5840@10cd37c8\n// ),\n// successK = zio.ZIO$$$Lambda$13266/0x00000001020fc040@75a1df8b\n// ),\n// successK = zio.ZIO$$Lambda$13323/0x00000001031a2840@2dedb04\n// ),\n// successK = zio.ZIO$$Lambda$13323/0x00000001031a2840@5975cf99\n// )\n\nval myAppLogic = ZIO.scoped {\n clientScoped.flatMap { client =>\n for {\n res <- client\n .withTimeoutMillis(3000).unary(Request())\n } yield res\n }\n}\n// myAppLogic: ZIO[Any, Throwable, Response] = OnSuccess(\n// trace = "repl.MdocSession.MdocApp.myAppLogic(deadlines.md:57)",\n// first = OnSuccess(\n// trace = "repl.MdocSession.MdocApp.myAppLogic(deadlines.md:57)",\n// first = Sync(\n// trace = "repl.MdocSession.MdocApp.myAppLogic(deadlines.md:57)",\n// eval = zio.Scope$ReleaseMap$$$Lambda$13366/0x0000000103bca040@55a243b4\n// ),\n// successK = zio.ZIO$$Lambda$13323/0x00000001031a2840@1854bea5\n// ),\n// successK = zio.ZIO$ScopedPartiallyApplied$$$Lambda$13368/0x0000000103bcb840@5f58e1ee\n// )\n')))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[869],{3905:function(e,n,t){t.d(n,{Zo:function(){return d},kt:function(){return m}});var a=t(7294);function i(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function r(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function s(e){for(var n=1;n=0||(i[t]=e[t]);return i}(e,n);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(i[t]=e[t])}return i}var o=a.createContext({}),l=function(e){var n=a.useContext(o),t=n;return e&&(t="function"==typeof e?e(n):s(s({},n),e)),t},d=function(e){var n=l(e.components);return a.createElement(o.Provider,{value:n},e.children)},p={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},u=a.forwardRef((function(e,n){var t=e.components,i=e.mdxType,r=e.originalType,o=e.parentName,d=c(e,["components","mdxType","originalType","parentName"]),u=l(t),m=i,h=u["".concat(o,".").concat(m)]||u[m]||p[m]||r;return t?a.createElement(h,s(s({ref:n},d),{},{components:t})):a.createElement(h,s({ref:n},d))}));function m(e,n){var t=arguments,i=n&&n.mdxType;if("string"==typeof e||i){var r=t.length,s=new Array(r);s[0]=u;var c={};for(var o in n)hasOwnProperty.call(n,o)&&(c[o]=n[o]);c.originalType=e,c.mdxType="string"==typeof e?e:i,s[1]=c;for(var l=2;l\n// )\n')),(0,r.kt)("h2",{id:"setting-timeout-for-each-request"},"Setting timeout for each request"),(0,r.kt)("p",null,"As in the previous example, assuming there is a client in the environment, we can set the timeout\nfor each request like this:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'ServiceNameClient.withTimeoutMillis(3000).unary(Request())\n// res0: ZIO[ServiceNameClient, io.grpc.StatusException, Response] = OnSuccess(\n// trace = "myexample.testservice.ZioTestservice.ServiceNameAccessors.unary(ZioTestservice.scala:77)",\n// first = Sync(\n// trace = "myexample.testservice.ZioTestservice.ServiceNameAccessors.unary(ZioTestservice.scala:77)",\n// eval = zio.ZIO$ServiceWithZIOPartiallyApplied$$$Lambda$15009/0x00000001040ac840@1e89d681\n// ),\n// successK = zio.ZIO$$$Lambda$15000/0x000000010409e840@7744254c\n// )\n')),(0,r.kt)("p",null,"Clients provide (through the ",(0,r.kt)("inlineCode",{parentName:"p"},"GeneratedClient")," trait) a number of methods that makes it possible to\nspecify a deadline or a timeout for each request:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},"// Provide a new absolute deadline\ndef withDeadline(deadline: Deadline): Service\n\n// Sets a new timeout for this service\ndef withTimeout(duration: zio.duration.Duration): Service\n\n// Sets a new timeout in millis\ndef withTimeoutMillis(millis: Long): Service\n\n// Replace the call options with the provided call options\ndef withCallOptions(callOptions: CallOptions): Service\n\n// update the CallOptions for this service\ndef mapCallOptions(f: CallOptions => CallOptions): Service\n\n// update the request Metadata for this service\ndef mapMetadataZIO(f: SafeMetadata => UIO[SafeMetadata]): Service\n")),(0,r.kt)("p",null,"If you are using a client instance, the above methods are available to provide you with a new\nclient that has a modified ",(0,r.kt)("inlineCode",{parentName:"p"},"CallOptions")," effect. Making the copy of those clients is cheap and can\nbe safely done for each individual call:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-scala"},'val clientScoped = ServiceNameClient.scoped(channel)\n// clientScoped: ZIO[Scope, Throwable, ServiceNameClient] = OnSuccess(\n// trace = "myexample.testservice.ZioTestservice.ServiceNameClient.scoped(ZioTestservice.scala:109)",\n// first = OnSuccess(\n// trace = "myexample.testservice.ZioTestservice.ServiceNameClientWithResponseMetadata.scoped(ZioTestservice.scala:191)",\n// first = OnSuccess(\n// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",\n// first = Sync(\n// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",\n// eval = zio.ZIO$$$Lambda$15080/0x00000001040dd840@3a8a0345\n// ),\n// successK = zio.ZIO$$$Lambda$15000/0x000000010409e840@7744254c\n// ),\n// successK = zio.ZIO$$Lambda$15057/0x00000001040c9840@3ff6cac8\n// ),\n// successK = zio.ZIO$$Lambda$15057/0x00000001040c9840@5950d795\n// )\n\nval myAppLogic = ZIO.scoped {\n clientScoped.flatMap { client =>\n for {\n res <- client\n .withTimeoutMillis(3000).unary(Request())\n } yield res\n }\n}\n// myAppLogic: ZIO[Any, Throwable, Response] = OnSuccess(\n// trace = "repl.MdocSession.MdocApp.myAppLogic(deadlines.md:57)",\n// first = OnSuccess(\n// trace = "repl.MdocSession.MdocApp.myAppLogic(deadlines.md:57)",\n// first = Sync(\n// trace = "repl.MdocSession.MdocApp.myAppLogic(deadlines.md:57)",\n// eval = zio.Scope$ReleaseMap$$$Lambda$15100/0x0000000104123840@7681e017\n// ),\n// successK = zio.ZIO$$Lambda$15057/0x00000001040c9840@105accc0\n// ),\n// successK = zio.ZIO$ScopedPartiallyApplied$$$Lambda$15102/0x0000000104125040@275b2f90\n// )\n')))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.385a1a00.js b/assets/js/runtime~main.77277023.js similarity index 96% rename from assets/js/runtime~main.385a1a00.js rename to assets/js/runtime~main.77277023.js index e8cf092f..f0db2bf2 100644 --- a/assets/js/runtime~main.385a1a00.js +++ b/assets/js/runtime~main.77277023.js @@ -1 +1 @@ -!function(){"use strict";var e,t,n,r,o,f={},a={};function c(e){var t=a[e];if(void 0!==t)return t.exports;var n=a[e]={exports:{}};return f[e].call(n.exports,n,n.exports,c),n.exports}c.m=f,e=[],c.O=function(t,n,r,o){if(!n){var f=1/0;for(d=0;d=o)&&Object.keys(c.O).every((function(e){return c.O[e](n[u])}))?n.splice(u--,1):(a=!1,o0&&e[d-1][2]>o;d--)e[d]=e[d-1];e[d]=[n,r,o]},c.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return c.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},c.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var o=Object.create(null);c.r(o);var f={};t=t||[null,n({}),n([]),n(n)];for(var a=2&r&&e;"object"==typeof a&&!~t.indexOf(a);a=n(a))Object.getOwnPropertyNames(a).forEach((function(t){f[t]=function(){return e[t]}}));return f.default=function(){return e},c.d(o,f),o},c.d=function(e,t){for(var n in t)c.o(t,n)&&!c.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},c.f={},c.e=function(e){return Promise.all(Object.keys(c.f).reduce((function(t,n){return c.f[n](e,t),t}),[]))},c.u=function(e){return"assets/js/"+({42:"d066d984",53:"935f2afb",60:"52069c2c",66:"5ee1189a",83:"90150c67",195:"c4f5d8e4",215:"47bf511f",292:"3a2a6d86",335:"9c997467",344:"b5b67730",347:"77b5bd74",348:"780353dd",399:"21f2ac89",491:"fbee2072",514:"1be78505",539:"e367d32b",596:"2ed64f4e",622:"b484f81c",764:"215c73f9",766:"cdb862af",869:"c445b332",892:"ab3b6cad",896:"5b05f69a",917:"8ff58b1b",918:"17896441",922:"a9f25167",975:"aa547a5c"}[e]||e)+"."+{42:"f490eff7",53:"4336e142",60:"ffc12dd8",66:"fc66384a",83:"4c39be07",195:"0b6c5ac8",215:"5545cdc7",292:"c60601d8",335:"ce02b66a",344:"961f4096",347:"aee6d68d",348:"d3f94b1b",399:"082666c8",491:"aa9261f1",514:"af05bda5",539:"a1038f3c",596:"37db4340",622:"7c68257e",764:"f6cda901",766:"fcfcd51f",869:"ed27b49f",892:"f83e1208",896:"79ff1b7e",917:"8c852a58",918:"8cfebb3f",922:"3bb07c1b",972:"5726a411",975:"ea208cf8"}[e]+".js"},c.miniCssF=function(e){},c.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),c.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="website:",c.l=function(e,t,n,f){if(r[e])r[e].push(t);else{var a,u;if(void 0!==n)for(var i=document.getElementsByTagName("script"),d=0;d=o)&&Object.keys(c.O).every((function(e){return c.O[e](n[u])}))?n.splice(u--,1):(a=!1,o0&&e[d-1][2]>o;d--)e[d]=e[d-1];e[d]=[n,r,o]},c.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return c.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},c.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var o=Object.create(null);c.r(o);var f={};t=t||[null,n({}),n([]),n(n)];for(var a=2&r&&e;"object"==typeof a&&!~t.indexOf(a);a=n(a))Object.getOwnPropertyNames(a).forEach((function(t){f[t]=function(){return e[t]}}));return f.default=function(){return e},c.d(o,f),o},c.d=function(e,t){for(var n in t)c.o(t,n)&&!c.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},c.f={},c.e=function(e){return Promise.all(Object.keys(c.f).reduce((function(t,n){return c.f[n](e,t),t}),[]))},c.u=function(e){return"assets/js/"+({42:"d066d984",53:"935f2afb",60:"52069c2c",66:"5ee1189a",83:"90150c67",195:"c4f5d8e4",215:"47bf511f",292:"3a2a6d86",335:"9c997467",344:"b5b67730",347:"77b5bd74",348:"780353dd",399:"21f2ac89",491:"fbee2072",514:"1be78505",539:"e367d32b",596:"2ed64f4e",622:"b484f81c",764:"215c73f9",766:"cdb862af",869:"c445b332",892:"ab3b6cad",896:"5b05f69a",917:"8ff58b1b",918:"17896441",922:"a9f25167",975:"aa547a5c"}[e]||e)+"."+{42:"f490eff7",53:"4336e142",60:"ffc12dd8",66:"fc66384a",83:"4c39be07",195:"0b6c5ac8",215:"5545cdc7",292:"ffb0bfef",335:"ce02b66a",344:"961f4096",347:"aee6d68d",348:"d3f94b1b",399:"082666c8",491:"aa9261f1",514:"af05bda5",539:"a1038f3c",596:"0ba90eb7",622:"dd9b8cff",764:"f6cda901",766:"fcfcd51f",869:"1eeb8d44",892:"f83e1208",896:"79ff1b7e",917:"8c852a58",918:"8cfebb3f",922:"3bb07c1b",972:"5726a411",975:"ea208cf8"}[e]+".js"},c.miniCssF=function(e){},c.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),c.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="website:",c.l=function(e,t,n,f){if(r[e])r[e].push(t);else{var a,u;if(void 0!==n)for(var i=document.getElementsByTagName("script"),d=0;dBackpressure | ZIO gRPC - + @@ -14,7 +14,7 @@
Version: 0.5.x

Backpressure

From version 0.5.3 onwards, zio-grpc provides backpressure support for server streaming RPCs. In case the call is not capable to sending additional messages without buffering (as determined by ServerCall.isReady), sending messages from the queue associated with server response Stream will stop. The default size of this queue is 16, and can be configured by setting the system property zio-grpc.backpressure-queue-size.

- + \ No newline at end of file diff --git a/docs/0.5.x/basics/index.html b/docs/0.5.x/basics/index.html index 8997cf7d..f78027ca 100644 --- a/docs/0.5.x/basics/index.html +++ b/docs/0.5.x/basics/index.html @@ -6,7 +6,7 @@ Basics Tutorial | ZIO gRPC - + @@ -116,7 +116,7 @@ get the other's messages in the order they were written, both the client and server can read and write in any order — the streams operate completely independently.

Providing the client layer into the application logic

All the effects we created were dependent on a RouteGuideClient available in the environment. We earlier instantiated a clientLayer, so we can provide it to our application logic at the top-level (the run method):

val myAppLogic =
for {
// Looking for a valid feature
_ <- getFeature(409146138, -746188906)
// Looking for a missing feature
_ <- getFeature(0, 0)

// Calls listFeatures with a rectangle of interest. Prints
// each response feature as it arrives.
// start: listFeatures
_ <-
RouteGuideClient
.listFeatures(
Rectangle(
lo = Some(Point(400000000, -750000000)),
hi = Some(Point(420000000, -730000000))
)
)
.zipWithIndex
.foreach {
case (feature, index) =>
putStrLn(s"Result #${index + 1}: $feature")
}
// end: listFeatures

_ <- recordRoute(10)

_ <- routeChat
} yield ()

final def run(args: List[String]) =
myAppLogic.provideCustomLayer(clientLayer).exitCode

Try it out!

  1. Run the server:

    sbt "runMain zio_grpc.examples.routeguide.RouteGuideServer"
  2. From another terminal, run the client:

    sbt "runMain zio_grpc.examples.routeguide.RouteGuideClientApp"
note

This document, "ZIO gRPC: Basics Tutorial", is a derivative of "gRPC Basics Tutorial" by gRPC Authors, used under CC-BY-4.0. "ZIO gRPC: Basics Tutorial" is licensed under CC-BY-4.0 by Nadav Samet.

- + \ No newline at end of file diff --git a/docs/0.5.x/context/index.html b/docs/0.5.x/context/index.html index e6305258..ffbd3789 100644 --- a/docs/0.5.x/context/index.html +++ b/docs/0.5.x/context/index.html @@ -6,7 +6,7 @@ Context and Dependencies | ZIO gRPC - + @@ -20,7 +20,7 @@ type ZSimpleService[Any, Has[RequestContext]]], which means no environment is expected, and it assumes a Has[RequestContext] context. To use this layer in an app, we can wire it like so:

import scalapb.zio_grpc.ServerLayer

val serverLayer =
ServerLayer.fromServiceLayer(
io.grpc.ServerBuilder.forPort(9000)
)(myServiceLive)
// serverLayer: zio.ZLayer[Console with UserDatabase, Throwable, Has[scalapb.zio_grpc.Server.Service]] = Fold(
// self = Managed(self = zio.ZManaged$$anon$2@15f00ea8),
// failure = Managed(self = zio.ZManaged$$anon$2@25f715d6),
// success = Managed(self = zio.ZManaged$$anon$2@3fcc59f7)
// )

val ourApp = (UserDatabase.live ++ Console.any) >>>
serverLayer
// ourApp: zio.ZLayer[Any with Console, Throwable, Has[scalapb.zio_grpc.Server.Service]] = Fold(
// self = ZipWithPar(
// self = Managed(self = zio.ZManaged$$anon$2@519de9b2),
// that = Managed(self = zio.ZManaged$$anon$2@64403fed),
// f = zio.ZLayer$$Lambda$16816/0x0000000803ee6040@2bba085b
// ),
// failure = Managed(self = zio.ZManaged$$anon$2@54989ce3),
// success = Fold(
// self = Managed(self = zio.ZManaged$$anon$2@15f00ea8),
// failure = Managed(self = zio.ZManaged$$anon$2@25f715d6),
// success = Managed(self = zio.ZManaged$$anon$2@3fcc59f7)
// )
// )

object LayeredApp extends zio.App {
def run(args: List[String]) = ourApp.build.useForever.exitCode
}

serverLayer wraps around our service layer to produce a server. Then, ourApp layer is constructed such that it takes UserDatabase.live in conjuction to a passthrough layer for Console to satisfy the two input requirements of serverLayer. The outcome, ourApp, is a ZLayer that can produce a Server from a Console. In the run method we build the layer and run it. Note that we are directly using a zio.App rather than ServerMain which does not support this use case yet.

- + \ No newline at end of file diff --git a/docs/0.5.x/deadlines/index.html b/docs/0.5.x/deadlines/index.html index 709812ca..9d2cea31 100644 --- a/docs/0.5.x/deadlines/index.html +++ b/docs/0.5.x/deadlines/index.html @@ -6,7 +6,7 @@ ZIO gRPC and Deadlines | ZIO gRPC - + @@ -24,7 +24,7 @@ to specify a deadline or a timeout for each request:

// Provide a new absolute deadline
def withDeadline(deadline: Deadline): Service

// Sets a new timeout for this service
def withTimeout(duration: zio.duration.Duration): Service

// Sets a new timeout in millis
def withTimeoutMillis(millis: Long): Service

// Replace the call options with the provided call options
def withCallOptions(callOptions: CallOptions): Service

// Effectfully update the CallOptions for this service
def mapCallOptionsM(f: CallOptions => zio.IO[Status, CallOptions]): Service

If you are using a client instance, the above methods are available to provide you with a new client that has a modified CallOptions effect. Making the copy of those clients is cheap and can be safely done for each individual call:

val clientManaged = ServiceNameClient.managed(channel)
// clientManaged: Managed[Throwable, ServiceNameClient.ZService[Any, Any]] = zio.ZManaged$$anon$2@168ab14e

val myAppLogic = for {
res <- clientManaged.use(
client =>
client.withTimeoutMillis(3000).unary(Request())
.mapError(_.asException)
)
} yield res
// myAppLogic: ZIO[Any with Any, Throwable, Response] = zio.ZIO$FlatMap@2be1bc9f
- + \ No newline at end of file diff --git a/docs/0.5.x/decorating/index.html b/docs/0.5.x/decorating/index.html index 951069ee..7480c35d 100644 --- a/docs/0.5.x/decorating/index.html +++ b/docs/0.5.x/decorating/index.html @@ -6,7 +6,7 @@ Decorating services | ZIO gRPC - + @@ -16,7 +16,7 @@ to apply a transformation to all methods of a service to generate a new "decorated" service. This can be used for pre- or post-processing of requests/responses and also for environment and context transformations.

We define decoration:

import io.grpc.Status
import scalapb.zio_grpc.{ RequestContext, ZTransform }
import zio._
import zio.stream.ZStream

class LoggingTransform[R] extends ZTransform[R, Status, R with Has[RequestContext]] {

def logCause(cause: Cause[Status]): URIO[Has[RequestContext], Unit] = ???

def accessLog: URIO[Has[RequestContext], Unit] = ???

override def effect[A](io: ZIO[R, Status, A]): ZIO[R with Has[RequestContext], Status, A] =
io.zipLeft(accessLog).tapCause(logCause)

override def stream[A](io: ZStream[R, Status, A]): ZStream[R with Has[RequestContext], Status, A] =
(io ++ ZStream.fromEffect(accessLog).drain).onError(logCause)
}

and then we apply it to our service:

import myexample.testservice.ZioTestservice.ZSimpleService
import myexample.testservice.{Request, Response}

object MyService extends ZSimpleService[Any, Any] {
def sayHello(req: Request): ZIO[Any, Status, Response] =
ZIO.succeed(Response(s"Hello user"))
}

val decoratedService =
MyService.transform(new LoggingTransform[Any])
// decoratedService: ZSimpleService[Has[RequestContext], Any] = myexample.testservice.ZioTestservice$ZSimpleService$$anon$6$$anon$7@67739b99
- + \ No newline at end of file diff --git a/docs/0.5.x/generated-code/index.html b/docs/0.5.x/generated-code/index.html index 66280afb..7ce219ac 100644 --- a/docs/0.5.x/generated-code/index.html +++ b/docs/0.5.x/generated-code/index.html @@ -6,7 +6,7 @@ Generated Code Reference | ZIO gRPC - + @@ -15,7 +15,7 @@ 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.

Service trait

Inside the object, for each service ServiceName that is defined in a .proto file, the following structure is generated:

trait ZServiceName[R, Context] {
// methods for each RPC
def sayHello(request: HelloRequest):
ZIO[R with Context, Status, HelloReply]
}
type ServiceName = ZServiceName[Any, Any]

The trait ZServiceName is to be extended when implementing a server for this service. The trait takes two type parameters: R and Context:

  • R representes the dependencies of the service. All the effects being returned by these methods depend on R to encode this dependency.
  • Context represents any domain object that you would like your RPC methods to have available in the environment.

You can set both R and 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 ZServiceName[Any, Any]:

trait ServiceNameImpl extends ServiceName {
}

Learn more about using context and dependencies in the next section.

info

Why 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.

Running the server

The easiest way to run a service, is to create an object that extends scalapb.zio_grpc.ServerMain:

import scalapb.zio_grpc.{ServerMain, ServiceList}

object MyMain extends ServerMain {
def services = ServiceList.add(ServiceNameImpl)

// Default port is 9000
override def port: Int = 8980
}

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.

Client trait

The generated client follows ZIO's module pattern:

type ServiceNameClient = Has[ServiceNameClient.Service]
object ServiceNameClient {
trait ZService[R] {
// methods for use as a client
def sayHello(request: HelloRequest):
ZIO[R, Status, HelloReply]
}
type Service = ZService[Any]

// accessor methods
def sayHello(request: HelloRequest):
ZIO[ServiceNameClient, Status, HelloReply]

def managed[R](
managedChannel: ZManagedChannel[R],
options: CallOptions =
io.grpc.CallOptions.DEFAULT,
headers: zio.UIO[SafeMetadata] =
scalapb.zio_grpc.SafeMetadata.make
): zio.Managed[Throwable, ZService[R]]

def live[R](
managedChannel: ZManagedChannel[R],
options: CallOptions =
io.grpc.CallOptions.DEFAULT,
headers: zio.UIO[scalapb.zio_grpc.SafeMetadata] =
scalapb.zio_grpc.SafeMetadata.make
): zio.ZLayer[R, Throwable, ServiceNameClient]
}

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:

type ZManagedChannel[R] = Managed[Throwable, ZChannel[R]]

Creating a channel:

import scalapb.zio_grpc.ZManagedChannel
import io.grpc.ManagedChannelBuilder

val channel = ZManagedChannel(
ManagedChannelBuilder
.forAddress("localhost", 8980)
.usePlaintext()
)
// channel: ZManagedChannel[Any] = zio.ZManaged$$anon$2@69497154

Using the client as a layer

A single 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:

import myexample.testservice.ZioTestservice.ServiceNameClient
import myexample.testservice.{Request, Response}
import zio._
import zio.console._

// create layer:
val clientLayer = ServiceNameClient.live(channel)
// clientLayer: ZLayer[Any, Throwable, Has[ServiceNameClient.ZService[Any, Any]]] = Managed(
// self = zio.ZManaged$$anon$2@25f8138d
// )

val myAppLogicNeedsEnv = for {
// use layer through accessor methods:
res <- ServiceNameClient.unary(Request())
_ <- putStrLn(res.toString)
} yield ()
// myAppLogicNeedsEnv: ZIO[Has[ServiceNameClient.ZService[Any, Any]] with Any with Console, Object, Unit] = zio.ZIO$FlatMap@2aacd69

// myAppLogicNeedsEnv needs access to a ServiceNameClient. We turn it into
// a self-contained effect (IO) by providing the layer to it:
val myAppLogic1 = myAppLogicNeedsEnv.provideCustomLayer(clientLayer)
// myAppLogic1: ZIO[ZEnv, Object, Unit] = zio.ZIO$CheckInterrupt@71b27fca

object LayeredApp extends zio.App {
def run(args: List[String]): URIO[ZEnv, ExitCode] = myAppLogic1.exitCode
}

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.

Using a Managed Client

As an alternative to using ZLayer, you can use the client through a managed resource:

import myexample.testservice.ZioTestservice.ServiceNameClient
import myexample.testservice.{Request, Response}

val clientManaged = ServiceNameClient.managed(channel)
// clientManaged: Managed[Throwable, ServiceNameClient.ZService[Any, Any]] = zio.ZManaged$$anon$2@66c5251c

val myAppLogic = for {
res <- clientManaged.use(
client =>
client.unary(Request()).mapError(_.asException)
)
} yield res
// myAppLogic: ZIO[Any with Any, Throwable, Response] = zio.ZIO$FlatMap@1a4cd330

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 StatusException. This way, the resulting effect can fail with a Throwable.

- + \ No newline at end of file diff --git a/docs/0.5.x/index.html b/docs/0.5.x/index.html index 4cbef473..ca8fa77f 100644 --- a/docs/0.5.x/index.html +++ b/docs/0.5.x/index.html @@ -6,13 +6,13 @@ Introduction | ZIO gRPC - +
Version: 0.5.x

Introduction

ZIO-gRPC lets you write purely functional gRPC servers and clients. It is built on top of ZIO, a library for asynchronous and concurrent functional programming in Scala.

Highlights

  • Supports all types of RPCs (unary, client streaming, server streaming, bidirectional).
  • Cancellable RPCs: easily cancel RPCs by calling interrupt on the effect. Server will immediately abort execution.
  • Scala.js support: call your service from Scala code running on the browser.

Why ZIO gRPC?

One of the advantages of a microservice architecture is the ability to write different microservices using different technogies. ZIO gRPC might be a great choice for your project if you value:

  • Type-safety: Your business logic and the data types are checked at compile time.
  • Resource safety: Managed resources (such as database connections) are guaranteed to be released.
  • Reusable behaviors: Create complex behaviors by easily combining basic building blocks. For example:
    • Exponential backoff, is a retry method call that gets an exponential schedule as a parameter.
    • Sending a few identical requests to a number of servers and waiting only until the first response.
    • Sending different requests in parallel and collecting all the results as a list.
  • Living on the edge: Yes, this is a word of warning. Both ZIO and ZIO gRPC are new technologies. While a lot of effort has been put to test, it is possible that you will encounter bugs. For ZIO gRPC, APIs may change between minor releases without notice.

Effects as pure values

In ZIO gRPC, the services you will write will be purely functional. When a client makes an RPC call to your service, a "handler" method in your service will be invoked. In contrast to imperative programming, instead of actually handling the call, this handler method will return a pure immutable value of type ZIO. This value, on its own, doesn't do anything - it represents the work that needs to get done to fulfill the request, for example: reading from a database, making a network call, or calling a local function. ZIO's runtime is going to run the effect immediately after you return it. As you will see, structuring your program by combining functional effects will lead to reusable code that is easier to reason about and more likely to be correct once you get it to compile.

There are also technical advantages: in case the client aborts the request, ZIO gRPC can interrupt the server computation even if the server is executing an effect that is unrelated to ZIO gRPC (in grpc-java for example, this can only be accomplished by the server occassionally checking for a cancellation). Using ZIO building blocks such as ZIO.bracket, ZIO#onExit, ZIO.uninterruptible you remain in control over the behavior of the program in case of interruptions.

Try it out

  • Got 5 to 10 more minutes? Check out our Quick Start tutorial. You will clone an existing ZIO gRPC client and a server. You will run them and add a new RPC method.
  • Got up to an hour? Take a look at the Basics tutorial. You will learn how to implement gRPC servers and clients, including all sort of streaming requests available in gRPC. The tutorial will also show you how to hook the clients and servers into a full working ZIO application.
- + \ No newline at end of file diff --git a/docs/0.5.x/installation/index.html b/docs/0.5.x/installation/index.html index 258e9bc7..c964324b 100644 --- a/docs/0.5.x/installation/index.html +++ b/docs/0.5.x/installation/index.html @@ -6,7 +6,7 @@ Installing ZIO gRPC | ZIO gRPC - + @@ -15,7 +15,7 @@ avoid unintended evictions and ensure binary compatibility:

ScalaPBZIO gRPC
0.11.x0.5.x
0.10.x0.4.x

If you are building with sbt, add the following to your project/plugins.sbt:

addSbtPlugin("com.thesamet" % "sbt-protoc" % "1.0.2")

libraryDependencies +=
"com.thesamet.scalapb.zio-grpc" %% "zio-grpc-codegen" % "0.5.0"

Then, add the following lines to your build.sbt:

PB.targets in Compile := Seq(
scalapb.gen(grpc = true) -> (sourceManaged in Compile).value / "scalapb",
scalapb.zio_grpc.ZioCodeGenerator -> (sourceManaged in Compile).value / "scalapb"
)

libraryDependencies ++= Seq(
"io.grpc" % "grpc-netty" % "1.41.0",
"com.thesamet.scalapb" %% "scalapb-runtime-grpc" % scalapb.compiler.Version.scalapbVersion
)

This configuration will set up the ScalaPB code generator alongside the ZIO gRPC code generator. Upon compilation, the source generator will process all proto files under src/main/protobuf. The ScalaPB generator will generate case classes for all messages as well as methods to serialize and deserialize those messages. The ZIO gRPC code generator will generate code as described in the generated code section.

Generating code using ScalaPBC (CLI)

If you are using ScalaPBC to generate Scala code from the CLI, you can invoke the zio code generator like this:

scalapbc \
--plugin-artifact=com.thesamet.scalapb.zio-grpc:protoc-gen-zio:0.5.0:default,classifier=unix,ext=sh,type=jar\
-- e2e/src/main/protobuf/service.proto --zio_out=/tmp/out --scala_out=grpc:/tmp/out \
-Ie2e/src/main/protobuf -Ithird_party -Iprotobuf

You will need to add to your project the following libraries:

  • com.thesamet.scalapb::scalapb-runtime-grpc:0.11.8
  • com.thesamet.scalapb.zio-grpc:zio-grpc-core:0.5.0
  • io.grpc:grpc-netty:1.41.0
- + \ No newline at end of file diff --git a/docs/0.5.x/quickstart/index.html b/docs/0.5.x/quickstart/index.html index 9ba71283..80e36373 100644 --- a/docs/0.5.x/quickstart/index.html +++ b/docs/0.5.x/quickstart/index.html @@ -6,13 +6,13 @@ Quick Start | ZIO gRPC - +
Version: 0.5.x

Quick Start

This guide gets you started with ZIO gRPC with a simple working example.

Prerequisites

Get the example code

The example code is part of the zio-grpc repository.

  1. Download the repo as a zip file and unzip it, or clone the repo:

    git clone https://github.com/scalapb/zio-grpc
  2. Change to the examples directory:

    cd zio-grpc/examples/helloworld

Run the example

From the examples directory:

  1. Run the server:

    sbt "runMain zio_grpc.examples.helloworld.HelloWorldServer"
  2. From another terminal, run the client:

    sbt "runMain zio_grpc.examples.helloworld.HelloWorldClient"

Congratulations! You’ve just run a client-server application with ZIO gRPC.

Update a gRPC service

In this section you’ll update the application by adding an extra server method. The gRPC service is defined using protocol buffers. To learn more about how to define a service in a .proto file see Basics Tutorial. For now, all you need to know is that both the server and the client stub have a SayHello() RPC method that takes a HelloRequest parameter from the client and returns a HelloReply from the server, and that the method is defined like this:

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}

Open src/main/protobuf/helloworld.proto and add a new SayHelloAgain() method with the same request and response types as SayHello().

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Sends another greeting
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}

Remember to save the file!

Update the app

The next time we compile the app (using compile in sbt), ZIO gRPC will regenerate ZioHelloworld.scala which contains a trait with the service definition. The trait has an abstract method for which RPC method. Therefore, with the new method added to the trait, we expect the compilation of HelloWorldServer.scala to fail, since the method sayHelloAgain will be undefined.

Let's implement the new method in the server and call it from the client.

Update the server

Open src/main/scala/zio_grpc/examples/helloworld/HelloWorldServer.scala, and add the following method to GreeterImpl:

def sayHelloAgain(request: HelloRequest) =
ZIO.succeed(HelloReply(s"Hello again, ${request.name}"))

Update the client

Open src/main/scala/zio_grpc/examples/helloworld/HelloWorldClient.scala, and update the definition of the myAppLogic method in GreeterImpl:

def myAppLogic =
for {
r <- GreeterClient.sayHello(HelloRequest("World"))
_ <- putStrLn(r.message)
s <- GreeterClient.sayHelloAgain(HelloRequest("World"))
_ <- putStrLn(s.message)
} yield ()

Run the updated app

If you still have the previous version of the server running, stop it by hitting Ctrl-C. Then run the server and client like you did before inside the examples directory:

  1. Run the server:

    sbt "runMain zio_grpc.examples.helloworld.HelloWorldServer"
  2. From another terminal, run the client:

    sbt "runMain zio_grpc.examples.helloworld.HelloWorldClient"

What's next

note

This document, "ZIO gRPC: Quick Start", is a derivative of "gRPC Quick Start" by gRPC Authors, used under CC-BY-4.0. "ZIO gRPC: Quick Start" is licensed under CC-BY-4.0 by Nadav Samet.

- + \ No newline at end of file diff --git a/docs/0.5.x/scala.js/index.html b/docs/0.5.x/scala.js/index.html index 4cbf7d7f..41e021fd 100644 --- a/docs/0.5.x/scala.js/index.html +++ b/docs/0.5.x/scala.js/index.html @@ -6,14 +6,14 @@ Using with Scala.js | ZIO gRPC - +
Version: 0.5.x

Using with Scala.js

More information will come here soon. In the mean time, take a look at this example project for using zio-grpc with Scala.js.

- + \ No newline at end of file diff --git a/docs/backpressure/index.html b/docs/backpressure/index.html index 688d7f8e..a10fbe0f 100644 --- a/docs/backpressure/index.html +++ b/docs/backpressure/index.html @@ -6,7 +6,7 @@ Backpressure | ZIO gRPC - + @@ -14,7 +14,7 @@
Version: 0.6.x

Backpressure

From version 0.5.3 onwards, zio-grpc provides backpressure support for server streaming RPCs. In case the call is not capable to sending additional messages without buffering (as determined by ServerCall.isReady), sending messages from the queue associated with server response Stream will stop. The default size of this queue is 16, and can be configured by setting the system property zio_grpc.backpressure_queue_size or the environment variable ZIO_GRPC_BACKPRESSURE_QUEUE_SIZE. Setting the value to 0 or a negative number will disable buffering but keep the back pressure at the chunk level (isReady will be checked after processing each chunk instead of each message).

- + \ No newline at end of file diff --git a/docs/basics/index.html b/docs/basics/index.html index b808bf24..c2c86af8 100644 --- a/docs/basics/index.html +++ b/docs/basics/index.html @@ -6,7 +6,7 @@ Basics Tutorial | ZIO gRPC - + @@ -116,7 +116,7 @@ get the other's messages in the order they were written, both the client and server can read and write in any order — the streams operate completely independently.

Providing the client layer into the application logic

All the effects we created were dependent on a RouteGuideClient available in the environment. We earlier instantiated a clientLayer, so we can provide it to our application logic at the top-level (the run method):

val myAppLogic =
for {
// Looking for a valid feature
_ <- getFeature(409146138, -746188906)
// Looking for a missing feature
_ <- getFeature(0, 0)

// Calls listFeatures with a rectangle of interest. Prints
// each response feature as it arrives.
// start: listFeatures
_ <-
RouteGuideClient
.listFeatures(
Rectangle(
lo = Some(Point(400000000, -750000000)),
hi = Some(Point(420000000, -730000000))
)
)
.zipWithIndex
.foreach { case (feature, index) =>
printLine(s"Result #${index + 1}: $feature")
}
// end: listFeatures

_ <- recordRoute(10)

_ <- routeChat
} yield ()

final def run =
myAppLogic.provideLayer(clientLayer).exitCode

Try it out!

  1. Run the server:

    sbt "runMain zio_grpc.examples.routeguide.RouteGuideServer"
  2. From another terminal, run the client:

    sbt "runMain zio_grpc.examples.routeguide.RouteGuideClientApp"
note

This document, "ZIO gRPC: Basics Tutorial", is a derivative of "gRPC Basics Tutorial" by gRPC Authors, used under CC-BY-4.0. "ZIO gRPC: Basics Tutorial" is licensed under CC-BY-4.0 by Nadav Samet.

- + \ No newline at end of file diff --git a/docs/context/index.html b/docs/context/index.html index c9da5514..da7b6bc2 100644 --- a/docs/context/index.html +++ b/docs/context/index.html @@ -6,21 +6,21 @@ Context and Dependencies | ZIO gRPC - +
Version: 0.6.x

Context and Dependencies

When implementing a server, ZIO gRPC allows you to specify that your service methods depend depends on a context of type Context which can be any Scala type.

For example, we can define a service with handlers that expect a context of type User for each request:

import zio.ZIO
import zio.Console
import zio.Console.printLine
import scalapb.zio_grpc.RequestContext
import myexample.testservice.ZioTestservice.ZSimpleService
import myexample.testservice.{Request, Response}
import io.grpc.{Status, StatusException}

case class User(name: String)

object MyService extends ZSimpleService[User] {
def sayHello(req: Request, user: User): ZIO[Any, StatusException, Response] =
for {
_ <- printLine("I am here!").orDie
} yield Response(s"Hello, ${user.name}")
}

Context transformations

In order to be able to bind our service to a gRPC server, we need to have the -service's Context type to be one of the supported types:

  • scalapb.zio_grpc.RequestContext
  • scalapb.zio_grpc.SafeMetadata
  • Any

The service MyService as defined above expects User as a context. In order to be able to bind it, we will transform it into a service that depends on a context of type RequestContext. To do this, we need to provide the function to produce a User out of a RequestContext. This way, when a request comes in, ZIO gRPC can take the RequestContext (which is request metadata such as headers and options), and use our function to construct a User and provide it into the environment of our original service.

In many typical cases, we may need to retrieve the user from a database, and thus we are using an effectful function RequestContext => IO[Status, User] to find the user.

For example, we can provide a function that returns an effect that always succeeds:

val fixedUserService =
MyService.transformContextZIO((rc: RequestContext) => ZIO.succeed(User("foo")))
// fixedUserService: myexample.testservice.ZioTestservice.GSimpleService[RequestContext, StatusException] = myexample.testservice.ZioTestservice$GSimpleService$$anon$5@4ecec9b8

and we got our service with context of type RequestContext so it can be bound to a gRPC server.

Accessing metadata

Here is how we would extract a user from a metadata header:

import zio.IO
import scalapb.zio_grpc.{ServiceList, ServerMain}

val UserKey = io.grpc.Metadata.Key.of(
"user-key", io.grpc.Metadata.ASCII_STRING_MARSHALLER)
// UserKey: io.grpc.Metadata.Key[String] = Key{name='user-key'}

def findUser(rc: RequestContext): IO[StatusException, User] =
rc.metadata.get(UserKey).flatMap {
case Some(name) => ZIO.succeed(User(name))
case _ => ZIO.fail(
Status.UNAUTHENTICATED.withDescription("No access!").asException)
}

val rcService =
MyService.transformContextZIO(findUser)
// rcService: myexample.testservice.ZioTestservice.GSimpleService[RequestContext, StatusException] = myexample.testservice.ZioTestservice$GSimpleService$$anon$5@776b7085

object MyServer extends ServerMain {
def services = ServiceList.add(rcService)
}

Context transformations that depends on a service

A context transformation may introduce a dependency on another service. For example, you +service's Context type to be one of the supported types:

  • scalapb.zio_grpc.RequestContext
  • scalapb.zio_grpc.SafeMetadata
  • Any

The service MyService as defined above expects User as a context. In order to be able to bind it, we will transform it into a service that depends on a context of type RequestContext. To do this, we need to provide the function to produce a User out of a RequestContext. This way, when a request comes in, ZIO gRPC can take the RequestContext (which is request metadata such as headers and options), and use our function to construct a User and provide it into the environment of our original service.

In many typical cases, we may need to retrieve the user from a database, and thus we are using an effectful function RequestContext => IO[Status, User] to find the user.

For example, we can provide a function that returns an effect that always succeeds:

val fixedUserService =
MyService.transformContextZIO((rc: RequestContext) => ZIO.succeed(User("foo")))
// fixedUserService: myexample.testservice.ZioTestservice.GSimpleService[RequestContext, StatusException] = myexample.testservice.ZioTestservice$GSimpleService$$anon$5@23469e4b

and we got our service with context of type RequestContext so it can be bound to a gRPC server.

Accessing metadata

Here is how we would extract a user from a metadata header:

import zio.IO
import scalapb.zio_grpc.{ServiceList, ServerMain}

val UserKey = io.grpc.Metadata.Key.of(
"user-key", io.grpc.Metadata.ASCII_STRING_MARSHALLER)
// UserKey: io.grpc.Metadata.Key[String] = Key{name='user-key'}

def findUser(rc: RequestContext): IO[StatusException, User] =
rc.metadata.get(UserKey).flatMap {
case Some(name) => ZIO.succeed(User(name))
case _ => ZIO.fail(
Status.UNAUTHENTICATED.withDescription("No access!").asException)
}

val rcService =
MyService.transformContextZIO(findUser)
// rcService: myexample.testservice.ZioTestservice.GSimpleService[RequestContext, StatusException] = myexample.testservice.ZioTestservice$GSimpleService$$anon$5@75e421a3

object MyServer extends ServerMain {
def services = ServiceList.add(rcService)
}

Context transformations that depends on a service

A context transformation may introduce a dependency on another service. For example, you may want to organize your code such that there is a UserDatabase service that provides -a fetchUser effect that retrieves users from a database. Here is how you can do this:

trait UserDatabase {
def fetchUser(name: String): IO[StatusException, User]
}

object UserDatabase {
val layer = zio.ZLayer.succeed(
new UserDatabase {
def fetchUser(name: String): IO[StatusException, User] =
ZIO.succeed(User(name))
})
}

Now, The context transformation effect we apply may introduce an additional environmental dependency to our service. For example:

import zio.Clock._
import zio.Duration._

val myServiceAuthWithDatabase: ZIO[UserDatabase, Nothing, ZSimpleService[RequestContext]] =
ZIO.serviceWith[UserDatabase](
userDatabase =>
MyService.transformContextZIO {
(rc: RequestContext) =>
rc.metadata.get(UserKey)
.someOrFail(Status.UNAUTHENTICATED.asException)
.flatMap(userDatabase.fetchUser(_))
}
)
// myServiceAuthWithDatabase: ZIO[UserDatabase, Nothing, ZSimpleService[RequestContext]] = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myServiceAuthWithDatabase(context.md:104)",
// first = Sync(
// trace = "repl.MdocSession.MdocApp.myServiceAuthWithDatabase(context.md:104)",
// eval = zio.ZIO$ServiceWithZIOPartiallyApplied$$$Lambda$13275/0x00000001031bf840@23f81348
// ),
// successK = zio.ZIO$$$Lambda$13266/0x00000001020fc040@75a1df8b
// )

Now our service can be built from an effect that depends on UserDatabase. This effect can be +a fetchUser effect that retrieves users from a database. Here is how you can do this:

trait UserDatabase {
def fetchUser(name: String): IO[StatusException, User]
}

object UserDatabase {
val layer = zio.ZLayer.succeed(
new UserDatabase {
def fetchUser(name: String): IO[StatusException, User] =
ZIO.succeed(User(name))
})
}

Now, The context transformation effect we apply may introduce an additional environmental dependency to our service. For example:

import zio.Clock._
import zio.Duration._

val myServiceAuthWithDatabase: ZIO[UserDatabase, Nothing, ZSimpleService[RequestContext]] =
ZIO.serviceWith[UserDatabase](
userDatabase =>
MyService.transformContextZIO {
(rc: RequestContext) =>
rc.metadata.get(UserKey)
.someOrFail(Status.UNAUTHENTICATED.asException)
.flatMap(userDatabase.fetchUser(_))
}
)
// myServiceAuthWithDatabase: ZIO[UserDatabase, Nothing, ZSimpleService[RequestContext]] = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myServiceAuthWithDatabase(context.md:104)",
// first = Sync(
// trace = "repl.MdocSession.MdocApp.myServiceAuthWithDatabase(context.md:104)",
// eval = zio.ZIO$ServiceWithZIOPartiallyApplied$$$Lambda$15009/0x00000001040ac840@4a9270b4
// ),
// successK = zio.ZIO$$$Lambda$15000/0x000000010409e840@7744254c
// )

Now our service can be built from an effect that depends on UserDatabase. This effect can be added to a ServiceList using addZIO:

object MyServer2 extends ServerMain {
def services = ServiceList
.addZIO(myServiceAuthWithDatabase)
.provide(UserDatabase.layer)
}

Using a service as ZLayer

If you require more flexibility than provided through ServerMain, you can construct -the server directly.

We first turn our service into a ZLayer:

val myServiceLayer = zio.ZLayer(myServiceAuthWithDatabase)
// myServiceLayer: zio.ZLayer[UserDatabase, Nothing, ZSimpleService[RequestContext]] = Suspend(
// self = zio.ZLayer$$$Lambda$13306/0x00000001031b2440@60eb0004
// )

Notice how the dependencies moved to the input side of the ZLayer and the resulting layer is of -type ZSimpleService[RequestContext].

To use this layer in an app, we can wire it like so:

import scalapb.zio_grpc.ServerLayer
import scalapb.zio_grpc.Server
import zio.ZLayer

val serviceList = ServiceList
.addFromEnvironment[ZSimpleService[RequestContext]]
// serviceList: ServiceList[Any with ZSimpleService[RequestContext]] = scalapb.zio_grpc.ServiceList@43c125b6

val serverLayer =
ServerLayer.fromServiceList(
io.grpc.ServerBuilder.forPort(9000),
serviceList
)
// serverLayer: ZLayer[Any with ZSimpleService[RequestContext], Throwable, Server] = Suspend(
// self = zio.ZLayer$ScopedEnvironmentPartiallyApplied$$$Lambda$13327/0x00000001031a1040@15c81b6a
// )

val ourApp =
ZLayer.make[Server](
serverLayer,
myServiceLayer,
UserDatabase.layer
)
// ourApp: ZLayer[Any, Throwable, Server] = Suspend(
// self = zio.ZLayer$ZLayerProvideSomeOps$$$Lambda$13329/0x00000001031a0840@4a7a82a0
// )

object LayeredApp extends zio.ZIOAppDefault {
def run = ourApp.launch.exitCode
}

serverLayer creates a Server from a ZSimpleService layer and still depends on a UserDatabase. Then, ourApp feeds a UserDatabase.layer into serverLayer to produce +the server directly.

We first turn our service into a ZLayer:

val myServiceLayer = zio.ZLayer(myServiceAuthWithDatabase)
// myServiceLayer: zio.ZLayer[UserDatabase, Nothing, ZSimpleService[RequestContext]] = Suspend(
// self = zio.ZLayer$$$Lambda$15040/0x00000001040c2440@60dd0320
// )

Notice how the dependencies moved to the input side of the ZLayer and the resulting layer is of +type ZSimpleService[RequestContext].

To use this layer in an app, we can wire it like so:

import scalapb.zio_grpc.ServerLayer
import scalapb.zio_grpc.Server
import zio.ZLayer

val serviceList = ServiceList
.addFromEnvironment[ZSimpleService[RequestContext]]
// serviceList: ServiceList[Any with ZSimpleService[RequestContext]] = scalapb.zio_grpc.ServiceList@5484cfb0

val serverLayer =
ServerLayer.fromServiceList(
io.grpc.ServerBuilder.forPort(9000),
serviceList
)
// serverLayer: ZLayer[Any with ZSimpleService[RequestContext], Throwable, Server] = Suspend(
// self = zio.ZLayer$ScopedEnvironmentPartiallyApplied$$$Lambda$15061/0x00000001040cb040@37f7b2de
// )

val ourApp =
ZLayer.make[Server](
serverLayer,
myServiceLayer,
UserDatabase.layer
)
// ourApp: ZLayer[Any, Throwable, Server] = Suspend(
// self = zio.ZLayer$ZLayerProvideSomeOps$$$Lambda$15063/0x00000001040cf840@1543ea94
// )

object LayeredApp extends zio.ZIOAppDefault {
def run = ourApp.launch.exitCode
}

serverLayer creates a Server from a ZSimpleService layer and still depends on a UserDatabase. Then, ourApp feeds a UserDatabase.layer into serverLayer to produce a Server that doesn't depend on anything. In the run method we launch the server layer.

Implementing a service with dependencies

In this scenario, your service depends on two additional services, DepA and DepB. Following ZIO's service pattern, we accept the (interaces of the ) dependencies as constructor parameters.

trait DepA {
def methodA(param: String): ZIO[Any, Nothing, Int]
}

object DepA {
val layer = ZLayer.succeed[DepA](new DepA {
def methodA(param: String) = ???
})
}

object DepB {
val layer = ZLayer.succeed[DepB](new DepB {
def methodB(param: Float) = ???
})
}

trait DepB {
def methodB(param: Float): ZIO[Any, Nothing, Double]
}

case class MyService2(depA: DepA, depB: DepB) extends ZSimpleService[User] {
def sayHello(req: Request, user: User): ZIO[Any, StatusException, Response] =
for {
num1 <- depA.methodA(user.name)
num2 <- depB.methodB(12.3f)
_ <- printLine("I am here $num1 $num2!").orDie
} yield Response(s"Hello, ${user.name}")
}

object MyService2 {
val layer: ZLayer[DepA with DepB, Nothing, ZSimpleService[RequestContext]] =
ZLayer.fromFunction {
(depA: DepA, depB: DepB) =>
MyService2(depA, depB).transformContextZIO(findUser(_))
}
}

Our service layer now depends on the DepA and DepB interfaces. A server can be created like this:

object MyServer3 extends zio.ZIOAppDefault {

val serverLayer =
ServerLayer.fromServiceList(
io.grpc.ServerBuilder.forPort(9000),
ServiceList.addFromEnvironment[ZSimpleService[RequestContext]]
)

val appLayer = ZLayer.make[Server](
serverLayer,
DepA.layer,
DepB.layer,
MyService2.layer
)

def run = ourApp.launch.exitCode
}
- + \ No newline at end of file diff --git a/docs/deadlines/index.html b/docs/deadlines/index.html index 3cee0739..dd00ed07 100644 --- a/docs/deadlines/index.html +++ b/docs/deadlines/index.html @@ -6,7 +6,7 @@ ZIO gRPC and Deadlines | ZIO gRPC - + @@ -19,12 +19,12 @@ the entire process.

In ZIO gRPC you can easily set deadlines (absolute timestamps), or timeouts which are relative to the time the outbound call is made.

Setting timeout for all requests

To set the same timeout for all requests, it is possible to provide a ClientTransform when constructing the client. This transformation is invoked before each request, and can determine the deadline relative to the -system clock at the time the effect is executed.

import myexample.testservice.ZioTestservice.ServiceNameClient
import myexample.testservice.{Request, Response}
import scalapb.zio_grpc.{ZManagedChannel, ClientTransform}
import io.grpc.ManagedChannelBuilder
import zio._
import zio.Console._

val channel = ZManagedChannel(
ManagedChannelBuilder
.forAddress("localhost", 8980)
.usePlaintext()
)
// channel: ZManagedChannel = OnSuccess(
// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",
// first = Sync(
// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",
// eval = zio.ZIO$$$Lambda$13346/0x0000000103bb5840@10cd37c8
// ),
// successK = zio.ZIO$$$Lambda$13266/0x00000001020fc040@75a1df8b
// )

// create layer:
val clientLayer = ServiceNameClient.live(
channel,
ClientTransform.withTimeoutMillis(3000))
// clientLayer: ZLayer[Any, Throwable, ServiceNameClient] = Fold(
// self = Suspend(
// self = zio.ZLayer$ScopedEnvironmentPartiallyApplied$$$Lambda$13327/0x00000001031a1040@e6c5dd9
// ),
// failure = zio.ZLayer$$Lambda$13353/0x00000001031dd840@63cf5a38,
// success = zio.ZLayer$$Lambda$13351/0x0000000103bb2840@e90bc31
// )

val myAppLogicNeedsEnv = for {
// use layer through accessor methods:
res <- ServiceNameClient.unary(Request())
_ <- printLine(res.toString)
} yield ()
// myAppLogicNeedsEnv: ZIO[ServiceNameClient, Exception, Unit] = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myAppLogicNeedsEnv(deadlines.md:40)",
// first = OnSuccess(
// trace = "myexample.testservice.ZioTestservice.ServiceNameAccessors.unary(ZioTestservice.scala:77)",
// first = Sync(
// trace = "myexample.testservice.ZioTestservice.ServiceNameAccessors.unary(ZioTestservice.scala:77)",
// eval = zio.ZIO$ServiceWithZIOPartiallyApplied$$$Lambda$13275/0x00000001031bf840@74c2d9a4
// ),
// successK = zio.ZIO$$$Lambda$13266/0x00000001020fc040@75a1df8b
// ),
// successK = <function1>
// )

Setting timeout for each request

As in the previous example, assuming there is a client in the environment, we can set the timeout -for each request like this:

ServiceNameClient.withTimeoutMillis(3000).unary(Request())
// res0: ZIO[ServiceNameClient, io.grpc.StatusException, Response] = OnSuccess(
// trace = "myexample.testservice.ZioTestservice.ServiceNameAccessors.unary(ZioTestservice.scala:77)",
// first = Sync(
// trace = "myexample.testservice.ZioTestservice.ServiceNameAccessors.unary(ZioTestservice.scala:77)",
// eval = zio.ZIO$ServiceWithZIOPartiallyApplied$$$Lambda$13275/0x00000001031bf840@28685251
// ),
// successK = zio.ZIO$$$Lambda$13266/0x00000001020fc040@75a1df8b
// )

Clients provide (through the GeneratedClient trait) a number of methods that makes it possible to +system clock at the time the effect is executed.

import myexample.testservice.ZioTestservice.ServiceNameClient
import myexample.testservice.{Request, Response}
import scalapb.zio_grpc.{ZManagedChannel, ClientTransform}
import io.grpc.ManagedChannelBuilder
import zio._
import zio.Console._

val channel = ZManagedChannel(
ManagedChannelBuilder
.forAddress("localhost", 8980)
.usePlaintext()
)
// channel: ZManagedChannel = OnSuccess(
// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",
// first = Sync(
// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",
// eval = zio.ZIO$$$Lambda$15080/0x00000001040dd840@3a8a0345
// ),
// successK = zio.ZIO$$$Lambda$15000/0x000000010409e840@7744254c
// )

// create layer:
val clientLayer = ServiceNameClient.live(
channel,
ClientTransform.withTimeoutMillis(3000))
// clientLayer: ZLayer[Any, Throwable, ServiceNameClient] = Fold(
// self = Suspend(
// self = zio.ZLayer$ScopedEnvironmentPartiallyApplied$$$Lambda$15061/0x00000001040cb040@160b2e27
// ),
// failure = zio.ZLayer$$Lambda$15087/0x000000010411c040@890b8a2,
// success = zio.ZLayer$$Lambda$15085/0x000000010411a840@65111152
// )

val myAppLogicNeedsEnv = for {
// use layer through accessor methods:
res <- ServiceNameClient.unary(Request())
_ <- printLine(res.toString)
} yield ()
// myAppLogicNeedsEnv: ZIO[ServiceNameClient, Exception, Unit] = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myAppLogicNeedsEnv(deadlines.md:40)",
// first = OnSuccess(
// trace = "myexample.testservice.ZioTestservice.ServiceNameAccessors.unary(ZioTestservice.scala:77)",
// first = Sync(
// trace = "myexample.testservice.ZioTestservice.ServiceNameAccessors.unary(ZioTestservice.scala:77)",
// eval = zio.ZIO$ServiceWithZIOPartiallyApplied$$$Lambda$15009/0x00000001040ac840@3a22392f
// ),
// successK = zio.ZIO$$$Lambda$15000/0x000000010409e840@7744254c
// ),
// successK = <function1>
// )

Setting timeout for each request

As in the previous example, assuming there is a client in the environment, we can set the timeout +for each request like this:

ServiceNameClient.withTimeoutMillis(3000).unary(Request())
// res0: ZIO[ServiceNameClient, io.grpc.StatusException, Response] = OnSuccess(
// trace = "myexample.testservice.ZioTestservice.ServiceNameAccessors.unary(ZioTestservice.scala:77)",
// first = Sync(
// trace = "myexample.testservice.ZioTestservice.ServiceNameAccessors.unary(ZioTestservice.scala:77)",
// eval = zio.ZIO$ServiceWithZIOPartiallyApplied$$$Lambda$15009/0x00000001040ac840@1e89d681
// ),
// successK = zio.ZIO$$$Lambda$15000/0x000000010409e840@7744254c
// )

Clients provide (through the GeneratedClient trait) a number of methods that makes it possible to specify a deadline or a timeout for each request:

// Provide a new absolute deadline
def withDeadline(deadline: Deadline): Service

// Sets a new timeout for this service
def withTimeout(duration: zio.duration.Duration): Service

// Sets a new timeout in millis
def withTimeoutMillis(millis: Long): Service

// Replace the call options with the provided call options
def withCallOptions(callOptions: CallOptions): Service

// update the CallOptions for this service
def mapCallOptions(f: CallOptions => CallOptions): Service

// update the request Metadata for this service
def mapMetadataZIO(f: SafeMetadata => UIO[SafeMetadata]): Service

If you are using a client instance, the above methods are available to provide you with a new client that has a modified CallOptions effect. Making the copy of those clients is cheap and can -be safely done for each individual call:

val clientScoped = ServiceNameClient.scoped(channel)
// clientScoped: ZIO[Scope, Throwable, ServiceNameClient] = OnSuccess(
// trace = "myexample.testservice.ZioTestservice.ServiceNameClient.scoped(ZioTestservice.scala:109)",
// first = OnSuccess(
// trace = "myexample.testservice.ZioTestservice.ServiceNameClientWithResponseMetadata.scoped(ZioTestservice.scala:191)",
// first = OnSuccess(
// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",
// first = Sync(
// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",
// eval = zio.ZIO$$$Lambda$13346/0x0000000103bb5840@10cd37c8
// ),
// successK = zio.ZIO$$$Lambda$13266/0x00000001020fc040@75a1df8b
// ),
// successK = zio.ZIO$$Lambda$13323/0x00000001031a2840@2dedb04
// ),
// successK = zio.ZIO$$Lambda$13323/0x00000001031a2840@5975cf99
// )

val myAppLogic = ZIO.scoped {
clientScoped.flatMap { client =>
for {
res <- client
.withTimeoutMillis(3000).unary(Request())
} yield res
}
}
// myAppLogic: ZIO[Any, Throwable, Response] = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myAppLogic(deadlines.md:57)",
// first = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myAppLogic(deadlines.md:57)",
// first = Sync(
// trace = "repl.MdocSession.MdocApp.myAppLogic(deadlines.md:57)",
// eval = zio.Scope$ReleaseMap$$$Lambda$13366/0x0000000103bca040@55a243b4
// ),
// successK = zio.ZIO$$Lambda$13323/0x00000001031a2840@1854bea5
// ),
// successK = zio.ZIO$ScopedPartiallyApplied$$$Lambda$13368/0x0000000103bcb840@5f58e1ee
// )
- +be safely done for each individual call:

val clientScoped = ServiceNameClient.scoped(channel)
// clientScoped: ZIO[Scope, Throwable, ServiceNameClient] = OnSuccess(
// trace = "myexample.testservice.ZioTestservice.ServiceNameClient.scoped(ZioTestservice.scala:109)",
// first = OnSuccess(
// trace = "myexample.testservice.ZioTestservice.ServiceNameClientWithResponseMetadata.scoped(ZioTestservice.scala:191)",
// first = OnSuccess(
// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",
// first = Sync(
// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",
// eval = zio.ZIO$$$Lambda$15080/0x00000001040dd840@3a8a0345
// ),
// successK = zio.ZIO$$$Lambda$15000/0x000000010409e840@7744254c
// ),
// successK = zio.ZIO$$Lambda$15057/0x00000001040c9840@3ff6cac8
// ),
// successK = zio.ZIO$$Lambda$15057/0x00000001040c9840@5950d795
// )

val myAppLogic = ZIO.scoped {
clientScoped.flatMap { client =>
for {
res <- client
.withTimeoutMillis(3000).unary(Request())
} yield res
}
}
// myAppLogic: ZIO[Any, Throwable, Response] = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myAppLogic(deadlines.md:57)",
// first = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myAppLogic(deadlines.md:57)",
// first = Sync(
// trace = "repl.MdocSession.MdocApp.myAppLogic(deadlines.md:57)",
// eval = zio.Scope$ReleaseMap$$$Lambda$15100/0x0000000104123840@7681e017
// ),
// successK = zio.ZIO$$Lambda$15057/0x00000001040c9840@105accc0
// ),
// successK = zio.ZIO$ScopedPartiallyApplied$$$Lambda$15102/0x0000000104125040@275b2f90
// )
+ \ No newline at end of file diff --git a/docs/decorating/index.html b/docs/decorating/index.html index 89926b9f..c3e29d63 100644 --- a/docs/decorating/index.html +++ b/docs/decorating/index.html @@ -6,7 +6,7 @@ Decorating services | ZIO gRPC - + @@ -14,8 +14,8 @@
Version: 0.6.x

Decorating services

When implementing a server, sometimes you might want to decorate all methods (effects or streams) in the service, for example to add access and error logging.

It can be done with the help of ZTransform. Instances of this class can be used to apply a transformation to all methods of a service to generate a new "decorated" service. -This can be used for pre- or post-processing of requests/responses and also for context transformations.

We define decoration:

import io.grpc.StatusException
import scalapb.zio_grpc.{ RequestContext, ZTransform }
import zio._
import zio.stream.ZStream

class LoggingTransform extends ZTransform[Any, RequestContext] {

def logCause(rc: RequestContext, cause: Cause[StatusException]): UIO[Unit] = ???

def accessLog(rc: RequestContext): UIO[Unit] = ???

override def effect[A](
io: Any => ZIO[Any, StatusException, A]): RequestContext => ZIO[Any, StatusException, A] = {
rc => io(rc).zipLeft(accessLog(rc)).tapErrorCause(logCause(rc, _))
}

override def stream[A](
io: Any => ZStream[Any, StatusException, A]): RequestContext => ZStream[Any, StatusException, A] = {
rc => (io(rc) ++ ZStream.fromZIO(accessLog(rc)).drain).onError(logCause(rc, _))
}
}

and then we apply it to our service:

import myexample.testservice.ZioTestservice._
import myexample.testservice.{Request, Response}

object MyService extends SimpleService {
def sayHello(req: Request): ZIO[Any, StatusException, Response] =
ZIO.succeed(Response(s"Hello user"))
}

// Note we now have a service with a RequestContext as context.
val decoratedService: ZSimpleService[RequestContext] =
MyService.transform(new LoggingTransform)
// decoratedService: ZSimpleService[RequestContext] = myexample.testservice.ZioTestservice$GSimpleService$$anon$5@12bcec48
- +This can be used for pre- or post-processing of requests/responses and also for context transformations.

We define decoration:

import io.grpc.StatusException
import scalapb.zio_grpc.{ RequestContext, ZTransform }
import zio._
import zio.stream.ZStream

class LoggingTransform extends ZTransform[Any, RequestContext] {

def logCause(rc: RequestContext, cause: Cause[StatusException]): UIO[Unit] = ???

def accessLog(rc: RequestContext): UIO[Unit] = ???

override def effect[A](
io: Any => ZIO[Any, StatusException, A]): RequestContext => ZIO[Any, StatusException, A] = {
rc => io(rc).zipLeft(accessLog(rc)).tapErrorCause(logCause(rc, _))
}

override def stream[A](
io: Any => ZStream[Any, StatusException, A]): RequestContext => ZStream[Any, StatusException, A] = {
rc => (io(rc) ++ ZStream.fromZIO(accessLog(rc)).drain).onError(logCause(rc, _))
}
}

and then we apply it to our service:

import myexample.testservice.ZioTestservice._
import myexample.testservice.{Request, Response}

object MyService extends SimpleService {
def sayHello(req: Request): ZIO[Any, StatusException, Response] =
ZIO.succeed(Response(s"Hello user"))
}

// Note we now have a service with a RequestContext as context.
val decoratedService: ZSimpleService[RequestContext] =
MyService.transform(new LoggingTransform)
// decoratedService: ZSimpleService[RequestContext] = myexample.testservice.ZioTestservice$GSimpleService$$anon$5@14de2eea
+ \ No newline at end of file diff --git a/docs/generated-code/index.html b/docs/generated-code/index.html index 18efb7d4..cd117e86 100644 --- a/docs/generated-code/index.html +++ b/docs/generated-code/index.html @@ -6,7 +6,7 @@ Generated Code Reference | ZIO gRPC - + @@ -16,9 +16,9 @@ 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.

Service trait

Inside the object, for each service MyService that is defined in a .proto file, the following structure is generated:

trait MyService {
// methods for each RPC
def sayHello(request: HelloRequest):
ZIO[Any, StatusException, HelloReply]
}

The trait MyService is to be extended when implementing a server for this service.

object MyServiceImpl extends MyService {
def sayHello(request: HelloRequest): ZIO[Any, StatusException, HelloReply] = ???
}

It is common that services need to extract information from the request context, for example the caller's identity. To accomplish that, there is another trait ZMyService which takes one type parameter Context. The Context type parameter represents any domain object that you would like your RPC methods to receive. Later on, we will see how to convert between a RequestContext which represents the underlying context of the request with your domain model.

The most generic type is called GMyService. It takes two type parameters: Context and Error. The context is the same as before, and the error could be any type you would like your server implementatino to use an error. Before you use this service with the rest of zio-grpc API, you would need to convert the error type -to StatusException by using either mapError or mapErrorZIO.

Learn more about using context and dependencies in the next section.

Running the server

The easiest way to run a service, is to create an object that extends scalapb.zio_grpc.ServerMain:

import scalapb.zio_grpc.{ServerMain, ServiceList}

object MyMain extends ServerMain {
def services = ServiceList.add(ServiceNameImpl)

// Default port is 9000
override def port: Int = 8980
}

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, resourcefully (scoped), or provided through a layer.

Client trait

The generated client follows ZIO's module pattern:

type ServiceNameClient = ServiceNameClient.Service

object ServiceNameClient {
trait ZService[Context] {
// methods for use as a client
def sayHello(request: HelloRequest):
ZIO[Context, StatusException, HelloReply]
}
type Service = ZService[Any]

// accessor methods
def sayHello(request: HelloRequest):
ZIO[ServiceNameClient, StatusException, HelloReply]

def scoped[R](
managedChannel: ZManagedChannel,
options: CallOptions =
io.grpc.CallOptions.DEFAULT,
headers: zio.UIO[SafeMetadata] =
scalapb.zio_grpc.SafeMetadata.make
): zio.Managed[Throwable, ZService[R]]

def live[Context](
managedChannel: ZManagedChannel,
options: CallOptions =
io.grpc.CallOptions.DEFAULT,
headers: zio.UIO[scalapb.zio_grpc.SafeMetadata] =
scalapb.zio_grpc.SafeMetadata.make
): zio.ZLayer[Any, Throwable, ZService[Context]]
}

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 scoped, proper shutdown of the channel is guaranteed:

type ZManagedChannel[R] = ZIO[Scope, Throwable, ZChannel[R]]

Creating a channel:

import scalapb.zio_grpc.ZManagedChannel
import io.grpc.ManagedChannelBuilder

val channel = ZManagedChannel(
ManagedChannelBuilder
.forAddress("localhost", 8980)
.usePlaintext()
)
// channel: ZManagedChannel = OnSuccess(
// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",
// first = Sync(
// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",
// eval = zio.ZIO$$$Lambda$13346/0x0000000103bb5840@6d240eb2
// ),
// successK = zio.ZIO$$$Lambda$13266/0x00000001020fc040@75a1df8b
// )

Using the client as a layer

A single 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 provideLayer at the top of your app:

import myexample.testservice.ZioTestservice.ServiceNameClient
import myexample.testservice.{Request, Response}
import zio._
import zio.Console._

// create layer:
val clientLayer = ServiceNameClient.live(channel)
// clientLayer: ZLayer[Any, Throwable, ServiceNameClient] = Fold(
// self = Suspend(
// self = zio.ZLayer$ScopedEnvironmentPartiallyApplied$$$Lambda$13327/0x00000001031a1040@691b87ce
// ),
// failure = zio.ZLayer$$Lambda$13353/0x00000001031dd840@2c190489,
// success = zio.ZLayer$$Lambda$13351/0x0000000103bb2840@50639ba8
// )

val myAppLogicNeedsEnv = for {
// use layer through accessor methods:
res <- ServiceNameClient.unary(Request())
_ <- printLine(res.toString)
} yield ()
// myAppLogicNeedsEnv: ZIO[ServiceNameClient, Exception, Unit] = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myAppLogicNeedsEnv(generated-code.md:41)",
// first = OnSuccess(
// trace = "myexample.testservice.ZioTestservice.ServiceNameAccessors.unary(ZioTestservice.scala:77)",
// first = Sync(
// trace = "myexample.testservice.ZioTestservice.ServiceNameAccessors.unary(ZioTestservice.scala:77)",
// eval = zio.ZIO$ServiceWithZIOPartiallyApplied$$$Lambda$13275/0x00000001031bf840@28ef8f1e
// ),
// successK = zio.ZIO$$$Lambda$13266/0x00000001020fc040@75a1df8b
// ),
// successK = <function1>
// )

// myAppLogicNeedsEnv needs access to a ServiceNameClient. We turn it into
// a self-contained effect (IO) by providing the layer to it:
val myAppLogic1 = myAppLogicNeedsEnv.provideLayer(clientLayer)
// myAppLogic1: ZIO[Any, Throwable, Unit] = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myAppLogic1(generated-code.md:46)",
// first = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myAppLogic1(generated-code.md:46)",
// first = Sync(
// trace = "repl.MdocSession.MdocApp.myAppLogic1(generated-code.md:46)",
// eval = zio.Scope$ReleaseMap$$$Lambda$13366/0x0000000103bca040@55a243b4
// ),
// successK = zio.ZIO$$Lambda$13323/0x00000001031a2840@33eb6639
// ),
// successK = zio.ZIO$$$Lambda$13419/0x0000000103bf0840@48784349
// )

object LayeredApp extends zio.ZIOAppDefault {
def run: UIO[ExitCode] = myAppLogic1.exitCode
}

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. Once we provide our custom layer, the effect type is ZEnv, which we can use with ZIO's exit method.

Using a Scoped client

As an alternative to using ZLayer, you can use the client as a scoped resource:

import myexample.testservice.ZioTestservice.ServiceNameClient
import myexample.testservice.{Request, Response}

val clientManaged = ServiceNameClient.scoped(channel)
// clientManaged: ZIO[Scope, Throwable, ServiceNameClient] = OnSuccess(
// trace = "myexample.testservice.ZioTestservice.ServiceNameClient.scoped(ZioTestservice.scala:109)",
// first = OnSuccess(
// trace = "myexample.testservice.ZioTestservice.ServiceNameClientWithResponseMetadata.scoped(ZioTestservice.scala:191)",
// first = OnSuccess(
// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",
// first = Sync(
// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",
// eval = zio.ZIO$$$Lambda$13346/0x0000000103bb5840@6d240eb2
// ),
// successK = zio.ZIO$$$Lambda$13266/0x00000001020fc040@75a1df8b
// ),
// successK = zio.ZIO$$Lambda$13323/0x00000001031a2840@6c21286a
// ),
// successK = zio.ZIO$$Lambda$13323/0x00000001031a2840@4f3f7239
// )

val myAppLogic = ZIO.scoped {
clientManaged.flatMap { client =>
for {
res <- client.unary(Request())
} yield res
}
}
// myAppLogic: ZIO[Any, Throwable, Response] = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myAppLogic(generated-code.md:66)",
// first = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myAppLogic(generated-code.md:66)",
// first = Sync(
// trace = "repl.MdocSession.MdocApp.myAppLogic(generated-code.md:66)",
// eval = zio.Scope$ReleaseMap$$$Lambda$13366/0x0000000103bca040@55a243b4
// ),
// successK = zio.ZIO$$Lambda$13323/0x00000001031a2840@2d6562fa
// ),
// successK = zio.ZIO$ScopedPartiallyApplied$$$Lambda$13368/0x0000000103bcb840@48626d44
// )
- +to StatusException by using either mapError or mapErrorZIO.

Learn more about using context and dependencies in the next section.

Running the server

The easiest way to run a service, is to create an object that extends scalapb.zio_grpc.ServerMain:

import scalapb.zio_grpc.{ServerMain, ServiceList}

object MyMain extends ServerMain {
def services = ServiceList.add(ServiceNameImpl)

// Default port is 9000
override def port: Int = 8980
}

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, resourcefully (scoped), or provided through a layer.

Client trait

The generated client follows ZIO's module pattern:

type ServiceNameClient = ServiceNameClient.Service

object ServiceNameClient {
trait ZService[Context] {
// methods for use as a client
def sayHello(request: HelloRequest):
ZIO[Context, StatusException, HelloReply]
}
type Service = ZService[Any]

// accessor methods
def sayHello(request: HelloRequest):
ZIO[ServiceNameClient, StatusException, HelloReply]

def scoped[R](
managedChannel: ZManagedChannel,
options: CallOptions =
io.grpc.CallOptions.DEFAULT,
headers: zio.UIO[SafeMetadata] =
scalapb.zio_grpc.SafeMetadata.make
): zio.Managed[Throwable, ZService[R]]

def live[Context](
managedChannel: ZManagedChannel,
options: CallOptions =
io.grpc.CallOptions.DEFAULT,
headers: zio.UIO[scalapb.zio_grpc.SafeMetadata] =
scalapb.zio_grpc.SafeMetadata.make
): zio.ZLayer[Any, Throwable, ZService[Context]]
}

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 scoped, proper shutdown of the channel is guaranteed:

type ZManagedChannel[R] = ZIO[Scope, Throwable, ZChannel[R]]

Creating a channel:

import scalapb.zio_grpc.ZManagedChannel
import io.grpc.ManagedChannelBuilder

val channel = ZManagedChannel(
ManagedChannelBuilder
.forAddress("localhost", 8980)
.usePlaintext()
)
// channel: ZManagedChannel = OnSuccess(
// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",
// first = Sync(
// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",
// eval = zio.ZIO$$$Lambda$15080/0x00000001040dd840@1f146475
// ),
// successK = zio.ZIO$$$Lambda$15000/0x000000010409e840@7744254c
// )

Using the client as a layer

A single 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 provideLayer at the top of your app:

import myexample.testservice.ZioTestservice.ServiceNameClient
import myexample.testservice.{Request, Response}
import zio._
import zio.Console._

// create layer:
val clientLayer = ServiceNameClient.live(channel)
// clientLayer: ZLayer[Any, Throwable, ServiceNameClient] = Fold(
// self = Suspend(
// self = zio.ZLayer$ScopedEnvironmentPartiallyApplied$$$Lambda$15061/0x00000001040cb040@1b2bf34f
// ),
// failure = zio.ZLayer$$Lambda$15087/0x000000010411c040@5e296e4e,
// success = zio.ZLayer$$Lambda$15085/0x000000010411a840@53b607d4
// )

val myAppLogicNeedsEnv = for {
// use layer through accessor methods:
res <- ServiceNameClient.unary(Request())
_ <- printLine(res.toString)
} yield ()
// myAppLogicNeedsEnv: ZIO[ServiceNameClient, Exception, Unit] = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myAppLogicNeedsEnv(generated-code.md:41)",
// first = OnSuccess(
// trace = "myexample.testservice.ZioTestservice.ServiceNameAccessors.unary(ZioTestservice.scala:77)",
// first = Sync(
// trace = "myexample.testservice.ZioTestservice.ServiceNameAccessors.unary(ZioTestservice.scala:77)",
// eval = zio.ZIO$ServiceWithZIOPartiallyApplied$$$Lambda$15009/0x00000001040ac840@2bc8c128
// ),
// successK = zio.ZIO$$$Lambda$15000/0x000000010409e840@7744254c
// ),
// successK = <function1>
// )

// myAppLogicNeedsEnv needs access to a ServiceNameClient. We turn it into
// a self-contained effect (IO) by providing the layer to it:
val myAppLogic1 = myAppLogicNeedsEnv.provideLayer(clientLayer)
// myAppLogic1: ZIO[Any, Throwable, Unit] = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myAppLogic1(generated-code.md:46)",
// first = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myAppLogic1(generated-code.md:46)",
// first = Sync(
// trace = "repl.MdocSession.MdocApp.myAppLogic1(generated-code.md:46)",
// eval = zio.Scope$ReleaseMap$$$Lambda$15100/0x0000000104123840@7681e017
// ),
// successK = zio.ZIO$$Lambda$15057/0x00000001040c9840@30ccca3f
// ),
// successK = zio.ZIO$$$Lambda$15153/0x000000010414a040@3a48cbbe
// )

object LayeredApp extends zio.ZIOAppDefault {
def run: UIO[ExitCode] = myAppLogic1.exitCode
}

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. Once we provide our custom layer, the effect type is ZEnv, which we can use with ZIO's exit method.

Using a Scoped client

As an alternative to using ZLayer, you can use the client as a scoped resource:

import myexample.testservice.ZioTestservice.ServiceNameClient
import myexample.testservice.{Request, Response}

val clientManaged = ServiceNameClient.scoped(channel)
// clientManaged: ZIO[Scope, Throwable, ServiceNameClient] = OnSuccess(
// trace = "myexample.testservice.ZioTestservice.ServiceNameClient.scoped(ZioTestservice.scala:109)",
// first = OnSuccess(
// trace = "myexample.testservice.ZioTestservice.ServiceNameClientWithResponseMetadata.scoped(ZioTestservice.scala:191)",
// first = OnSuccess(
// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",
// first = Sync(
// trace = "scalapb.zio_grpc.ZManagedChannel.apply(ZManagedChannel.scala:13)",
// eval = zio.ZIO$$$Lambda$15080/0x00000001040dd840@1f146475
// ),
// successK = zio.ZIO$$$Lambda$15000/0x000000010409e840@7744254c
// ),
// successK = zio.ZIO$$Lambda$15057/0x00000001040c9840@a58556c
// ),
// successK = zio.ZIO$$Lambda$15057/0x00000001040c9840@53e86cdc
// )

val myAppLogic = ZIO.scoped {
clientManaged.flatMap { client =>
for {
res <- client.unary(Request())
} yield res
}
}
// myAppLogic: ZIO[Any, Throwable, Response] = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myAppLogic(generated-code.md:66)",
// first = OnSuccess(
// trace = "repl.MdocSession.MdocApp.myAppLogic(generated-code.md:66)",
// first = Sync(
// trace = "repl.MdocSession.MdocApp.myAppLogic(generated-code.md:66)",
// eval = zio.Scope$ReleaseMap$$$Lambda$15100/0x0000000104123840@7681e017
// ),
// successK = zio.ZIO$$Lambda$15057/0x00000001040c9840@794af79d
// ),
// successK = zio.ZIO$ScopedPartiallyApplied$$$Lambda$15102/0x0000000104125040@31713eed
// )
+ \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index deb847f6..52e6ccc2 100644 --- a/docs/index.html +++ b/docs/index.html @@ -6,13 +6,13 @@ Introduction | ZIO gRPC - +
Version: 0.6.x

Introduction

ZIO-gRPC lets you write purely functional gRPC servers and clients. It is built on top of ZIO, a library for asynchronous and concurrent functional programming in Scala.

Highlights

  • Supports all types of RPCs (unary, client streaming, server streaming, bidirectional).
  • Cancellable RPCs: easily cancel RPCs by calling interrupt on the effect. Server will immediately abort execution.
  • Scala.js support: call your service from Scala code running on the browser.

Why ZIO gRPC?

One of the advantages of a microservice architecture is the ability to write different microservices using different technogies. ZIO gRPC might be a great choice for your project if you value:

  • Type-safety: Your business logic and the data types are checked at compile time.
  • Resource safety: Managed resources (such as database connections) are guaranteed to be released.
  • Reusable behaviors: Create complex behaviors by easily combining basic building blocks. For example:
    • Exponential backoff, is a retry method call that gets an exponential schedule as a parameter.
    • Sending a few identical requests to a number of servers and waiting only until the first response.
    • Sending different requests in parallel and collecting all the results as a list.
  • Living on the edge: Yes, this is a word of warning. Both ZIO and ZIO gRPC are new technologies. While a lot of effort has been put to test, it is possible that you will encounter bugs. For ZIO gRPC, APIs may change between minor releases without notice.

Effects as pure values

In ZIO gRPC, the services you will write will be purely functional. When a client makes an RPC call to your service, a "handler" method in your service will be invoked. In contrast to imperative programming, instead of actually handling the call, this handler method will return a pure immutable value of type ZIO. This value, on its own, doesn't do anything - it represents the work that needs to get done to fulfill the request, for example: reading from a database, making a network call, or calling a local function. ZIO's runtime is going to run the effect immediately after you return it. As you will see, structuring your program by combining functional effects will lead to reusable code that is easier to reason about and more likely to be correct once you get it to compile.

There are also technical advantages: in case the client aborts the request, ZIO gRPC can interrupt the server computation even if the server is executing an effect that is unrelated to ZIO gRPC (in grpc-java for example, this can only be accomplished by the server occassionally checking for a cancellation). Using ZIO building blocks such as ZIO.bracket, ZIO#onExit, ZIO.uninterruptible you remain in control over the behavior of the program in case of interruptions.

Try it out

  • Got 5 to 10 more minutes? Check out our Quick Start tutorial. You will clone an existing ZIO gRPC client and a server. You will run them and add a new RPC method.
  • Got up to an hour? Take a look at the Basics tutorial. You will learn how to implement gRPC servers and clients, including all sort of streaming requests available in gRPC. The tutorial will also show you how to hook the clients and servers into a full working ZIO application.
- + \ No newline at end of file diff --git a/docs/installation/index.html b/docs/installation/index.html index e8da285d..cefcb315 100644 --- a/docs/installation/index.html +++ b/docs/installation/index.html @@ -6,7 +6,7 @@ Installing ZIO gRPC | ZIO gRPC - + @@ -15,7 +15,7 @@ avoid unintended evictions and ensure binary compatibility:

ScalaPBZIO gRPC
0.11.x0.5.x
0.10.x0.4.x

If you are building with sbt, add the following to your project/plugins.sbt:

addSbtPlugin("com.thesamet" % "sbt-protoc" % "1.0.6")

libraryDependencies +=
"com.thesamet.scalapb.zio-grpc" %% "zio-grpc-codegen" % "0.6.0-rc6"

Then, add the following lines to your build.sbt:

PB.targets in Compile := Seq(
scalapb.gen(grpc = true) -> (sourceManaged in Compile).value / "scalapb",
scalapb.zio_grpc.ZioCodeGenerator -> (sourceManaged in Compile).value / "scalapb"
)

libraryDependencies ++= Seq(
"io.grpc" % "grpc-netty" % "1.50.1",
"com.thesamet.scalapb" %% "scalapb-runtime-grpc" % scalapb.compiler.Version.scalapbVersion
)

This configuration will set up the ScalaPB code generator alongside the ZIO gRPC code generator. Upon compilation, the source generator will process all proto files under src/main/protobuf. The ScalaPB generator will generate case classes for all messages as well as methods to serialize and deserialize those messages. The ZIO gRPC code generator will generate code as described in the generated code section.

Generating code using ScalaPBC (CLI)

If you are using ScalaPBC to generate Scala code from the CLI, you can invoke the zio code generator like this:

scalapbc \
--plugin-artifact=com.thesamet.scalapb.zio-grpc:protoc-gen-zio:0.6.0-rc6:default,classifier=unix,ext=sh,type=jar\
-- e2e/src/main/protobuf/service.proto --zio_out=/tmp/out --scala_out=grpc:/tmp/out \
-Ie2e/src/main/protobuf -Ithird_party -Iprotobuf

You will need to add to your project the following libraries:

  • com.thesamet.scalapb::scalapb-runtime-grpc:0.11.14
  • com.thesamet.scalapb.zio-grpc:zio-grpc-core:0.6.0-rc6
  • io.grpc:grpc-netty:1.50.1
- + \ No newline at end of file diff --git a/docs/quickstart/index.html b/docs/quickstart/index.html index 22bdef67..eccf0f54 100644 --- a/docs/quickstart/index.html +++ b/docs/quickstart/index.html @@ -6,13 +6,13 @@ Quick Start | ZIO gRPC - +
Version: 0.6.x

Quick Start

This guide gets you started with ZIO gRPC with a simple working example.

Prerequisites

Get the example code

The example code is part of the zio-grpc repository.

  1. Download the repo as a zip file and unzip it, or clone the repo:

    git clone https://github.com/scalapb/zio-grpc
  2. Change to the examples directory:

    cd zio-grpc/examples/helloworld

Run the example

From the examples directory:

  1. Run the server:

    sbt "runMain zio_grpc.examples.helloworld.HelloWorldServer"
  2. From another terminal, run the client:

    sbt "runMain zio_grpc.examples.helloworld.HelloWorldClient"

Congratulations! You’ve just run a client-server application with ZIO gRPC.

Update a gRPC service

In this section you’ll update the application by adding an extra server method. The gRPC service is defined using protocol buffers. To learn more about how to define a service in a .proto file see Basics Tutorial. For now, all you need to know is that both the server and the client stub have a SayHello() RPC method that takes a HelloRequest parameter from the client and returns a HelloReply from the server, and that the method is defined like this:

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}

Open src/main/protobuf/helloworld.proto and add a new SayHelloAgain() method with the same request and response types as SayHello().

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Sends another greeting
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}

Remember to save the file!

Update the app

The next time we compile the app (using compile in sbt), ZIO gRPC will regenerate ZioHelloworld.scala which contains a trait with the service definition. The trait has an abstract method for which RPC method. Therefore, with the new method added to the trait, we expect the compilation of HelloWorldServer.scala to fail, since the method sayHelloAgain will be undefined.

Let's implement the new method in the server and call it from the client.

Update the server

Open src/main/scala/zio_grpc/examples/helloworld/HelloWorldServer.scala, and add the following method to GreeterImpl:

def sayHelloAgain(request: HelloRequest) =
ZIO.succeed(HelloReply(s"Hello again, ${request.name}"))

The example project includes another implementation of this service which you will need to change in order for the project to compile. Open src/main/scala/zio_grpc/examples/helloworld/GreeterWithDatabase.scala and add the same method to the GreeterWithDatabase class:

def sayHelloAgain(request: HelloRequest) =
ZIO.succeed(HelloReply(s"Hello again, ${request.name}"))

Update the client

Open src/main/scala/zio_grpc/examples/helloworld/HelloWorldClient.scala, and update the definition of the myAppLogic method in GreeterImpl:

def myAppLogic =
for {
r <- GreeterClient.sayHello(HelloRequest("World"))
_ <- printLine(r.message)
s <- GreeterClient.sayHelloAgain(HelloRequest("World"))
_ <- printLine(s.message)
} yield ()

Run the updated app

If you still have the previous version of the server running, stop it by hitting Ctrl-C. Then run the server and client like you did before inside the examples directory:

  1. Run the server:

    sbt "runMain zio_grpc.examples.helloworld.HelloWorldServer"
  2. From another terminal, run the client:

    sbt "runMain zio_grpc.examples.helloworld.HelloWorldClient"

What's next

note

This document, "ZIO gRPC: Quick Start", is a derivative of "gRPC Quick Start" by gRPC Authors, used under CC-BY-4.0. "ZIO gRPC: Quick Start" is licensed under CC-BY-4.0 by Nadav Samet.

- + \ No newline at end of file diff --git a/docs/scala.js/index.html b/docs/scala.js/index.html index 8884466a..97aacddf 100644 --- a/docs/scala.js/index.html +++ b/docs/scala.js/index.html @@ -6,14 +6,14 @@ Using with Scala.js | ZIO gRPC - + - + \ No newline at end of file diff --git a/index.html b/index.html index 7f4f11c6..0a10a019 100644 --- a/index.html +++ b/index.html @@ -6,13 +6,13 @@ ZIO gRPC: Write gRPC services and clients with ZIO | ZIO gRPC - +

Build gRPC clients and servers with ZIO

[object Object]

Start Quickly and Scale

Build your first gRPC server in minutes and scale to production loads.

[object Object]

Functional and Type-safe

Use the power of Functional Programming and the Scala compiler to build robust, correct and fully-featured gRPC servers.

[object Object]

Stream with ZStream

Use ZIO's feature-rich ZStreams to create server-streaming, client-streaming and bi-directionally streaming RPC endpoints.

[object Object]

Highly Concurrent

Leverage the power of ZIO to build asynchronous clients and servers without deadlocks and race conditions.

[object Object]

Safe Cancellations

Safely cancel an RPC call by interrupting the effect. Resources on the server will never leak!

[object Object]

Browser Ready

ZIO gRPC comes with Scala.js support so you can send RPCs to your service from the browser.

- + \ No newline at end of file