{"id":25044951,"url":"https://github.com/d0x7/go-protonats","last_synced_at":"2025-04-14T02:00:31.899Z","repository":{"id":255441002,"uuid":"850912399","full_name":"d0x7/go-protonats","owner":"d0x7","description":"Go Protoc plugin for using Protobuf with NATS.io","archived":false,"fork":false,"pushed_at":"2025-02-27T17:52:07.000Z","size":196,"stargazers_count":6,"open_issues_count":3,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-27T15:55:58.364Z","etag":null,"topics":["cli","go","golang","nats","nats-io","natsio"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/d0x7.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-02T04:28:00.000Z","updated_at":"2025-02-28T13:37:10.000Z","dependencies_parsed_at":"2024-09-03T06:42:30.396Z","dependency_job_id":"d098d4c6-6508-44f9-8667-2abc6713a813","html_url":"https://github.com/d0x7/go-protonats","commit_stats":null,"previous_names":["d0x7/protoc-gen-go-nats"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d0x7%2Fgo-protonats","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d0x7%2Fgo-protonats/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d0x7%2Fgo-protonats/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d0x7%2Fgo-protonats/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/d0x7","download_url":"https://codeload.github.com/d0x7/go-protonats/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248809032,"owners_count":21164895,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","go","golang","nats","nats-io","natsio"],"created_at":"2025-02-06T05:19:59.124Z","updated_at":"2025-04-14T02:00:31.870Z","avatar_url":"https://github.com/d0x7.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# go-protonats [![PkgGoDev](https://pkg.go.dev/badge/xiam.li/go-protonats)](https://pkg.go.dev/xiam.li/go-protonats)\n\nThis is a protoc plugin that generates Go server and client code for NATS microservices.\n\nGo-ProtoNATS uses the shared [protonats](https://github.com/d0x7/protonats) package and is protocol compatible with the [Java implementation](https://github.com/d0x7/java-protonats).\n\nPrior experience with Protobuf is greatly recommended, especially to understand how the package and imports work.\n\n\u003e [!IMPORTANT]\n\u003e Please note that the structure has greatly been changed since the last release.\n\u003e This repository now only contains the protoc-gen-go-nats compiler plugin, but not the runtime code.\n\u003e For that, please refer to [protonats](https://github.com/d0x7/protonats) and import that module instead.\n\n## Installation\n\nYou already need to have the protoc compiler along the Go protobuf plugin installed on your system.\nAfter that, you can go ahead and install this plugin using the following command:\n\n```shell\ngo install xiam.li/go-protonats/cmd/protoc-gen-go-nats@latest\n```\n\nTo check if the installation was successful, you can run:\n\n```shell\nprotoc-gen-go-nats -v\n```\n\n## Usage\n\nUpon installation, you should create a protobuf file that contains a service, similar to how gRPC servers work.\nAn example protobuf file might look like this:\n\n```protobuf\nsyntax = \"proto3\";\npackage your.package;\noption go_package = \"github.com/user/repo/pb;pb\";\n\nservice HelloWorldService {\n    rpc HelloWorld(HelloWorldRequest) returns (HelloWorldResponse);\n}\n\nmessage HelloWorldRequest {\n    string name = 1;\n}\n\nmessage HelloWorldResponse {\n    string message = 1;\n}\n```\n\nTo generate the Go code for this service, run the following command.\nThis command expects your proto file in a directory called `pb` in your project.\n\n```shell\nprotoc -I pb --go_out=pb --go_opt=paths=source_relative --go-nats_out=pb --go-nats_opt=paths=source_relative pb/hello_world.proto\n```\n\nThis obviously requires the protoc compiler to be installed on your system\nand also having the go protobuf plugin installed, so that besides the code\nregarding NATS can be generated, the messages and everything else can also be generated.\n\nNow you can use the generated code to create a NATS server and client:\n\n```go\npackage main\nimport (\n    \"fmt\"\n    \"github.com/nats-io/nats.go\"\n    \"github.com/user/repo/pb\"\n)\n\ntype serviceImpl struct {}\n\nfunc (s *serviceImpl) HelloWorld(req *pb.HelloWorldRequest) (*pb.HelloWorldResponse, error) {\n\tmsg := fmt.Sprintf(\"Hello, %s!\", req.GetName())\n\treturn \u0026pb.HelloWorldResponse{Message: msg}, nil\n}\n\nfunc main() {\n\tnc, _ := nats.Connect(nats.DefaultURL)\n\tpb.NewHelloWorldServiceNATSServer(nc, \u0026serviceImpl{})\n}\n```\n\nClient:\n\n```go\npackage main\nimport (\n\t\"github.com/nats-io/nats.go\"\n    \"github.com/user/repo/pb\"\n)\n\nfunc main() {\n\tnc, _ := nats.Connect(nats.DefaultURL)\n\tcli := pb.NewHelloWorldServiceNATSClient(nc)\n\n\t// List all instances currently connected\n\tinstances, err := cli.ListInstances()\n\n\t// Get stats from all instances\n\tstats, err := cli.Stats()\n\n\t// Or, from a specific instance:\n\tstats, err := cli.Stats(pb.WithInstanceID(instances[0].ID))\n\n\t// Obviously, you can also call your defined service methods:\n\tresponse, err := cli.HelloWorld(\u0026pb.HelloWorldRequest{Name: \"John Doe\"})\n\n\t// And again for a specific instance, instead of the default load balanced distribution:\n\tresponse, err := cli.HelloWorld(\u0026pb.HelloWorldRequest{Name: \"John Doe\"}, pb.WithInstanceID(instances[0].ID))\n}\n```\n\n### Special handling for empty requests/responses\n\nWhen specifying an RPC method that uses either or both the [`google/protobuf/empty.proto`](https://protobuf.dev/reference/protobuf/google.protobuf/#empty) type, that method will not generate a parameter to be passed as request/response, depending on how the RPC is defined.\n\nFor example, the following protobuf file will generate the following method signature:\n\n```protobuf\nsyntax = \"proto3\";\npackage your.package;\nimport \"google/protobuf/empty.proto\";\noption go_package = \"github.com/user/repo/pb;pb\";\n\nservice HelloWorldService {\n    rpc NoRequest(google.protobuf.Empty) returns (HelloWorldResponse);\n    rpc NoResponse(HelloWorldRequest) returns (google.protobuf.Empty);\n    rpc NoRequestNoResponse(google.protobuf.Empty) returns (google.protobuf.Empty);\n}\n```\n\n```go\n// Client\ntype HelloWorldServiceNATSClient interface {\n    NoRequest(opts ...CallOption) (*HelloWorldResponse, error)\n    NoResponse(req *HelloWorldResponse, opts ...CallOption) (error)\n    NoRequestNoResponse(opts ...CallOption) (error)\n    // [...]\n}\n// Server\ntype HelloWorldServiceNATSServer interface {\n    NoRequest() (*HelloWorldResponse, error)\n    NoResponse(req *HelloWorldResponse) (error)\n    NoRequestNoResponse() (error)\n    // [...]\n}\n```\n\n### Broadcasting\n\nIf you want to broadcast a message to all instances of a service, you can set the `protonats.broadcast` option to true in the method definition.\nThis will generate a method in the client that will broadcast the message to all instances of the service.\n\nThe issue with that is, it requires the client to have the `protonats.proto` imported, but can be easily done, by appending this to your protoc generation command:\n\n```shell\n-I$(go list -m -f '{{ .Dir }}' xiam.li/protonats)/proto\n```\n\nThis takes the local directory of the `protonats` module and adds it as a import path for proto, so that it can find the `protonats.proto` file in there.\nYou can now use it like this:\n\n```protobuf\nsyntax = \"proto3\";\npackage your.package;\nimport \"google/protobuf/empty.proto\";\nimport \"protonats.proto\";\noption go_package = \"github.com/user/repo/pb;pb\";\n\nservice BroadcastingService {\n  rpc ABroadcastingMethod(HelloWorldRequest) returns (HelloWorldResponse) {\n    option (protonats.broadcast) = true;\n  }\n  rpc FanOut(HelloWorldRequest) returns (google.protobuf.Empty) {\n    option (protonats.broadcast) = true;\n  }\n  rpc FanIn(google.protobuf.Empty) returns (HelloWorldResponse) {\n    option (protonats.broadcast) = true;\n  }\n  rpc VeryEmptyMethod(google.protobuf.Empty) returns (google.protobuf.Empty) {\n    option (protonats.broadcast) = true;\n  }\n}\n```\n\nWhen using broadcast, it will also honour your usage of `google.protobuf.Empty`, so that these methods won't generate a parameter to be passed as request/response, depending on how the RPC is defined.\nAlthough there are opts available for these methods, the only one used is the timeout and passing an instance id does nothing.\n\nYou can use it like this on the server side:\n\n```go\ntype impl struct {\n\tsrv micro.Service\n\terr bool\n}\n\nfunc (i *impl) ABroadcastingMethod(req *pb.HelloWorldRequest) (*pb.HelloWorldResponse, error) {\n\treturn \u0026pb.Test{Message: \"Hello \" + req.GetName() + \", welcome to a broadcasting response from \" + i.srv.Info().ID}, nil\n}\n\nfunc (i *impl) FanOut(req *pb.HelloWorldRequest) error {\n\t// Do something with the request and then return to confirm the request was received\n\treturn nil\n}\n\nfunc (i *impl) FanIn() (*pb.HelloWorldResponse, error) {\n\t// Do something and then return a response\n\treturn \u0026pb.HelloWorldResponse{Message: \"Hello FanIn response from \" + i.srv.Info().ID}, nil\n}\n\nfunc (i *impl) VeryEmptyMethod() error {\n\t// Do something\n\treturn nil\n}\n\nfunc main() {\n\tnc, err := nats.Connect(nats.DefaultURL)\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to connect to NATS: %v\", err)\n\t}\n\tsrvImpl := \u0026impl{}\n\tsrv := broadcast.NewBroadcastingServiceNATSServer(nc, srvImpl)\n\tsrvImpl.srv = srv\n\n\tlog.Println(\"Server started\")\n\tfor {\n\t\tif !srv.Stopped() {\n\t\t\ttime.Sleep(time.Second)\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t}\n}\n```\n\nAnd on the client:\n\n```go\nnc, err := nats.Connect(nats.DefaultURL)\nif err != nil {\n    log.Fatalf(\"Failed to connect to NATS: %v\", err)\n}\ncli := broadcast.NewBroadcastingServiceNATSClient(nc)\nresponses, serviceErrs, err := cli.ABroadcastingMethod(\u0026pb.HelloWorldRequest{Name: \"John Doe\"})\nif err != nil {\n    log.Fatalf(\"Failed to call ABroadcastingMethod: %v\", err)\n}\nif serviceErrs != nil {\n    log.Println(\"Some services returned an error:\")\n    for i, serviceErr := range serviceErrs {\n        log.Printf(\"Service Error %d/%d: %v\\n\", i+1, len(serviceErrs), serviceErr)\n    }\n}\nlog.Printf(\"Broadcast Responses\")\nfor i, response := range responses {\n    log.Printf(\"Response %d/%d: %v\\n\", i+1, len(responses), response)\n}\n// Or similar-ish for methods using the empty type\nserviceErrs, errs = cli.VeryEmptyMethod()\nresponses, serviceErrs, errs = cli.FanIn()\nserviceErrs, errs = cli.FanOut(\u0026pb.HelloWorldRequest{Name: \"John Doe\"})\n```\n\n### Instance identifier\n\nIf you need the instance id of the current service on the server side, you could either call `.Info().ID` on the returned `micro.Service`, but your service implementation can also just implement the service-specfic generated `[ServiceName]ServiceId` interface.\nFor example, if your service is defined as `HelloWorldService` in the protobuf file, the following interface would be generated:\n\n```go\ntype HelloWorldServiceId interface {\n\tSetHelloWorldServiceId(string)\n}\n```\n\nTherefore, you can just implement this interface on your service implementation optionally and upon initialization, the method would be called once, so you can store it in a field inside the service implementation, or similar:\n\n```go\ntype helloWorldImpl struct {\n\tid string\n}\n\nfunc (i *helloWorldImpl) SetHelloWorldServiceId(s string) {\n\ti.id = s\n}\n```\n\n### Consensus Integration\n\nIf you use a consensus algorithm like Raft, you can use the `protonats.consensus_Target` option to mark methods to be used only by the leader or follower.\nThese methods will be generated onto a separate interface, which is composited onto the main service interface.\nBy default, the normal `NewYourServiceNATSServer` method will still register all methods, regardless of it the target is leader or follower, but you can use the specialized `NewYourServiceNATSLeaderServer` or `NewYourServiceNATSFollowerServer` methods to only register a server for either methods - or you can use the normal `[...]NATSServer` method and pass either a `protonats.WithoutLeaderFns()` or `protonats.WithoutFollowerFns()` to disable the registration of these, but still allow for the normal methods to be registered.\n\nMethods marked with a consensus can still use the broadcasting flag, which will for example make a call to that method broadcast to all followers, instead of only one follower. \n\nTo mark methods with a consensus target, use the `protonats.consensus_target` option in the method definition:\n\n```protobuf\nservice ConsensusService {\n  // The CurrentSnapshot method will only be called on the leader\n  rpc CurrentSnapshot(google.protobuf.Empty) returns (Snapshot) {\n    option (protonats.consensus_target) = LEADER;\n  }\n  // And the ApplyChange will be sent to all followers, because it's also a broadcast\n  rpc ApplyChange(Snapshot) returns (google.protobuf.Empty) {\n    option (protonats.consensus_target) = FOLLOWER;\n    option (protonats.broadcast) = true;\n  }\n}\n```\n\n```go\n// Full implementation, normal methods, leader methods and follower methods\n_ = consensus.NewConsensusServiceNATSServer(conn, impl)\n\n// Normal implementation with follower methods, but leader methods unimplemented\n_ = consensus.NewConsensusServiceNATSServer(conn, impl, protonats.WithoutLeaderFns())\n\n// Normal implementation with leader methods, but follower methods unimplemented\n_ = consensus.NewConsensusServiceNATSServer(conn, impl, protonats.WithoutFollowerFns())\n\n// Follower-only implementation - only those marked as follower methods will be registered \n// Notice the use of the [...]NATSFollowerServer interface instead the broader [...]NATSServer interface\n_ = consensus.NewConsensusServiceNATSFollowerServer(conn, impl)\n\n// Leader-only implementation - only those marked as leader methods will be registered\n// Notice the use of the [...]NATSLeaderServer interface instead the broader [...]NATSServer interface\n_ = consensus.NewConsensusServiceNATSLeaderServer(conn, impl)\n```\n\n### Custom Errors\n\nYou can also send custom errors to the client, but for that you need to add this package to your project:\n\n```shell\ngo get xiam.li/protonats\n```\n\nThen, you can use the `protonats.ServerError` type to send custom errors to the client:\n\n```go\n// In any method of your service implementation, do the following\n// Or, if you want to return a custom error:\nreturn nil, protonats.NewServerErr(\"400\", \"Unknown Name\")\n\n// Or, you can also wrap an existing error for more detailed information:\nreturn nil, protonats.WrapServerErr(err, \"500\", \"Failed to query database\")\n\n// You can also send custom headers using this method:\nserverErr := protonats.NewServerErr(\"400\", \"Unknown Name\")\nserverErr.AddHeader(\"err-details\", \"Username is not in the database\")\nreturn nil, serverErr\n```\n\nOn the client side they are received as `ServiceError` (Important: ServiceError, not ServerError).\n\n```go\n_, err := cli.HelloWorld(\u0026pb.HelloWorldRequest{Name: \"John Doe\"})\nif err != nil {\n    serviceErr, isSrvErr := protonats.AsServiceError(err)\n    if isSrvErr {\n        fmt.Printf(\"Got a service error with code %s: %s\\n\", serviceErr.Code, serviceErr.Description)\n    } else {\n        fmt.Println(\"Other different error, usually networking related or an issue with unmarshalling the response\")\n    }\n}\n```\n\nYou can also use `protonats.IsServiceError(err)` to check if an error is a ServiceError.\n\nThere's also an `Details` field in the ServiceError struct, but that's only used when\nthe server, instead of returning a proper ServerError, only returns a generic error.\nIn that case, the result from that error's `Error()` will end up in the `Details` field.\n\n### Streaming\n\nStreaming is not yet supported, but is planned for the future.\nIt'll probably be implemented along with better timeout handling,\nthat will come with keepalive messages and therefore also allow streaming.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd0x7%2Fgo-protonats","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fd0x7%2Fgo-protonats","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd0x7%2Fgo-protonats/lists"}