A Clojure library designed to provide hassle-free, ready to go gRPC experience without ton of preparations and Java code...
...or just a bunch of macros functions and macros.
- Enable easy and fast integration of gRPC into existing projects
- Save user from painful Java-in-Clojure experience
- Only require user to read basic Java tutorial for gRPC (https://grpc.io/docs/tutorials/basic/java/) without necessarily
taking deep dive into all the Java libraries around gRPC, servers like
netty
, what Java class to import where, etc. - Avoid creating new logic, letting user to follow official Java guide.
- One gRPC service is implemented per Clojure namespace
- Maybe being too optimistic about
kebab-case
tocamelCase
conversion in some places (weird identifiers might cause problems here and there)
- Client-streaming methods
- Tests - unfortunately, there are none (only thin layer of
spec
). But this is just a bunch of macros developed on real project, so I had no time to build full testing workflow.
- https://grpc.io/docs/tutorials/basic/java/ - for general gRPC in Java tutorial
- https://github.com/danielsz/system - as
clj-grpc
currently implements onlysystem
(component
) behavior. - https://blog.jmibanez.com/2018/07/22/grpc-with-clojure-and-leiningen.html - for examples of Java-level gRPC in Clojure
- @LiaisonTechnologies and @awebneck for
lein-protoc
- @jmibanez for gRPC introduction in Clojure https://blog.jmibanez.com/2018/07/22/grpc-with-clojure-and-leiningen.html
- In
project.clj
addclj-grpc
to dependencies:
[clj-grpc "0.1.0"]
- Configure
lein-protoc
plugin. Please note, that until originalprotoc
is fixed, for Clojure 1.9 you have to useawebneck
fork. For more details, see https://github.com/LiaisonTechnologies/lein-protoc/pull/16
:plugins [...
[org.clojars.awebneck/lein-protoc "0.5.5"]
...
]
;; decide which version of protoc and grpc-java to use
:protoc-version "3.10.0"
:protoc-grpc {:version "1.25.0"}
:protoc-source-paths ["src/proto"] ;; where to look for `.proto` files
:proto-target-path "target/generated-sources/protobuf" ;; where should protoc put generated sources
:java-source-paths [... "target/generated-sources/protobuf"] ;; point java compiler to newly generated sources
- Create
example.proto
file insrc/protoc
:
syntax = "proto3";
option java_package = "example.grpc_api";
option java_outer_classname = "ExampleProto";
package example;
message GetAllExamplesParams {
}
message GetExampleParams {
string example_id = 1;
}
service Example {
rpc GetExample (GetExampleParams) returns (Example) {
}
rpc GetAllExamples (GetAllExamplesParams) returns (stream Example) {
}
}
message Example {
string example_id = 1;
string name = 2;
string description = 3;
}
- Create namespace implementing
grpc
service:
(ns example.grpc-api.core
;; require macros from `clj-grpc.server` namespace
(:require [clj-grpc.server :refer [implement-grpc-service defrpc on-next]]))
;; describe service which will be implemented in this namespace.
;; `:java-package` and `:java-outer-classname` has to be consistent with `proto` file
;;
;; NOTE: it might be necessary to restart Clojure process after this is defined so all the classes will be compiled
(implement-grpc-service Client
:java-package "data_engine.grpc.client"
:java-outer-classname "ClientProto" )
;; define RPC method `getExample` returning only one example
(defrpc getExample [_this req res]
;;
;; do some code here
;;
;; Finally call `on-next` which builds message of type `Example`, initializes it with provided map
;; handling kebab-case to camelCase conversion and sends it through StreamObserver `res`
(on-next "Example" res
{:name "Name"
:description "Description"
:example-id (:example-id req)}))
;; Another example of method, this time `server-streaming`.
;; Implemenation has nothing specific about it, just call `on-next` multiple times
(defrpc getAllExamples [_this _req res]
(dotimes [x 10]
(on-next "Example" res
{:name (str "Name " x)
:description (str "Description" x)
:example-id (str "example-" x)})))
- Assuming you're using
danielsz/system
, addGrpcServer
component to your system definition:
;; Require `new-grpc-server`
(ns example.systems
"System definition"
(:require [com.stuartsierra.component :as component]
[system.core :refer [defsystem]]
[clj-grpc.server :refer [new-grpc-server]]
...
)
;; Add `GrpcServer` to system definition. Specify port on which to listen. It will automatically server
;; all services defined with `IMPLEMENT-GRPC-SERVICE`
(defsystem dev-system
[...
:my-grpc-server (new-grpc-server :port 5000)
...
]
- Otherwise, just call
(start-grpc (make-grpc-server :port <port-number>))
fromclj-grpc.server
and you should be up and running.
Start you system with system's (start)
and you should have your service running!
To avoid warnings and unexpected behaviour when using provided macros, configure their IDE resolution as follows:
implement-grpc-service
- resolve asdef
defgrpc
- resolve asdefn
Behavior won't be perfect, but good enough - for example warnings about unused functions might still occur.
See https://cursive-ide.com/userguide/macros.html#customising-symbol-resolution for more info.
Copyright © 2019 Slawomir Gonet [email protected]
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.