IIUC you are asking how the gRPC server implements the functionality described by the protobuf?
I think you're referring to this example
The protobuf compiler generates client and server stubs that you must implement. You can implement these in any language implementation. When you implement the server, you are entirely responsible for ensuring that e.g. ListBooks()
(for a shelf) returns any books added to the shelf by CreateBook()
.
The implementation is independent of gRPC.
rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {}
rpc CreateBook(CreateBookRequest) returns (Book) {}
gRPC conceptually simply ensures that your client(s) think they're calling a local method: CreateBook()
when, in fact, they're calling a local stub that transfer the request over a network to the remote server that receives the CreateBook()
request and does something about it.
So, let's focus on the server, it will likely use some form of persistence to record shelves and books. In practice this will be some of database:
type Server struct {
db db
}
func (s *Server) CreateBook(r *pb.CreateBookRequest) {
shelf := s.db.Get(r.get_shelf())
shelf.Add(r.get_book())
}
func (s *server) ListBooksRequest(r *pb.ListBooksRequest) {
shelf := s.db.Get(r.get_shelf())
for _, book := range shelf.Get() {
fmt.Println(book)
}
}
NOTE In the above, the server implementation of the gRPC service includes a database connection that, the gRPC methods use to interact with the database. This could represent some other micro-service too...turtles all the way!
So, to answer your question, somewhere in the bowels of your micro-services, there's some form of shared state (e.g. database or similar) where e.g. books are persisted (in shelves).
Whether the clients and|or servers are containerized, while probably good practice, is irrelevant to the question of how communication occurs.