Added go rpc and grpc sections

This commit is contained in:
2025-01-10 18:38:39 -05:00
parent 894a15525a
commit 9458db3381
4 changed files with 310 additions and 0 deletions

View File

@@ -0,0 +1,212 @@
# gRPC
GRPC is an enhancement to RPC in that both sides don't have to be written in Go, because there are libraries for a lot of other languages. It also defines it's payload types in separate proto files so they can be shared among teams.
Documentation: https://grpc.io/docs/languages/go/basics/
## Install GRPC
```bash
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.27
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
```
## Proto Files
```proto
// log.proto
syntax = "proto3";
package logs;
option go_package = "/logs";
message Log{
string name = 1;
string data = 2;
}
message LogRequest{
Log logEntry = 1;
}
message LogResponse{
string result = 1;
}
service LogService{
rpc WriteLog(LogRequest) returns (LogResponse);
}
```
## Compile Protos
Proto compiler link: https://grpc.io/docs/protoc-installation/. This should be stored somewhere in your path, preferably in your `~/go/bin` directory.
In directory with your proto file, run the below command. This will generate
```bash
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative logs.proto
```
## Listener
### Install dependencies
```bash
go get google.golang.org/gprc
go get google.golang.org/protobuf
```
### Implement handlers
```go
// grpc.go
package main
import (
"context"
"fmt"
"log"
"logger/data" // our db models
"logger/logs" // our proto files
"net"
"google.golang.org/grpc"
)
type LogServer struct {
logs.UnimplementedLogServiceServer
Models data.Models // db writer
}
// Using code generated from proto
func (l *LogServer) WriteLog(ctx context.Context, req *logs.LogRequest) (*logs.LogResponse, error) {
input := req.GetLogEntry() // auto generated function
logEntry := data.LogEntry{
Name: input.Name,
Data: input.Data,
}
err := l.Models.LogEntry.Insert(logEntry)
if err != nil {
resp := &logs.LogResponse{
Result: "failed",
}
return resp, err
}
resp := &logs.LogResponse{
Result: "logged!",
}
return resp, nil
}
func (app *Config) gRPCListen() {
lis, err := net.Listen("tcp", fmt.Sprintf(":%s", grpcPort))
if err != nil {
log.Fatalf("failed to listen to gRPC: %v", err)
}
s := grpc.NewServer()
logs.RegisterLogServiceServer(s, &LogServer{
Models: app.Models,
})
log.Printf("gRPC server listening on port %s", grpcPort)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve gRPC: %v", err)
}
}
```
### Instantiate server
```go
// grpc.go
func (app *Config) gRPCListen() {
lis, err := net.Listen("tcp", fmt.Sprintf(":%s", grpcPort))
if err != nil {
log.Fatalf("failed to listen to gRPC: %v", err)
}
s := grpc.NewServer()
// auto generated function
logs.RegisterLogServiceServer(s, &LogServer{
Models: app.Models,
})
log.Printf("gRPC server listening on port %s", grpcPort)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve gRPC: %v", err)
}
}
```
```go
// main.go
go app.grpcLisen()
```
## Sender
The sender must also have access to the proto files generated by the listener. It would be a good idea to store these in a separate repo so that multiple teams can access them from a central source of truth.
```go
import (
"context"
"log" // proto
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func (app *Config) logItemViaGRPC(w http.ResponseWriter, r *http.Request) {
var requestPayload RequestPayload
err := app.readJSON(w, r, &requestPayload)
if err != nil {
app.errorJSON(w, err)
return
}
// need to pass some sort of credentials to the server
conn, err := grpc.Dial("logger:50001", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
if err != nil {
app.errorJSON(w, err)
return
}
defer conn.Close()
// generated in proto
client := logs.NewLogServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
_, err = client.WriteLog(ctx, &logs.LogRequest{
LogEntry: &logs.Log{
Name: requestPayload.Log.Name,
Data: requestPayload.Log.Data,
},
})
if err != nil {
app.errorJSON(w, err)
return
}
payload := jsonResponse{
Error: false,
Message: "logged via gRPC",
}
app.writeJSON(w, http.StatusAccepted, payload)
}
```

0
docs/go/advanced/http.md Normal file
View File

View File

@@ -0,0 +1,97 @@
# RPC
RPC stands for Remote Procedure Calls, and allows for external Go microservices to call functions in our application directly.
## Listener
To do this, you need to define a RPCServer dummy type, and payload to receive, and a function that gets a pointer to a response string and returns an error.
```go
type RPCServer struct{}
type RPCPayload struct {
Name string
Data string
}
func (r *RPCServer) LogInfo(payload RPCPayload, resp *string) error {
collection := client.Database("logs").Collection("logs")
_, err := collection.InsertOne(context.TODO(), data.LogEntry{
Name: payload.Name,
Data: payload.Data,
})
if err != nil {
log.Println("error writing to mongo ", err)
return err
}
*resp = "processed payload via RPC: " + payload.Name
return nil
}
```
To instantiate the RPCServer, use the below syntax in your main application file. Basically you define a function that creates the connection and listens on a port. Then, you register your RPC handler type and serve the RPC server.
```go
func (app *Config) rpcListen() error {
log.Println("Starting RPC server on port " + RPC_PORT)
listen, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%s", RPC_PORT))
if err != nil {
return err
}
defer listen.Close()
for {
rpcConn, err := listen.Accept()
if err != nil {
log.Println("Error accepting connection: ", err)
continue
}
go rpc.ServeConn(rpcConn)
}
}
// in function where you start your app...
err = rpc.Register(new(RPCServer))
go app.rpcListen()
```
## Sender
To send messages via RPC, you need to create a type for the payload that exactly matches that of the receiver.
```go
type RPCPayload struct {
Name string
Data string
}
func (app *Config) logItemViaRPC(r http.ResponseWriter, l LogPayload) {
client, err := rpc.Dial("tcp", "logger:5001")
if err != nil {
app.errorJSON(r, err)
return
}
payload := RPCPayload{
Name: l.Name,
Data: l.Data,
}
var resp string
err = client.Call("RPCServer.LogInfo", payload, &resp)
if err != nil {
app.errorJSON(r, err)
return
}
returnPayload := jsonResponse{
Error: false,
Message: resp,
}
app.writeJSON(r, http.StatusAccepted, returnPayload)
}
```

View File

@@ -17,6 +17,7 @@
"text": "Go Advanced", "text": "Go Advanced",
"items": [ "items": [
{ "text": "Testing", "link": "/go/advanced/testing" }, { "text": "Testing", "link": "/go/advanced/testing" },
{"text": "HTTP", "link": "/go/advanced/http"},
{"text": "RPC", "link": "/go/advanced/rpc"}, {"text": "RPC", "link": "/go/advanced/rpc"},
{"text": "gRPC", "link": "/go/advanced/grpc"} {"text": "gRPC", "link": "/go/advanced/grpc"}
] ]