Clojure server/client utilities for GRPC communication.
FIXME: add clojars coords
This library is not concerned with compiling .proto
files (protocol buffers).
These are expected to have been compiled in advance (via protoc
). In addition, the
generated Java source files, are expected to have been compiled too (via javac
).
In other words, all the necessary Java class files should be in-place, before attempting to load namespaces with service definitions. Moreover, such namespaces should be AOT-compiled.
After compiling a .proto
service you end up with a Class like xxxGrpc
,
containing an inner Class xxxImplBase
. This contains abstract method(s)
which we will need to override.
Assuming the standard greeting.proto,
the following macro will emit gen-class
expressions for extending GreeterGrpc$GreeterImplBase
.
(service/impl "Greeter"
:java-package "com.foo.services"
:java-outer-classname "greeting"
:metadata [:user-id]) ;; more on this later
After the above is evaluated we have essentially a proxy to GreeterGrpc$GreeterImplBase
in the current namespace. We now need to implement the service method(s), and
service/defgrpc
does exactly that:
(declare greet!)
(service/defgrpc SayHello ;; fn-name/method we're overriding
[HelloRequest HelloReply] ;; request/response types
`[middleware/with-trace-logs] ;; middleware stack
`greet!) ;; the actual (business-logic) handler
A handler is simply a function taking a request (conforming the provided request type), and producing a response (conforming the provided response type). The following function is a perfect candidate for our example:
;; takes a request-map conforming to `HelloRequest`,
;; and MUST produce a response-map conforming to `HelloReply`
(defn greet!
[{:keys [name] :as req}]
(println "Request metadata:" (meta req))
{:message (str "Hi " name)})
Clients calling a particular grpc-method have the option to attach metadata. This library exposes a mechanism for propagating that metadata automatically via the Context. All you need to do is to declare the set of keys each service expects, and they will be made available as clojure metadata (on the request map), to all service handlers.
For example, earlier we saw that the Greeter
service-impl declared [:user-id]
as
the metadata it expects, and then we saw that the greet!
function can just call meta
on its argument.
Once you have defined all your services, a server can be started with
(def SERVER (start! {:target "localhost:5050"}))
. You don't need to
explicitly add the services (they are auto-discovered), although you can.
FIXME
Copyright © 2024 Dimitrios Piliouras
This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0.
This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version, with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html.