Rust gRPC Server

We'll be expending our repo to get a gRPC server binary created. In the next chapter we'll use Bazel to build this into a docker container.

We'll put the server in a new directory, $HOME/repo/src/services/summation. Again, Bazel doesn't care how we arrange our repository, at this point we've come up with this package structure:

src/proto/summation
src/summation
src/services/summation

The src/summation directory looks a little weird. We could pretty easily move it to something like src/lib/summation. We could also move things aroung to have:

src/summation/proto
src/summation/lib
src/summation/services

One feature of a monorepo where all the dependencies are self-contained is it's easier to move things after the fact. In general it's easier to make breaking and backwards incompatible changes in a monorepo. We won't do that here and we'll keep things as is.

Exposing tokio crate

We'll use tokio to run our server. If you look in $HOME/repo/third_party/rust/remote you'll see that cargo raze has already pulled down tokio because it's a transitive dependency for other things. If you look at ``$HOME/repo/third_party/rust/BUILD.bazelyou'll seecargo razemade that BUILD file and exposes third-party crates using thealiasrule. This is what exposes these crates under the//third_party/rustpath, and cargo-raze only exposes the dependencies we explictly list in$HOME/repo/third_party/rust/Cargo.toml`.

Let's update [dependencies] of $HOME/repo/third_party/rust/Cargo.toml to include tokio:

[dependencies]
clap = { version = "4.2.2", features = ["derive"] }
log = "0.4.17"
prost = "0.11.6"
tonic = { version = "0.9.1", features = ["tls", "tls-roots", "default"] }
tonic-build = "0.9.1"
tokio = "1.27"

Then rerun cargo raze:

cd $HOME/repo/third_party/rust
cargo raze

We didn't remove the lock file because we're not expecting and don't want this step to change the versions of any of our third party crates.

Creating the gRPC server

Now Let's start making $HOME/repo/src/services/summation/main.rs with:

use src_proto_summation::summation_server::SummationServer;
use std::env;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use tonic::transport::Server;

mod my_summation;
use my_summation::MySummation;

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let port = env::var("PORT")
        .map(|p| p.parse::<u16>())
        .unwrap_or(Ok(50051))?;
    let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), port);
    let summation = MySummation::new();

    Server::builder()
        .add_service(SummationServer::new(summation))
        .serve(addr)
        .await?;

    Ok(())
}

Then create $HOME/repo/src/services/summation/my_summation.rs

#![allow(unused)]
fn main() {
use src_proto_summation::summation_server::Summation;
use src_proto_summation::ComputeSumF64Request;
use src_proto_summation::ComputeSumF64Response;
use src_summation::f64::summation_f64;
use tonic::{Request, Response, Status};

pub struct MySummation {}

impl MySummation {
    pub fn new() -> Self {
        MySummation {}
    }
}

#[tonic::async_trait]
impl Summation for MySummation {
    async fn compute_sum_f64(
        &self,
        request: Request<ComputeSumF64Request>,
    ) -> Result<Response<ComputeSumF64Response>, Status> {
        let request = request.into_inner();
        let sum = summation_f64(&request.value);
        Ok(Response::new(ComputeSumF64Response { sum }))
    }
}
}

And finally make $HOME/repo/src/services/summation/BUILD:

load("@rules_rust//rust:defs.bzl", "rust_binary")

rust_binary(
    name = "server",
    srcs = [
        "main.rs",
        "my_summation.rs",
    ],
    deps = [
        "//src/proto/summation:src_proto_summation",
        "//src/summation:src_summation",
        "//third_party/rust:tokio",
        "//third_party/rust:tonic",
    ],
)

Now let's try to build with bazel build //.... Oops, it doesn't work because //src/summation:src_summation isn't visible to our new package.

Updating visibility of //src/summation:src_summation

The visibility attribute on our targets controls who can depend on a target. In //src/summation/BUILD we omitted visibility for the src_summation target, which means it defaults to only being visible to targets in that same BUILD file. So our //src/summation:executable target could depend on it, but //src/services/summation can't.

In a multi-owner repo where one team might own //src/summation and another team owns //src/services/summation this helps the first team ensure they control who can depend on them. (Usually you'll have a code review process with CODEOWNERS to ensure the //src/summation team reviews/approves any changes to visibility).

To make //src/summation:src_summation visible to /src/services/summation we'll add visibility = ["//src/services/summation:__pgk__"] to the src_summation target in $HOME/repo/src/summation/BUILD:

rust_library(
    name = "src_summation",
    srcs = [
        "lib.rs",
        "f64.rs",
        "u32.rs",
    ],
    deps = ["//third_party/rust:log"],
    visibility = ["//src/services/summation:__pkg__"],
)

Build and test

Now when we run bazel build //... everything should build.

Next, lets run our server:

bazel run -c opt //src/services/summation:server

Finally, to test it we'll use grpcurl. If you're on the debian VM we built you can use the following to get the binary:

cd $HOME/repo
curl -L https://github.com/fullstorydev/grpcurl/releases/download/v1.8.7/grpcurl_1.8.7_linux_x86_64.tar.gz -o grpcurl.tar.gz
tar -xzvf grpcurl.tar.gz grpcurl

And then run it:

cd $HOME/repo
./grpcurl -proto repo/src/proto/summation/summation.proto -plaintext -d '{"value": 5.0, "value": 2.0}' localhost:50051 src_proto_summation.Summation/ComputeSumF64

This should output:

{
  "sum": 7
}