AlexStocks commented on code in PR #3340:
URL: https://github.com/apache/dubbo-go/pull/3340#discussion_r3610132741
##########
.agents/.claude-plugin/marketplace.json:
##########
@@ -0,0 +1,20 @@
+{
Review Comment:
[P1] README 中的 Claude 安装入口无法从仓库根目录发现这个 marketplace
README 要求执行 `/plugin marketplace add apache/dubbo-go`,但 Claude 官方协议要求 GitHub
marketplace 的 `.claude-plugin/marketplace.json` 位于仓库根目录;当前文件放在
`.agents/.claude-plugin/marketplace.json`。本地 `claude plugin validate .agents`
虽能通过,但对 README 实际指向的仓库根目录执行 `claude plugin validate .` 会稳定报 `No manifest found
... Expected .claude-plugin/marketplace.json or .claude-plugin/plugin.json`。此外
marketplace 的 source 指向整个仓库,而实际 skills/plugin root 位于
`.agents`。因此用户按文档操作时无法安装这些 skill。建议将 marketplace manifest 放到仓库根
`.claude-plugin/marketplace.json`,并用相对 `./.agents` 或 `git-subdir` 明确插件根;插件
manifest 也应按协议放在插件根的 `.claude-plugin/plugin.json`,然后用 README 中的完整命令做一次端到端安装验证。
##########
.agents/skills/scaffolding/SKILL.md:
##########
@@ -0,0 +1,279 @@
+---
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+name: dubbo-go-scaffolding
+description: Generates dubbo-go v3 provider or consumer skeletons in the
code-API style (dubbo.NewInstance / server.NewServer / client.NewClient). Use
when the user asks to create, bootstrap, or scaffold a new dubbo-go service,
provider, or consumer, including direct mode, registry-backed, OpenAPI,
HTTP-mounted, or HTTP/3 variants.
+---
+
+# Scaffolding dubbo-go Services
+
+dubbo-go v3 has two entry styles. Use the **code API** by default — it is the
style the samples repo has converged on. Fall back to YAML-driven
`dubbo.Load()` only if the user is migrating an existing YAML project.
+
+Before generating code, confirm three things:
+1. **Protocol** — Triple (default, HTTP/2, gRPC-compatible) / Dubbo (Java
interop with Hessian2) / JSONRPC / REST. Pick Triple unless the user has a
reason.
+2. **Registry** — Nacos / ZooKeeper / etcd / Polaris / direct (no registry).
Pick direct for local demos, Nacos for production Apache stacks.
+3. **Service definition** — does the user already have a `.proto` file?
+
+## Project Layout
+
+Mirror the [samples repo](https://github.com/apache/dubbo-go-samples) layout
so the user can cross-reference:
+
+```
+myservice/
+├── go.mod
+├── proto/
+│ ├── greet.proto
+│ ├── greet.pb.go # protoc-gen-go
+│ └── greet.triple.go # protoc-gen-go-triple
+├── go-server/cmd/main.go
+└── go-client/cmd/main.go
+```
+
+## Step 1: Proto Definition
+
+```protobuf
+syntax = "proto3";
+package greet;
+option go_package = "github.com/yourorg/myservice/proto;greet";
+
+message GreetRequest { string name = 1; }
+message GreetResponse { string greeting = 1; }
+
+service GreetService {
+ rpc Greet(GreetRequest) returns (GreetResponse);
+}
+```
+
+Install codegen plugins (one-time):
+```bash
+go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
+go install github.com/dubbogo/protoc-gen-go-triple/v3@latest
+```
+
+Generate:
+```bash
+protoc --go_out=. --go_opt=paths=source_relative \
+ --go-triple_out=. --go-triple_opt=paths=source_relative \
+ proto/greet.proto
+```
+
+## Step 2: Provider
+
+**Direct mode** (no registry, simplest local demo):
+
+```go
+package main
+
+import "context"
+
+import (
+ _ "dubbo.apache.org/dubbo-go/v3/imports"
+ "dubbo.apache.org/dubbo-go/v3/protocol"
+ "dubbo.apache.org/dubbo-go/v3/server"
+
+ "github.com/dubbogo/gost/log/logger"
+)
+
+import greet "github.com/yourorg/myservice/proto"
+
+type GreetTripleServer struct{}
+
+func (s *GreetTripleServer) Greet(ctx context.Context, req
*greet.GreetRequest) (*greet.GreetResponse, error) {
+ return &greet.GreetResponse{Greeting: "hello " + req.Name}, nil
+}
+
+func main() {
+ srv, err := server.NewServer(
+ server.WithServerProtocol(
+ protocol.WithPort(20000),
+ protocol.WithTriple(),
+ ),
+ )
+ if err != nil {
+ logger.Fatalf("new server: %v", err)
+ }
+
+ if err := greet.RegisterGreetServiceHandler(srv, &GreetTripleServer{});
err != nil {
+ logger.Fatalf("register handler: %v", err)
+ }
+
+ if err := srv.Serve(); err != nil {
+ logger.Fatalf("serve: %v", err)
+ }
+}
+```
+
+**Registry-backed** — wrap with `dubbo.NewInstance` so registry and protocol
are applied globally:
+
+```go
+import (
+ "dubbo.apache.org/dubbo-go/v3"
+ "dubbo.apache.org/dubbo-go/v3/registry"
+)
+
+func main() {
+ ins, err := dubbo.NewInstance(
+ dubbo.WithName("myservice-provider"),
+ dubbo.WithRegistry(
+ registry.WithNacos(),
+ registry.WithAddress("127.0.0.1:8848"),
+ ),
+ dubbo.WithProtocol(
+ protocol.WithTriple(),
+ protocol.WithPort(20000),
+ ),
+ )
+ if err != nil { panic(err) }
+
+ srv, err := ins.NewServer()
+ if err != nil { panic(err) }
+ if err := greet.RegisterGreetServiceHandler(srv, &GreetTripleServer{});
err != nil { panic(err) }
+ if err := srv.Serve(); err != nil { panic(err) }
+}
+```
+
+Swap registries by changing two lines:
+- Nacos: `registry.WithNacos()` + `registry.WithAddress("127.0.0.1:8848")`
+- ZooKeeper: `registry.WithZookeeper()` +
`registry.WithAddress("127.0.0.1:2181")`
+- etcd: `registry.WithEtcdV3()` + `registry.WithAddress("127.0.0.1:2379")`
+- Polaris: `registry.WithPolaris()` + `registry.WithAddress("127.0.0.1:8091")`
+
+## Step 3: Consumer
+
+**Direct mode**:
+
+```go
+package main
+
+import (
+ "context"
+ "time"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/client"
+ _ "dubbo.apache.org/dubbo-go/v3/imports"
+
+ "github.com/dubbogo/gost/log/logger"
+)
+
+import greet "github.com/yourorg/myservice/proto"
+
+func main() {
+ cli, err := client.NewClient(client.WithClientURL("127.0.0.1:20000"))
+ if err != nil { logger.Fatalf("new client: %v", err) }
+
+ svc, err := greet.NewGreetService(cli)
+ if err != nil { logger.Fatalf("new service: %v", err) }
+
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+ defer cancel()
+
+ resp, err := svc.Greet(ctx, &greet.GreetRequest{Name: "world"})
+ if err != nil { logger.Fatalf("call: %v", err) }
+ logger.Infof("resp: %s", resp.Greeting)
+}
+```
+
+**With registry** — drop `WithClientURL`, attach the same registry to an
`Instance`:
+
+```go
+ins, _ := dubbo.NewInstance(
+ dubbo.WithName("myservice-consumer"),
+ dubbo.WithRegistry(registry.WithNacos(),
registry.WithAddress("127.0.0.1:8848")),
+)
+cli, _ := ins.NewClient()
+svc, _ := greet.NewGreetService(cli)
+```
+
+If application-level discovery cannot resolve which application owns the
interface, hint with:
+
+```go
+svc, _ := greet.NewGreetService(cli,
client.WithProvidedBy("myservice-provider"))
+```
+
+## Triple Extras
+
+**OpenAPI docs** — Triple can publish a runtime OpenAPI spec:
+
+```go
+server.WithServerProtocol(
+ protocol.WithPort(20000),
+ protocol.WithTriple(
+ triple.OpenAPIEnable(true),
+ triple.OpenAPIInfoTitle("Greet API"),
+ ),
+)
+```
+
+See
[rpc/triple/openapi](https://github.com/apache/dubbo-go-samples/tree/main/rpc/triple/openapi).
+
+**Mount an HTTP handler** on the same Triple port (Triple-only):
+
+```go
+srv.AttachHTTPHandler("/healthz", http.HandlerFunc(healthz))
Review Comment:
[P1] 生成的 HTTP handler 示例无法编译
当前 `server.(*Server).AttachHTTPHandler` 的签名只有一个参数:`AttachHTTPHandler(handler
http.Handler) error`,不接受 path。把本示例放进独立消费者模块编译会报 `too many arguments ... have
(string, http.HandlerFunc), want (http.Handler)`。同样的错误还出现在 `guide/SKILL.md` 和
`migrate/SKILL.md`。这与 README 宣称“生成可直接编译的骨架”冲突。建议先用 `http.NewServeMux()` 在 mux
上注册 `/healthz`、`/api` 等路径,再把整个 mux 作为唯一参数传给 `AttachHTTPHandler`,并给所有 Go
示例增加自动编译测试。
##########
.agents/skills/extensions/SKILL.md:
##########
@@ -0,0 +1,218 @@
+---
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+name: dubbo-go-extensions
+description: Guides writing custom dubbo-go v3 extensions — Filter,
LoadBalance, Registry, Protocol, Router, Logger, ConfigCenter — through the SPI
pattern. Use when the user asks how to write a filter, custom interceptor,
custom load balancer, plug in a new registry, or hook into dubbo-go's extension
points.
+---
+
+# Writing dubbo-go Extensions
+
+dubbo-go exposes nearly every runtime behavior through a uniform SPI pattern:
+
+1. Implement an interface
+2. Call `extension.SetXxx(name, factory)` in an `init()`
+3. Blank-import the package so `init()` runs
+4. Enable the extension by name at the call site (`server.WithFilter`,
`client.WithFilter`, etc.)
+
+The extension point you pick depends on **what you want to intercept**:
+
+| You want to... | Extension point | `extension.Set*` fn |
+|---|---|---|
+| Intercept every RPC call (auth, logging, metrics) | Filter | `SetFilter` |
+| Choose which provider instance to call | LoadBalance | `SetLoadbalance` |
+| Prune the provider list before LB (canary, A/B) | Router |
`SetRouterFactory` (rules usually pushed via config center) |
+| Plug in a new service-discovery backend | Registry | `SetRegistry` |
+| Add a new wire protocol | Protocol | `SetProtocol` |
+| Replace the logger backend | Logger | `SetLogger` |
+
+## Filter (the 90% case)
+
+Filters run on every RPC. They're the right extension point for auth, token
injection, metrics, tracing, rate limiting, and request/response logging.
+
+**Package layout**:
+```
+yourapp/filter/myfilter/myfilter.go
+```
+
+**myfilter.go**:
+```go
+package myfilter
+
+import (
+ "context"
+ "time"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/common/extension"
+ "dubbo.apache.org/dubbo-go/v3/filter"
+ "dubbo.apache.org/dubbo-go/v3/protocol/base"
+ "dubbo.apache.org/dubbo-go/v3/protocol/result"
+
+ "github.com/dubbogo/gost/log/logger"
+)
+
+func init() {
+ extension.SetFilter("timing", func() filter.Filter { return
&timingFilter{} })
+}
+
+type timingFilter struct{}
+
+func (f *timingFilter) Invoke(ctx context.Context, invoker base.Invoker, inv
base.Invocation) result.Result {
+ start := time.Now()
+ res := invoker.Invoke(ctx, inv)
+ logger.Infof("call %s took %s", inv.MethodName(), time.Since(start))
+ return res
+}
+
+func (f *timingFilter) OnResponse(ctx context.Context, res result.Result,
invoker base.Invoker, inv base.Invocation) result.Result {
+ return res
+}
+```
+
+**Activate in main.go**:
+```go
+import _ "github.com/yourorg/yourapp/filter/myfilter" // triggers init()
+
+// Server side, per service
+pb.RegisterGreetServiceHandler(srv, impl, server.WithFilter("timing"))
+
+// Client side, per reference
+svc, _ := pb.NewGreetService(cli, client.WithFilter("timing"))
+
+// Or globally on the server/client:
+// server.WithServerFilter("timing")
+// client.WithClientFilter("timing")
+```
+
+**Chain multiple filters** with comma separation:
`server.WithFilter("timing,auth")`. Filters run in order on the way in, reverse
order on `OnResponse`.
+
+**Modify attachments** (e.g. add a request ID) in `Invoke` via
`inv.SetAttachment(key, val)`; read on the other side with
`inv.Attachment(key)`.
+
+**Short-circuit a call** by returning a result without invoking the next link:
+```go
+func (f *authFilter) Invoke(ctx context.Context, invoker base.Invoker, inv
base.Invocation) result.Result {
+ if !isAuthorized(inv) {
+ return &result.RPCResult{Err: errors.New("unauthorized")}
+ }
+ return invoker.Invoke(ctx, inv)
+}
+```
+
+See
[filter/custom](https://github.com/apache/dubbo-go-samples/tree/main/filter/custom)
for the canonical example.
+
+## LoadBalance
+
+Choose one provider out of a list. Built-ins: Random (default), RoundRobin,
LeastActive, ConsistentHash, P2C.
+
+Write a custom one only when the built-ins do not cover your selection rule
(e.g. pick by request header, pick by tenant ID).
+
+```go
+package affinityLB
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/cluster/loadbalance"
+ "dubbo.apache.org/dubbo-go/v3/common/extension"
+ "dubbo.apache.org/dubbo-go/v3/protocol/base"
+)
+
+func init() {
+ extension.SetLoadbalance("tenant-affinity", func() loadbalance.LoadBalance
{
+ return &tenantAffinityLB{}
+ })
+}
+
+type tenantAffinityLB struct{}
+
+func (lb *tenantAffinityLB) Select(invokers []base.Invoker, inv
base.Invocation) base.Invoker {
+ tenant := inv.Attachment("tenant-id")
Review Comment:
[P1] 示例调用了不存在的 Invocation API
`protocol/base.Invocation` 只提供
`GetAttachment`、`GetAttachmentInterface`、`GetAttachmentWithDefaultValue` 等方法,没有
`Attachment`。独立消费者探针按这里的代码编译会报 `inv.Attachment
undefined`;同一错误还出现在本文件前面的附件说明。建议根据需要改为 `GetAttachment` 或
`GetAttachmentInterface`,并把 skill 中的代码块纳入编译门禁,否则 Agent 会稳定生成无法构建的扩展实现。
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]