From abe785db4321ba2771b34d176ec62558e241aab2 Mon Sep 17 00:00:00 2001 From: JasterV <49537445+JasterV@users.noreply.github.com> Date: Thu, 30 Oct 2025 12:40:40 +0100 Subject: [PATCH] update: practical_grpc slides --- docs/practical_grpc.md | 328 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) diff --git a/docs/practical_grpc.md b/docs/practical_grpc.md index ee59761..ca5060b 100644 --- a/docs/practical_grpc.md +++ b/docs/practical_grpc.md @@ -508,3 +508,331 @@ publish = ["artifactory"] +--- + +## Building a gRPC server + +Tonic logo + +--- + +### **Importing our library:** Required setup + +```toml +# .cargo/config.toml + +[registries.policy-management-crates] +index = "sparse+https://prima.jfrog.io/artifactory/api/cargo/policy-management-crates/index/" + +[registry] +global-credential-providers = ["cargo:token"] +``` + +--- + +### **Importing our library:** Required setup + +```toml +# ~/.cargo/credentials.toml + +[registries.policy-management] +token = "Bearer " +``` + +--- + +### Importing our library + +```toml +# Cargo.toml + +es-policy-grpc = { version = "=0.6.4", registry = "policy-management-crates", features = [ + "tonic_0_14", +] } +prost = "0.14" +prost-types = "0.14" +tonic = { version = "0.14", features = [ + "gzip", + "server", + "tls-native-roots", + "tls-ring", + "tls-webpki-roots", +] } +tonic-health = "0.14" +``` + +--- + +### Implementing the service trait + +```rust +// .. +use es_policy_grpc::policy_service::v1::PolicyManagementService; + +pub struct PolicyManagementServiceImpl { + application: Application, +} + +#[tonic::async_trait] +impl PolicyManagementService for PolicyManagementServiceImpl { + // .. +} +``` + +--- + +### Implementing the service trait + +```rust +// .. +impl PolicyManagementService for PolicyManagementServiceImpl { + async fn issue_policy( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let issue_policy = request.try_to_domain().map_err(|err| { + Status::invalid_argument( + format!("Failed to parse issue policy request: '{err}'") + ) + })?; + + let policy_id = self + .application + .policy_service() + .issue(issue_policy) + .await + .map_err(|err| Status::internal( + format!("Error issuing policy; error '{err:?}'") + ))?; + + Ok(Response::new(IssuePolicyResponse { policy_id: policy_id.to_string() })) + } +} +``` + +--- + +### **Implementing the service trait:** Parsing request data + +```rust +impl TryToDomain for IssuePolicyRequest { + fn try_to_domain(self) -> Result { + Ok(DomainIssuePolicyRequest { + start_at: self + .start_at + .ok_or(ParseGrpcError::MissingField("start_at")) + .and_then(|v| { + parse_prost_timestamp(v).map_err(|err| + ParseGrpcError::InvalidField("start_at", err) + ) + })?, + purchased_at: self + .purchased_at + .ok_or(ParseGrpcError::MissingField("purchased_at")) + .and_then(|v| { + parse_prost_timestamp(v).map_err(|err| + ParseGrpcError::InvalidField("purchased_at", err) + ) + })?, + transaction: self + .transaction + .clone() + .ok_or(ParseGrpcError::MissingField("transaction"))? + .try_to_domain()?, + //.. + }) + } +} +``` +--- + +### **Implementing the service trait:** Parsing request data + +```rust +impl TryToDomain for IssuingCompany { + fn try_to_domain(self) -> Result { + match self { + IssuingCompany::Unspecified => { + Err(ParseGrpcError::UnspecifiedEnumVariant("IssuingCompany")) + } + IssuingCompany::Iptiq => Ok(DomainIssuingCompany::Iptiq), + IssuingCompany::GreatLakes => Ok(DomainIssuingCompany::GreatLakes), + } + } +} +``` + +--- + +### **Implementing the service trait:** Parsing request data + +```rust +impl TryToDomain for OwnerInformation { + fn try_to_domain(self) -> Result { + Ok(OwnerInfo { + name: self.first_name, + first_last_name: self.primary_last_name, + second_last_name: self.secondary_last_name, + birth_date: parse_proto_naive_date(self.birth_date.ok_or( + ParseGrpcError::MissingField("Policy vehicle owner birthdate"), + )?) + .map_err(|e| { + ParseGrpcError::InvalidField("Policy vehicle owner birthdate", e.into()) + })?, + residential_address: match self.address { + Some(address) => Some(address.try_to_domain()?), + None => None, + }, + document_id: self.document, + }) + } +} +``` + +--- + +### Exposing our server + +```rust +let router = Server::builder() + .add_service(PolicyManagementServiceServer::new( + PolicyManagementServiceImpl::new(application) + )); + +let listener = tokio::net::TcpListener::bind(("0.0.0.0", port)) + .await + .context("Failed to open socket connection for gRPC server")?; + +router + .serve_with_incoming(TcpListenerStream::new(listener)) + .await?; +``` + +--- + +### **Middleware:** Authentication + +```rust +use prima_tower::authentication::verify_jwt::JwtAuthLayer; + +// .. + +let jwks_client = JwksClient::builder().build( + WebSource::builder() + .build(jwks_url) + .expect("Failed to build WebSource for JwksClient"), +); + +let service = ServiceBuilder::new() + .layer(JwtAuthLayer::new(jwks_client, auth0_audience)) + .named_layer(PolicyManagementServiceServer::new( + PolicyManagementServiceImpl::new(application), + )); + +let router = Server::builder().add_service(service); +``` + +--- + +### **Middleware:** Tracing + +```rust +use prima_tower::trace::OpenTelemetryServerTracingLayer; +use tower_http::sensitive_headers::{ + SetSensitiveRequestHeadersLayer, SetSensitiveResponseHeadersLayer, +}; + +// .. + +let service = ServiceBuilder::new() + .layer(SetSensitiveRequestHeadersLayer::new([ + header::AUTHORIZATION, + ])) + .layer(OpenTelemetryServerTracingLayer::new_for_grpc()) + .layer(SetSensitiveResponseHeadersLayer::new([ + header::AUTHORIZATION, + ])) + // .. + .named_layer(PolicyManagementServiceServer::new( + PolicyManagementServiceImpl::new(application), + )); + +let router = Server::builder().add_service(service); +``` + +--- + +## **Deploying** our server + +--- + +### **Deploying** our server + +```yaml +# deploy/values/default.yml + +default: + # .. + container: + # .. + environmentVars: + language: rust + port: 50051 + run_migrations: false + extras: + # .. environment vars + ports: + - name: grpc + containerPort: 50051 + protocol: TCP +``` + +--- + +### **Deploying** our server + +```yaml +# deploy/values/default.yml + +apps: + web: + deployment: + serviceAccount: web + containers: + main: + environmentVars: + role: web + startupProbe: + grpc: + port: 50051 + initialDelaySeconds: 15 + livenessProbe: + grpc: + port: 50051 + initialDelaySeconds: 15 + readinessProbe: + grpc: + port: 50051 + initialDelaySeconds: 15 +``` + +--- + +### **Deploying** our server + +```yaml +# deploy/values/default.yml + +services: + default: + selector: web + spec: + type: ClusterIP + ports: + - port: 50051 + targetPort: 50051 + name: web + protocol: TCP +```