Consuming Configuration Updates
protoconf provides a gRPC service that allows your application to subscribe to configuration updates. This service is provided by the protoconf agent, which can run in development mode and listen on 0.0.0.0:4300
.
This guide will walk you through how to subscribe to configuration updates using the protoconf agent in various languages: Go, Python, Node.js, Rust, and Java.
protoconf Agent
To start the protoconf agent in development mode, use the following command:
protoconf agent -dev .
The protoconf agent implements the following gRPC service:
syntax = "proto3";
package v1;
option java_package = "com.protoconf.agent.api.v1";
import "google/protobuf/any.proto";
message ConfigSubscriptionRequest {
string path = 1;
}
message ConfigUpdate {
google.protobuf.Any value = 1;
}
service ProtoconfService{
rpc SubscribeForConfig(ConfigSubscriptionRequest) returns (stream ConfigUpdate);
}
You can use it as a dependency from buf: buf.build/protoconf/protoconf
.
Code Generation
Before consuming the configuration updates, you need to generate code from the proto file. A simple solution for this is using buf
. Here is the content for buf.yaml
and buf.gen.yaml
:
buf.yaml:
version: v1
name: buf.build/myusername/myrepository
deps:
- buf.build/protoconf/protoconf
buf.gen.yaml:
version: v1
plugins:
- name: go
out: gen/go
- name: java
out: gen/java
- name: node
out: gen/js
- name: python
out: gen/python
With the above configurations, run buf generate
to generate the code for your protobuf files. Now you can consume the configuration updates in your preferred language.
- Go
- Python
- Node.js
- Rust
- Java
In Go, use the grpc package to create a client and subscribe to configuration updates:
package main
import (
"context"
"log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/golang/protobuf/ptypes"
pb "gen/go/protoconf/v1"
mypb "gen/go/myproject/v1"
)
func main() {
conn, err := grpc.Dial("localhost:4300", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
client := pb.NewProtoconfServiceClient(conn)
stream, err := client.SubscribeForConfig(context.Background(), &pb.ConfigSubscriptionRequest{
Path: "myproject/server_config",
})
if err != nil {
log.Fatalf("Failed to subscribe for config: %v", err)
}
for {
configUpdate, err := stream.Recv()
if err != nil {
log.Fatalf("Error receiving config update: %v", err)
}
var config mypb.ServerConfiguration
if err := ptypes.UnmarshalAny(configUpdate.Value, &config); err != nil {
log.Fatalf("Failed to unmarshal config update: %v", err)
}
log.Printf("Received config update: %+v", config)
}
}
In Python, use the grpc package to create a client and subscribe to configuration updates:
from google.protobuf import any_pb2
from grpc import insecure_channel
import protoconf.v1.protoconf_pb2 as pb
import myproject.v1.server_config_pb2 as mypb
with insecure_channel('localhost:4300') as channel:
client = pb.ProtoconfServiceStub(channel)
request = pb.ConfigSubscriptionRequest(path="myproject/server_config")
for config_update in client.SubscribeForConfig(request):
config = mypb.ServerConfiguration()
if config_update.value.Is(config.DESCRIPTOR):
config_update.value.Unpack(config)
print(f"Received config update: {config}")
In Node.js, use the grpc-js package to create a client and subscribe to configuration updates:
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
const mypb = require("./gen/js/myproject/v1/server_config_pb.js");
const PROTO_PATH = __dirname + "/gen/js/protoconf/v1/protoconf.proto";
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
const client = new protoDescriptor.v1.ProtoconfService(
"localhost:4300",
grpc.credentials.createInsecure()
);
const call = client.SubscribeForConfig({ path: "myproject/server_config" });
call.on("data", (configUpdate) => {
const config = mypb.ServerConfiguration.deserializeBinary(
configUpdate.value.value
);
console.log(`Received config update: ${JSON.stringify(config.toObject())}`);
});
call.on("error", (error) => {
console.error(`Error receiving config update: ${error}`);
});
call.on("end", () => {
console.log("End of config updates");
});
In Rust, use the tonic crate to create a client and subscribe to configuration updates:
use tonic::transport::Channel;
use futures::StreamExt;
use prost::Message;
use protoconf::v1::protoconf_service_client::ProtoconfServiceClient;
use protoconf::v1::ConfigSubscriptionRequest;
use myproject::v1::ServerConfiguration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let channel = Channel::from_static("http://localhost:4300").connect().await?;
let mut client = ProtoconfServiceClient::new(channel);
let request = tonic::Request::new(ConfigSubscriptionRequest {
path: "myproject/server_config".to_string(),
});
let mut stream = client.subscribe_for_config(request).await?.into_inner();
while let Some(config_update) = stream.message().await? {
let config = ServerConfiguration::decode(config_update.value.value.as_slice())?;
println!("Received config update: {:?}", config);
}
Ok(())
}
In Java, use the grpc package to create a client and subscribe to configuration updates:
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
import com.protoconf.agent.api.v1.ProtoconfServiceGrpc;
import com.protoconf.agent.api.v1.ConfigUpdate;
import com.protoconf.agent.api.v1.ConfigSubscriptionRequest;
import myproject.v1.ServerConfiguration;
public class Main {
public static void main(String[] args) throws InterruptedException {
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 4300)
.usePlaintext()
.build();
ProtoconfServiceGrpc.ProtoconfServiceBlockingStub stub = ProtoconfServiceGrpc.newBlockingStub(channel);
stub.subscribeForConfig(ConfigSubscriptionRequest.newBuilder().setPath("myproject/server_config").build())
.forEachRemaining(configUpdate -> {
try {
ServerConfiguration config = configUpdate.getValue().unpack(ServerConfiguration.class);
System.out.println("Received config update: " + config);
} catch (InvalidProtocolBufferException e) {
System.err.println("Failed to unpack config update: " + e);
}
});
channel.shutdown();
}
}
In this example, the SubscribeForConfig
RPC is used to subscribe for updates to the myproject/server_config
configuration. The received ConfigUpdate
messages are unpacked into ServerConfiguration
objects which can then be used by your application.
These examples provide a starting point for integrating protoconf into your applications. As you adapt these examples to your specific needs, you may find additional resources on the gRPC, protobuf, and protoconf libraries helpful.