diff --git a/.agents/.claude-plugin/marketplace.json b/.agents/.claude-plugin/marketplace.json new file mode 100644 index 0000000000..9be1d8ce3c --- /dev/null +++ b/.agents/.claude-plugin/marketplace.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://anthropic.com/claude-code/marketplace.schema.json", + "name": "dubbo-go-agent-skills", + "description": "Agent skills bundled with Apache dubbo-go for v3 framework users", + "owner": { + "name": "Apache Dubbo" + }, + "plugins": [ + { + "name": "dubbo-go", + "description": "AI skills for Apache dubbo-go v3 users — scaffolding, extensions, Java interop, debugging, guidance, migration, plus contributor support.", + "category": "development", + "source": { + "source": "url", + "url": "https://github.com/apache/dubbo-go.git" + }, + "homepage": "https://github.com/apache/dubbo-go/tree/main/.agents" + } + ] +} diff --git a/.agents/.codex/INSTALL.md b/.agents/.codex/INSTALL.md new file mode 100644 index 0000000000..a4a2cf1b7b --- /dev/null +++ b/.agents/.codex/INSTALL.md @@ -0,0 +1,68 @@ + + +# Installing dubbo-go Agent Skills for Codex + +## Install + +Install these skills globally when the user asks to install dubbo-go skills for Codex. + +```bash +if [ -d ~/.codex/dubbo-go/.git ]; then + git -C ~/.codex/dubbo-go pull --ff-only +else + git clone https://github.com/apache/dubbo-go.git ~/.codex/dubbo-go +fi + +mkdir -p ~/.agents/skills +rm -rf ~/.agents/skills/dubbo-go +ln -s ~/.codex/dubbo-go/.agents/skills ~/.agents/skills/dubbo-go +``` + +Restart Codex after installation. + +### Windows + +```powershell +$repo = "$env:USERPROFILE\.codex\dubbo-go" +if (Test-Path "$repo\.git") { + git -C $repo pull --ff-only +} else { + git clone https://github.com/apache/dubbo-go.git $repo +} + +New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.agents\skills" +Remove-Item -Recurse -Force "$env:USERPROFILE\.agents\skills\dubbo-go" -ErrorAction SilentlyContinue +cmd /c mklink /J "$env:USERPROFILE\.agents\skills\dubbo-go" "$env:USERPROFILE\.codex\dubbo-go\.agents\skills" +``` + +Restart Codex after installation. + +## Update + +```bash +cd ~/.codex/dubbo-go +git pull --ff-only +rm -rf ~/.agents/skills/dubbo-go +ln -s ~/.codex/dubbo-go/.agents/skills ~/.agents/skills/dubbo-go +``` + +Restart Codex after updating. + +## Usage + +The installed skills cover scaffolding, custom SPI extensions, Java interoperability, runtime debugging, conceptual guidance, and migration for dubbo-go v3 — plus a contributor-focused skill for modifying the `apache/dubbo-go` repository itself. diff --git a/.agents/.opencode/plugins/dubbo-go-agent-skills.js b/.agents/.opencode/plugins/dubbo-go-agent-skills.js new file mode 100644 index 0000000000..015d4f8c7c --- /dev/null +++ b/.agents/.opencode/plugins/dubbo-go-agent-skills.js @@ -0,0 +1,46 @@ +/* + * 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. + */ + +import { readFileSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const pluginRoot = join(__dirname, '..', '..'); + +const skillNames = ['scaffolding', 'extensions', 'java-interop', 'debug', 'guide', 'migrate', 'development']; + +export const skills = skillNames.map(name => { + const skillPath = join(pluginRoot, 'skills', name, 'SKILL.md'); + let content; + try { + content = readFileSync(skillPath, 'utf-8'); + } catch (err) { + throw new Error(`dubbo-go-agent-skills: failed to read ${skillPath}: ${err.message}`); + } + // Parse frontmatter (tolerates both LF and CRLF line endings) + const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/); + const frontmatter = match ? match[1] : ''; + const body = match ? match[2] : content; + const nameMatch = frontmatter.match(/^name:\s*(.+)$/m); + const descMatch = frontmatter.match(/^description:\s*(.+)$/m); + return { + name: nameMatch ? nameMatch[1].trim() : `dubbo-go:${name}`, + description: descMatch ? descMatch[1].trim() : '', + content: body, + }; +}); diff --git a/.agents/README.md b/.agents/README.md new file mode 100644 index 0000000000..834d3b6eae --- /dev/null +++ b/.agents/README.md @@ -0,0 +1,130 @@ + + +# dubbo-go Agent Skills + +[English](README.md) | [中文](README_CN.md) + +AI agent skills bundled with [Apache dubbo-go](https://github.com/apache/dubbo-go) — make your coding assistant fluent in dubbo-go v3. + +## What This Gives You + +When you're using dubbo-go, your AI assistant now actually knows what it's doing. + +Ask it to scaffold a new provider or consumer and it picks the current code-API style (`dubbo.NewInstance` + `server.WithServerProtocol`), wires up the registry you actually use, and produces something that compiles on the first try. Need a custom filter or load balancer? It knows the SPI pattern — `extension.SetXxx` registration, blank imports, per-service or per-reference activation. Calling a Java Dubbo service? It picks Triple+Protobuf or Dubbo+Hessian2 based on whether you have a `.proto` or a Java interface, and gets the POJO class names right. + +Ask why your service won't connect, and it triages by structured checklist — registry, protocol, serialization, filter chain — instead of guessing. Migrating from gRPC-Go or Spring Cloud? It maps the concepts side by side. + +Skills activate automatically when the conversation matches; you don't invoke them by name. + +> All skills target dubbo-go **v3**. v1 and v2 are deprecated. + +## Install + +Install paths vary by AI tool. Claude Code and Cursor read a marketplace; Codex and OpenCode are manual. + +### Claude Code / Cursor + +```bash +/plugin marketplace add apache/dubbo-go +/plugin install dubbo-go@dubbo-go-agent-skills +``` + +### Codex + +Tell Codex: + +``` +Fetch and follow instructions from https://raw.githubusercontent.com/apache/dubbo-go/main/.agents/.codex/INSTALL.md +``` + +Full instructions: [.codex/INSTALL.md](.codex/INSTALL.md) + +### OpenCode + +Add to `opencode.json`: + +```json +{ + "plugin": ["dubbo-go-agent-skills@git+https://github.com/apache/dubbo-go.git#path:.agents"] +} +``` + +### Verify + +Open a new session and try one of these: + +- "Scaffold a dubbo-go provider with Nacos" +- "Why does my consumer log 'no provider available'?" +- "How do I call a Java Dubbo service from Go?" + +The assistant should auto-trigger the matching skill. + +## Reference Docs + +Use the official Dubbo Golang SDK documentation as the primary reference for user-facing behavior and tutorials: + +- [Dubbo Golang SDK documentation](https://github.com/apache/dubbo-website/tree/master/content/zh-cn/overview/mannual/golang-sdk) + +## Skills + +### scaffolding + +Generates provider or consumer skeletons in v3 code-API style (`dubbo.NewInstance` / `server.NewServer` / `client.NewClient`). Asks about protocol (Triple / Dubbo / gRPC) and registry (Nacos / ZooKeeper / etcd / direct) first, then produces a complete, compilable skeleton matching the official samples. Covers OpenAPI, HTTP handler attachment, and HTTP/3. + +### extensions + +Custom SPI extensions — Filter, LoadBalance, Registry, Protocol, Router, Logger. Explains the uniform pattern (`extension.SetXxx` + blank import + `WithFilter` / `WithLoadBalance`), with runnable templates and the silent-failure modes that catch newcomers. + +### java-interop + +Cross-language RPC between dubbo-go and dubbo-java. Picks Triple+Protobuf for new services, Dubbo+Hessian2 for existing Java-defined ones, and gets POJO class names, method-name casing, and the curl-friendly HTTP route format right. + +### debug + +Structured diagnosis for runtime errors. Matches your error or log against known patterns — "no provider available", connection refused, serialization mismatch, timeout, filter panic, OpenAPI 404, AttachHTTPHandler failure, slow shutdown — and gives a targeted checklist. + +### guide + +Architecture, extension points, and best practices. Covers Instance, Protocol, Registry, Filter, Cluster, LoadBalance, Router, Triple OpenAPI, HTTP/3, CORS, graceful shutdown, observability, and tells agents to read the current `apache/dubbo-go-samples` README and directories when choosing examples. + +### migrate + +Step-by-step migration guidance: + +- **gRPC-Go** — concept mapping, proto reuse, direct mode parallels +- **Spring Cloud (Java)** — registry sharing, Java/Go coexistence path +- **Gin / plain HTTP** — coexistence options and Triple REST mode +- **dubbo-go v1/v2 → v3** — breaking-change table, minimum migration path +- **YAML-heavy v3** — incremental move to the code API + +### development + +For contributors modifying the `apache/dubbo-go` repository itself — Go toolchain, package boundaries, validation commands, generated files, repository-level do-not rules. Not for application-side scaffolding. + +## Contributing + +Skills live in `.agents/skills//SKILL.md`. + +1. Edit or add a skill in place. +2. Keep the frontmatter `description` trigger-focused — that line is what the agent reads to decide whether to use the skill. +3. Update `.agents/` metadata only when install paths, skill names, or supported agents change. +4. Submit through the normal dubbo-go contribution workflow. + +## License + +Apache License 2.0 diff --git a/.agents/README_CN.md b/.agents/README_CN.md new file mode 100644 index 0000000000..d6154d2920 --- /dev/null +++ b/.agents/README_CN.md @@ -0,0 +1,130 @@ + + +# dubbo-go Agent Skills + +[English](README.md) | 中文 + +随 [Apache dubbo-go](https://github.com/apache/dubbo-go) 一起维护的 AI Agent Skills——让你的编码助手真正会写 dubbo-go v3。 + +## 它能给你什么 + +你在用 dubbo-go 时,AI 助手现在知道自己在做什么了。 + +让它搭一个新的 provider 或 consumer,它会选对当前的 code-API 风格(`dubbo.NewInstance` + `server.WithServerProtocol`),接上你实际用的注册中心,第一次就能编译过。要写自定义 filter 或负载均衡?它懂 SPI 模式——`extension.SetXxx` 注册、blank import、按 service 或 reference 启用。要和 Java Dubbo 服务互通?它会根据你有的是 `.proto` 还是 Java 接口,正确选 Triple+Protobuf 或 Dubbo+Hessian2,POJO 类名也能对齐。 + +问它服务为什么连不上,它按结构化清单逐步排查——注册中心、协议匹配、序列化、filter 链——而不是瞎猜。要从 gRPC-Go 或 Spring Cloud 迁移?它会把概念逐一映射。 + +Skill 在上下文匹配时自动触发,不需要手动调用。 + +> 所有 skill 仅支持 dubbo-go **v3**,v1/v2 已废弃。 + +## 安装 + +不同 AI 工具安装方式不同。Claude Code 和 Cursor 内置市场,Codex 和 OpenCode 需要手动安装。 + +### Claude Code / Cursor + +```bash +/plugin marketplace add apache/dubbo-go +/plugin install dubbo-go@dubbo-go-agent-skills +``` + +### Codex + +告诉 Codex: + +``` +Fetch and follow instructions from https://raw.githubusercontent.com/apache/dubbo-go/main/.agents/.codex/INSTALL.md +``` + +详细文档:[.codex/INSTALL.md](.codex/INSTALL.md) + +### OpenCode + +在 `opencode.json` 中加: + +```json +{ + "plugin": ["dubbo-go-agent-skills@git+https://github.com/apache/dubbo-go.git#path:.agents"] +} +``` + +### 验证 + +新开一个会话,试试这些: + +- "帮我搭一个用 Nacos 的 dubbo-go provider" +- "consumer 报 'no provider available' 是什么原因" +- "怎么从 Go 调一个 Java Dubbo 服务?" + +助手应该会自动触发对应的 skill。 + +## 参考文档 + +面向用户的行为说明和教程,以官方 Dubbo Golang SDK 文档为主要参考: + +- [Dubbo Golang SDK 文档](https://github.com/apache/dubbo-website/tree/master/content/zh-cn/overview/mannual/golang-sdk) + +## Skills + +### scaffolding + +按 v3 code-API 风格(`dubbo.NewInstance` / `server.NewServer` / `client.NewClient`)生成 provider 或 consumer 骨架。先确认协议(Triple / Dubbo / gRPC)和注册中心(Nacos / ZooKeeper / etcd / 直连),再生成与官方 samples 一致、可直接编译运行的骨架。覆盖 OpenAPI、HTTP handler 挂载、HTTP/3。 + +### extensions + +自定义 SPI 扩展——Filter、LoadBalance、Registry、Protocol、Router、Logger。讲清统一模式(`extension.SetXxx` + blank import + `WithFilter` / `WithLoadBalance`),附可运行模板和新人最常踩的"静默失败"陷阱。 + +### java-interop + +dubbo-go 与 dubbo-java 的跨语言 RPC。新服务选 Triple+Protobuf,老 Java 接口选 Dubbo+Hessian2,POJO 类名、方法名大小写、curl 友好的 HTTP 路由格式都对齐好。 + +### debug + +运行时报错的结构化排查。把错误或日志和已知模式匹配——"no provider available"、连接被拒、序列化不匹配、超时、filter panic、OpenAPI 404、AttachHTTPHandler 失败、shutdown 偏慢——给出针对性的检查清单。 + +### guide + +架构、扩展点、最佳实践。覆盖 Instance、Protocol、Registry、Filter、Cluster、LoadBalance、Router、Triple OpenAPI、HTTP/3、CORS、graceful shutdown、可观测性,并要求 Agent 在选择示例时读取当前 `apache/dubbo-go-samples` README 和目录。 + +### migrate + +分步骤迁移指引: + +- **gRPC-Go**——概念映射、proto 复用、直连模式对照 +- **Spring Cloud(Java)**——注册中心复用、Java/Go 共存 +- **Gin / 纯 HTTP**——共存方案和 Triple REST 模式 +- **dubbo-go v1/v2 → v3**——breaking-change 对照、最小迁移路径 +- **YAML 重度依赖的 v3**——平滑迁移到 code API + +### development + +面向 `apache/dubbo-go` 仓库本身的贡献者——Go 工具链、包边界、校验命令、生成文件、仓库级禁止事项。**不**用于应用侧脚手架。 + +## 贡献 + +Skill 文件位于 `.agents/skills//SKILL.md`。 + +1. 直接修改或新增 skill。 +2. 保持 frontmatter 的 `description` 聚焦触发场景——Agent 就是靠这一行决定要不要使用该 skill。 +3. 只有当安装路径、skill 名称或支持的 Agent 发生变化时,才修改 `.agents/` 下的元数据。 +4. 按 dubbo-go 正常贡献流程提交修改。 + +## License + +Apache License 2.0 diff --git a/.agents/package.json b/.agents/package.json new file mode 100644 index 0000000000..83c2337a8c --- /dev/null +++ b/.agents/package.json @@ -0,0 +1,15 @@ +{ + "name": "dubbo-go-agent-skills", + "version": "0.2.0", + "type": "module", + "description": "Agent skills bundled with Apache dubbo-go for v3 framework users — scaffolding, extensions, Java interop, debugging, guidance, migration, plus contributor support.", + "author": "Apache Dubbo contributors", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/apache/dubbo-go.git", + "directory": ".agents" + }, + "homepage": "https://github.com/apache/dubbo-go/tree/main/.agents", + "main": ".opencode/plugins/dubbo-go-agent-skills.js" +} diff --git a/.agents/plugin.json b/.agents/plugin.json new file mode 100644 index 0000000000..92013f4454 --- /dev/null +++ b/.agents/plugin.json @@ -0,0 +1,15 @@ +{ + "name": "dubbo-go", + "version": "0.2.0", + "description": "Agent skills bundled with Apache dubbo-go for v3 framework users — scaffolding, extensions, Java interop, debugging, guidance, migration, plus contributor support.", + "author": "Apache Dubbo contributors", + "skills": [ + "skills/scaffolding", + "skills/extensions", + "skills/java-interop", + "skills/debug", + "skills/guide", + "skills/migrate", + "skills/development" + ] +} diff --git a/.agents/skills/debug/SKILL.md b/.agents/skills/debug/SKILL.md new file mode 100644 index 0000000000..2d7e55af6b --- /dev/null +++ b/.agents/skills/debug/SKILL.md @@ -0,0 +1,189 @@ +--- +# 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-debugging +description: Structured diagnosis for dubbo-go v3 runtime errors. Use when the user reports an error, pastes logs, mentions timeout/panic/connection refused/no provider/serialization mismatch, or asks why their dubbo-go service isn't working. +--- + +# Debugging dubbo-go + +When the user shares an error or log, match it to a pattern below. If unclear, ask for: + +1. Full error message or stack trace +2. Registry type (Nacos / ZooKeeper / etcd / Polaris / direct) +3. Protocol (Triple / Dubbo / gRPC / JSONRPC) +4. Whether provider and consumer are separate processes + +## Quick Triage + +| Symptom in logs | Jump to | +|---|---| +| `no provider available`, `no route`, `Should has at least one way to know` | [No provider / no route](#no-provider--no-route) | +| `dial tcp ... connection refused`, `i/o timeout` on connect | [Connection refused / dial error](#connection-refused--dial-error) | +| `hessian: failed to decode`, `protobuf: cannot parse invalid wire-format data` | [Serialization / decode error](#serialization--decode-error) | +| `context deadline exceeded`, `invoke timeout` | [Timeout](#timeout) | +| `filter not found`, panic inside a filter | [Filter not found / filter panic](#filter-not-found--filter-panic) | +| Provider exits seconds after start | [Provider starts but immediately exits](#provider-starts-but-immediately-exits) | +| `404` at `/dubbo/openapi/...`, empty spec | [OpenAPI 404 / empty spec](#openapi-404--empty-spec) | +| `AttachHTTPHandler` returns error | [AttachHTTPHandler errors](#attachhttphandler-errors) | +| Pods drain slowly, in-flight requests truncated on shutdown | [Graceful shutdown](#graceful-shutdown) | + +## No provider / no route + +**Cause**: Consumer cannot find a provider in the registry. + +Checklist: +- [ ] Provider started successfully? Look for `dubbo server started` and `A provider service ... was registered successfully` in provider logs. +- [ ] Same registry address on both sides (`dubbo.WithRegistry(registry.WithAddress(...))` / YAML `dubbo.registries.xxx.address`)? +- [ ] Same application name on both sides (`dubbo.WithName(...)`)? v3 defaults to **application-level** discovery — the registry stores the app name, not the interface FQN. +- [ ] Same service discovery level on both sides? Latest dubbo-go defaults to **application-level** discovery, while older dubbo-go versions commonly defaulted to **interface-level** discovery; application-level and interface-level discovery do not interoperate. +- [ ] Same `interface` name passed to `pb.RegisterXxxHandler` and `pb.NewXxxService`? +- [ ] Same protocol on both sides (both `tri` or both `dubbo`)? +- [ ] Provider visible in the registry? + +```bash +# Nacos — application-level discovery, query by app name +curl "http://127.0.0.1:8848/nacos/v1/ns/instance/list?serviceName=" + +# ZooKeeper +zkCli.sh ls /services + +# etcd +etcdctl get --prefix /services +``` + +If the consumer cannot map an interface to an application, hint with `client.WithProvidedBy("")`. + +## Connection refused / dial error + +**Cause**: Network or port misconfiguration. + +Checklist: +- [ ] Provider port (`protocol.WithPort(...)`) matches what the consumer is dialing? +- [ ] Firewall / Docker network allows the port? +- [ ] Inside Docker: using container/service name instead of `localhost`? +- [ ] Provider actually listening? `lsof -i :20000` on the provider host. + +## Serialization / decode error + +``` +hessian: failed to decode +protobuf: cannot parse invalid wire-format data +``` + +**Cause**: Provider and consumer using different serialization formats. + +Checklist: +- [ ] Both sides on the same protocol (both `tri` or both `dubbo`)? +- [ ] Protobuf: same `.proto` compiled on both sides? Same `go_package`? +- [ ] Hessian2: POJO `JavaClassName()` matches the Java class FQN exactly? `RegisterPOJO` called in an `init()` that actually runs? + +For Hessian2 specifics, see [dubbo-go-java-interop](../java-interop/SKILL.md). + +## Timeout + +``` +context deadline exceeded +invoke timeout +``` + +**Cause**: Provider too slow, filter blocking, network delay, or low timeout. + +Checklist: +- [ ] Provider actually receiving the request? Add a log line at the top of the handler. +- [ ] Any filter that may block? (auth, token, tps-limit) +- [ ] Timeout high enough? + +```go +// per-reference +svc, _ := pb.NewGreetService(cli, client.WithRequestTimeout(10*time.Second)) + +// per-call +ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) +defer cancel() +resp, err := svc.Greet(ctx, req) +``` + +## Filter not found / filter panic + +**Cause**: Filter imported but `init()` not registered, or name mismatch between `extension.SetFilter` and `WithFilter`. + +Checklist: +- [ ] Blank import present? e.g. `_ "dubbo.apache.org/dubbo-go/v3/filter/token"`, `_ "github.com/apache/dubbo-go-extensions/filter/hystrix"`, or `_ "github.com/yourorg/yourapp/filter/myfilter"` +- [ ] String passed to `server.WithFilter("xxx")` / `client.WithFilter("xxx")` matches the name in `extension.SetFilter("xxx", ...)`? +- [ ] Built-ins not pulled in? Use `_ "dubbo.apache.org/dubbo-go/v3/imports"` during development to auto-import all built-ins. + +## Provider starts but immediately exits + +**Cause**: Missing blocking call or wrong shutdown setup. + +```go +// Correct: blocking +if err := srv.Serve(); err != nil { panic(err) } + +// Wrong: non-blocking, process exits immediately +go srv.Serve() +``` + +## OpenAPI 404 / empty spec + +Triple-only feature. Checklist: +- [ ] `triple.OpenAPIEnable(true)` set in the protocol options? +- [ ] Hitting the right route? Default is `/dubbo/openapi/openapi.json`. See `triple.OpenAPIPath(...)` to customize. +- [ ] Visiting the right port? OpenAPI lives on the Triple port, not a separate one. + +## AttachHTTPHandler errors + +Checklist: +- [ ] Protocol is Triple? Triple is the only protocol that hosts plain HTTP handlers. +- [ ] Called *before* `srv.Serve()`? +- [ ] Path conflicts with the Triple-reserved namespace (`/dubbo/...`)? + +## Graceful shutdown + +Checklist: +- [ ] Built-in graceful shutdown filters registered? `_ "dubbo.apache.org/dubbo-go/v3/imports"` includes them; with selective imports, add `_ "dubbo.apache.org/dubbo-go/v3/filter/graceful_shutdown"` so `pshutdown` / `cshutdown` are registered. +- [ ] Shutdown timeout long enough for in-flight calls to drain? +- [ ] If running behind Kubernetes: container `terminationGracePeriodSeconds` longer than dubbo-go's shutdown timeout? + +## Reading dubbo-go logs + +Enable debug logging: + +```go +import "dubbo.apache.org/dubbo-go/v3/logger" + +ins, _ := dubbo.NewInstance( + dubbo.WithLogger(logger.WithLevel("debug")), +) +``` + +Inject trace IDs into logs (when OpenTelemetry tracing is configured): + +```go +dubbo.WithLogger(logger.WithTraceIntegration(true)) +``` + +Key log lines: +- `Registering service: ` — service registration starting +- `A provider service ... was registered successfully` — provider ready +- `export service failed` — registration error +- `No provider available` / `no route` — consumer lookup failed + +## Related Skills + +- `dubbo-go-extensions` — when a missing/registered SPI is the root cause +- [dubbo-go-java-interop](../java-interop/SKILL.md) — for Hessian2 decode failures and cross-language discovery +- `dubbo-go-guide` — for the conceptual map (Instance / Server / Client / Protocol / Registry / Filter) diff --git a/.agents/skills/development/SKILL.md b/.agents/skills/development/SKILL.md new file mode 100644 index 0000000000..fddff8deca --- /dev/null +++ b/.agents/skills/development/SKILL.md @@ -0,0 +1,101 @@ +--- +# 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-development +description: Use when modifying, reviewing, testing, or debugging the apache/dubbo-go repository itself - including framework Go code, generated stubs, config structs, tools, CI, tests, or repository-local agent skills. Not for application-side scaffolding. +--- + +# Contributing to apache/dubbo-go + +This skill is for working **inside** the dubbo-go repository, not for using the framework from an application. For app-side work, use `dubbo-go-scaffolding`. + +## Branch Facts + +- Module path: `dubbo.apache.org/dubbo-go/v3` +- Default branch: `main` +- `go.mod` Go version: `1.25.0` +- Toolchain: `GOTOOLCHAIN=go1.25.0+auto` (referenced by the Makefile) +- Import-block format is enforced by `tools/imports-formatter` +- `make fmt` also runs the Go `modernize` analyzer to replace eligible `interface{}` with `any` + +## Validation + +Pick the smallest scope that covers the change: + +```bash +make fmt # format the tree +make check-fmt # exactly what CI runs +make lint # golangci-lint +make test # full suite + dubbogo-cli submodule +make rpc-contract-check # warn on exported variadic RPC contracts + +# targeted package tests +GOTOOLCHAIN=go1.25.0+auto go test ./protocol/triple/... +GOTOOLCHAIN=go1.25.0+auto go test ./server/... +``` + +For `.agents/` doc-only changes, skip the Go suite and validate the skill frontmatter and adapter (`.agents/.opencode/plugins/dubbo-go-agent-skills.js`) instead. + +## Package Boundaries + +| Path | Scope | +|---|---| +| `dubbo.go`, `options.go`, `instance_options_init.go` | Instance-level options and root config bridging | +| `client/` | Reference options, consumer URL, invoker creation | +| `server/` | Provider options, service registration, `AttachHTTPHandler` | +| `protocol/base`, `protocol/result`, `protocol/invocation` | Core invocation interfaces | +| `protocol/triple` | Triple impl: HTTP/2, HTTP/3, CORS, OpenAPI, reflection, attached HTTP | +| `registry/`, `metadata/` | Registry, service discovery, app-to-interface mapping | +| `cluster/` | Cluster strategies, directory, load balance, router chain | +| `filter/` | Provider/consumer filters | +| `config/`, `global/`, `config_center/` | YAML/koanf config models, dynamic config loading | +| `graceful_shutdown/` | Shutdown timeouts and lifecycle | +| `logger/`, `metrics/`, `otel/` | Observability | +| `tools/` | CLI, schema, generators, imports formatter, RPC contract scanner | + +## Current Implementation Notes + +- Config loading uses **koanf**. Do not introduce viper-style assumptions in new code. +- Triple config now includes CORS, HTTP/3, and OpenAPI sub-options. +- `server.AttachHTTPHandler` hosts one `http.Handler` on an explicit Triple port and must be called before `Serve`. +- Graceful shutdown has separate total / step / notify / consumer-update / offline-request / closing-invoker timeouts. +- Application-level discovery may need metadata mapping or explicit `client.WithProvidedBy`. +- Registry config can choose service / interface / all registration via registry options. +- Routers implementing `router.StaticConfigSetter` accept static injection. +- New code should import `protocol/base` and `protocol/result` directly, not the older aliases. + +## Generated and Tooling Files + +- Do not hand-edit generated Protobuf / Triple files unless the generator source is also in scope. +- User-side stubs come from `protoc-gen-go` and `github.com/dubbogo/protoc-gen-go-triple/v3`. +- For OpenAPI: runtime Triple OpenAPI or `tools/protoc-gen-triple-openapi`, depending on task. +- `tools/dubbogo-cli` has its own module — run tests from that directory when editing it. + +## Red Flags + +Stop and surface the tradeoff before doing any of these: + +- Removing or skipping a failing test to "unblock" CI +- Renaming or removing an exported option/function in `client/`, `server/`, `protocol/`, or `registry/` without a migration note +- Bypassing `tools/imports-formatter` with hand-edited import groupings +- Replacing koanf-based config with a different config library +- `--no-verify` on a commit, or skipping `make check-fmt` / `make lint` +- Introducing a third-party dependency for something `gost`, `koanf`, or stdlib already covers + +## Related Skills + +- `dubbo-go-guide` — conceptual map of packages and options +- `dubbo-go-extensions` — when adding or fixing an SPI in this repo +- `dubbo-go-debugging` — when reproducing a user-reported failure diff --git a/.agents/skills/extensions/SKILL.md b/.agents/skills/extensions/SKILL.md new file mode 100644 index 0000000000..03fb0fc500 --- /dev/null +++ b/.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") + for _, iv := range invokers { + if iv.GetURL().GetParam("tenant", "") == tenant { + return iv + } + } + return invokers[0] +} +``` + +Activate per reference: +```go +svc, _ := pb.NewGreetService(cli, client.WithLoadBalance("tenant-affinity")) +``` + +Or as the client default: +```go +cli, _ := client.NewClient(client.WithClientLoadBalance("tenant-affinity")) +``` + +## Registry (new service-discovery backend) + +Only needed when the built-in set (Nacos, ZooKeeper, etcd, Polaris, Kubernetes) does not cover your platform. + +```go +import ( + "dubbo.apache.org/dubbo-go/v3/common" + "dubbo.apache.org/dubbo-go/v3/common/extension" + "dubbo.apache.org/dubbo-go/v3/registry" +) + +func init() { + extension.SetRegistry("myregistry", func(url *common.URL) (registry.Registry, error) { + return newMyRegistry(url) + }) +} +``` + +Implementing the full `registry.Registry` interface is a non-trivial undertaking — read `registry/nacos` or `registry/zookeeper` for reference. + +Enable: +```go +dubbo.WithRegistry(registry.WithRegistry("myregistry"), registry.WithAddress("...")) +``` + +## Router (traffic-shaping) + +Routers prune the provider list before the load balancer runs. Built-ins: tag, condition, script. + +**Custom routers are uncommon.** The shipped routers read rules from the config center at runtime, so what users usually want is *rule authoring*, not a new router. See: +- [router/tag](https://github.com/apache/dubbo-go-samples/tree/main/router/tag) — tag-based canary +- [router/condition](https://github.com/apache/dubbo-go-samples/tree/main/router/condition) — predicate-based +- [router/script](https://github.com/apache/dubbo-go-samples/tree/main/router/script) — scripted rules + +If you genuinely need a new routing algorithm, implement `router.PriorityRouter` and register via `extension.SetRouterFactory`. + +## Protocol (new wire protocol) + +Rarely needed. Implement `base.Protocol`: `Export(invoker)`, `Refer(url)`, `Destroy()`. Register with `extension.SetProtocol(name, factory)`. Most users should stick with Triple, Dubbo, JSONRPC, or REST. + +## Troubleshooting + +| Symptom | Likely cause | +|---|---| +| `filter not found` at startup | Blank import missing, or string in `WithFilter("x")` does not match `SetFilter("x", ...)` | +| Filter registered but never called | `init()` lives in a file that is never imported — check the blank-import path | +| LoadBalance not used | Built-in LB took precedence; ensure the name in `WithLoadBalance(...)` matches your registered name | +| Filter modifies attachment but not visible on the other side | Triple attachments travel as HTTP/2 headers; reserved header names may be dropped | +| Custom registry not picked up | `extension.SetRegistry("name", ...)` must match `registry.WithRegistry("name")` exactly | + +## Key Rule of Thumb + +The **name string is the contract**. Whatever you pass to `extension.SetFilter("timing", ...)` must be the exact string passed to `server.WithFilter("timing")` or `client.WithFilter("timing")`. Typos here produce silent failures — the extension simply does not run. + +## Related Skills + +- `dubbo-go-guide` — when deciding whether a built-in already covers the need +- `dubbo-go-scaffolding` — when the user also needs the surrounding provider/consumer skeleton +- `dubbo-go-debugging` — when an SPI is registered but does not run diff --git a/.agents/skills/guide/SKILL.md b/.agents/skills/guide/SKILL.md new file mode 100644 index 0000000000..5ee1db7fbd --- /dev/null +++ b/.agents/skills/guide/SKILL.md @@ -0,0 +1,98 @@ +--- +# 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-guide +description: Explains dubbo-go v3 architecture, extension points, and best practices. Use when the user asks how dubbo-go works, what a concept means (Instance, Protocol, Registry, Filter, Cluster, LoadBalance, Router, OpenAPI, graceful shutdown), which sample to look at, or asks for best practices. +--- + +# Guiding dubbo-go Development + +## Core Concepts + +**Instance** — top-level entry point. `dubbo.NewInstance()` configures the application globally; create server and client from it. + +**Protocol** — how bytes travel on the wire. Triple is the default (HTTP/2, gRPC-compatible). Dubbo is for Java interop with Hessian2. JSONRPC and REST are also built-in. + +**Registry** — service discovery. Provider registers itself; consumer queries to find providers. Built-ins: Nacos, ZooKeeper, etcd, Polaris, Kubernetes. + +**Filter** — interceptor chain around every RPC call, on both sides. Used for auth, metrics, tracing, rate limiting. + +**Cluster** — fault-tolerance strategy when calling a provider group. Failover (default), Failfast, Failsafe, Forking, Broadcast. + +**LoadBalance** — selects one provider from the candidate list. Random (default), RoundRobin, LeastActive, ConsistentHash, P2C. + +**Router** — prunes the provider list before load balancing. Tag, condition, script. Rules typically come from the config center, not from custom code. + +**MetadataReport / ConfigCenter** — application-level metadata storage and dynamic configuration. Same backend as the registry in most setups. + +## SPI Extension Pattern + +Every dubbo-go extension follows the same shape: + +```go +// 1. Implement the interface +type myFilter struct{} +func (f *myFilter) Invoke(ctx context.Context, invoker base.Invoker, inv base.Invocation) result.Result { + res := invoker.Invoke(ctx, inv) + return res +} +func (f *myFilter) OnResponse(ctx context.Context, res result.Result, invoker base.Invoker, inv base.Invocation) result.Result { + return res +} + +// 2. Register in init() +func init() { + extension.SetFilter("my-filter", func() filter.Filter { return &myFilter{} }) +} + +// 3. Activate with a blank import in main.go +// import _ "github.com/yourorg/yourapp/filter/myfilter" + +// 4. Enable per-service via the code API +// pb.RegisterGreetServiceHandler(srv, impl, server.WithFilter("my-filter")) +``` + +The same pattern applies to Protocol, Registry, LoadBalance, Router, ConfigCenter, MetadataReport, Logger. + +> Note: older samples still show `protocol.Invoker/Invocation/Result` aliases. They resolve to the same types in `protocol/base` and `protocol/result` — prefer the new packages in new code. + +## Best Practices + +- Use `_ "dubbo.apache.org/dubbo-go/v3/imports"` in development for convenience; switch to selective imports in production for smaller binaries. +- Define Triple services with Protobuf for cross-language interop. +- Configure via the code API: `dubbo.NewInstance(dubbo.WithRegistry(...), dubbo.WithProtocol(...))`. YAML via `dubbo.Load()` is the legacy path. +- Keep `srv.Serve()` as the blocking entry point for process-level shutdown. `_ "dubbo.apache.org/dubbo-go/v3/imports"` registers the default graceful shutdown filters; with selective imports, include `_ "dubbo.apache.org/dubbo-go/v3/filter/graceful_shutdown"`. +- Use `client.WithProvidedBy` when application-level discovery cannot resolve which application owns the interface. +- Apply per-reference timeouts: `client.WithRequestTimeout(...)` or a `context.WithTimeout` at the call site. + +## Triple-Only Capabilities + +- **OpenAPI** — `triple.OpenAPIEnable(true)` publishes a runtime OpenAPI spec. +- **HTTP handler attachment** — `srv.AttachHTTPHandler("/path", handler)` mounts plain HTTP on the same port. +- **HTTP/3** — `triple.Http3Enable()` (experimental, TLS required). +- **CORS** — `triple.CORSAllowOrigins(...)` etc. +- **Reflection** — gRPC reflection is on by default; tools like `grpcurl` work without extra config. + +## Finding Samples + +When the user asks which sample to follow, read the current [apache/dubbo-go-samples](https://github.com/apache/dubbo-go-samples) README and relevant directories instead of relying on a copied index. The samples repository changes independently of dubbo-go; prefer its live README, directory names, and nearby sample code as the source of truth. + +## Related Skills + +- `dubbo-go-scaffolding` — when the user wants to generate a provider/consumer +- `dubbo-go-extensions` — when a built-in is not enough and the user needs a custom SPI +- `dubbo-go-java-interop` — when the question involves dubbo-java compatibility +- `dubbo-go-debugging` — when the question is "why is X failing" +- `dubbo-go-migration` — when the question is "how do I move from Y to dubbo-go" diff --git a/.agents/skills/java-interop/SKILL.md b/.agents/skills/java-interop/SKILL.md new file mode 100644 index 0000000000..324fab0f85 --- /dev/null +++ b/.agents/skills/java-interop/SKILL.md @@ -0,0 +1,181 @@ +--- +# 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-java-interop +description: Guides interoperability between dubbo-go v3 and dubbo-java — cross-language RPC using Triple+Protobuf or Dubbo+Hessian2. Use when the user asks how to call a Java Dubbo service from Go, expose a Go service to Java clients, share a proto/interface across languages, or pick between Triple and Dubbo for interop. +--- + +# Dubbo Java and Go Interop + +A single service definition can be served by Go and consumed by Java (or vice versa) without a gateway. Two viable paths: + +| Path | Wire format | Serialization | When to use | +|---|---|---|---| +| **Triple + Protobuf** (recommended) | HTTP/2 | Protobuf | New services, polyglot teams, gRPC-compatible clients | +| **Dubbo + Hessian2** | TCP (dubbo) | Hessian2 | Calling existing Java services that were originally defined as Java interfaces (no `.proto`) | + +Pick Triple unless integrating with an existing Hessian2 Java codebase. + +## Path 1: Triple + Protobuf (recommended) + +A shared `.proto` file drives both sides. Set both `go_package` and `java_package`: + +```protobuf +syntax = "proto3"; + +package org.apache.dubbo.sample; + +option go_package = "github.com/yourorg/yourapp/proto;greet"; +option java_package = "org.apache.dubbo.sample"; +option java_multiple_files = true; + +service Greeter { + rpc SayHello(HelloRequest) returns (HelloReply); +} + +message HelloRequest { string name = 1; } +message HelloReply { string message = 1; } +``` + +**Go server** — nothing interop-specific, just Triple: + +```go +srv, _ := server.NewServer( + server.WithServerProtocol( + protocol.WithPort(20000), + protocol.WithTriple(), + ), +) +greet.RegisterGreeterHandler(srv, &impl{}) +srv.Serve() +``` + +**Test interop with curl** — Triple supports JSON over HTTP/1.1: + +```bash +curl -H "Content-Type: application/json" \ + -d '{"name":"Dubbo"}' \ + http://localhost:20000/org.apache.dubbo.sample.Greeter/sayHello +``` + +The path is `/./` — the Java side uses lowercase-first-letter method names by convention. + +**Java client** uses standard dubbo-java APIs; no Go-specific code. See [java_interop/protobuf-triple](https://github.com/apache/dubbo-go-samples/tree/main/java_interop/protobuf-triple) for the working pair. + +### Rules of thumb for Triple interop + +- **Method-name casing**: Protobuf generators emit `SayHello` in Go and `sayHello` on the Java wire. The HTTP route uses the lowercase-first form. +- **Package matters**: `java_package` in the proto becomes the Java interface FQN — pick deliberately, renaming later is breaking. +- **Reflection**: Triple exposes gRPC reflection automatically; tools like `grpcurl` and `bloomrpc` work without extra config. + +## Path 2: Dubbo + Hessian2 (Java-defined services) + +Use this when the service is **defined by a Java interface** (no `.proto`) and you need to add a Go side. + +The Go side mirrors the Java POJO via Hessian2 registration. Field names, types, and `JavaClassName()` must match the Java class. + +```go +package greet + +import ( + dubbo_go_hessian2 "github.com/apache/dubbo-go-hessian2" +) + +type GreetRequest struct { + Name string +} + +func (x *GreetRequest) JavaClassName() string { + return "org.apache.dubbo.hessian2.api.GreetRequest" +} + +type GreetResponse struct { + Greeting string +} + +func (x *GreetResponse) JavaClassName() string { + return "org.apache.dubbo.hessian2.api.GreetResponse" +} + +func init() { + dubbo_go_hessian2.RegisterPOJO(new(GreetRequest)) + dubbo_go_hessian2.RegisterPOJO(new(GreetResponse)) +} +``` + +**Go server** — pick the Dubbo protocol, not Triple: + +```go +srv, _ := server.NewServer( + server.WithServerProtocol( + protocol.WithPort(20000), + protocol.WithDubbo(), + ), +) +greet.RegisterGreetServiceHandler(srv, &impl{}) +srv.Serve() +``` + +See [java_interop/non-protobuf-dubbo](https://github.com/apache/dubbo-go-samples/tree/main/java_interop/non-protobuf-dubbo) for the canonical end-to-end example. + +### Hessian2 gotchas + +- **Field names are case-sensitive on the wire** — Go's exported names are serialized lowercase-first to match Java conventions. The hessian2 library handles this; custom struct tags can break it. +- **Unsupported types**: Go generics, channels, functions will not serialize. Stick to primitives, slices, maps, and other registered POJOs. +- **Unregistered POJOs** → `hessian: failed to decode` at runtime. Always `RegisterPOJO` in the package's `init()`. +- **Java `enum` maps to Go `int`** (the ordinal), not a string — unless the Java side customized serialization. +- **Time**: Java `Date` ↔ Go `time.Time` generally works; `java.time.*` types need extra care. + +## Service Discovery Across Languages + +When both sides use the **same registry** (Nacos / ZooKeeper), they discover each other automatically — the registry entry is language-agnostic, just a URL with protocol + address. + +```go +dubbo.NewInstance( + dubbo.WithName("polyglot-service"), // same application name as the Java side + dubbo.WithRegistry(registry.WithNacos(), registry.WithAddress("127.0.0.1:8848")), + dubbo.WithProtocol(protocol.WithTriple(), protocol.WithPort(20000)), +) +``` + +If application-level discovery cannot resolve which application owns the interface, hint with `client.WithProvidedBy("")`. + +See [java_interop/service_discovery](https://github.com/apache/dubbo-go-samples/tree/main/java_interop/service_discovery). + +## Decision Table + +| Scenario | Path | +|---|---| +| New service, Go and Java teams both need clients | **Triple + Protobuf** | +| Calling an existing Java Dubbo service defined by a Java interface | **Dubbo + Hessian2** | +| Legacy Java service exports both Triple and Dubbo endpoints | **Triple + Protobuf** on the Go side | +| Need a gRPC-compatible wire | **Triple + Protobuf** | +| Cannot touch the Java side, and it is not Protobuf | **Dubbo + Hessian2** | + +## Reference Samples + +- [java_interop/protobuf-triple](https://github.com/apache/dubbo-go-samples/tree/main/java_interop/protobuf-triple) — both directions, Triple + Protobuf +- [java_interop/non-protobuf-dubbo](https://github.com/apache/dubbo-go-samples/tree/main/java_interop/non-protobuf-dubbo) — Hessian2 POJO bridge +- [java_interop/non-protobuf-triple](https://github.com/apache/dubbo-go-samples/tree/main/java_interop/non-protobuf-triple) — Triple without Protobuf (rare) +- [java_interop/service_discovery](https://github.com/apache/dubbo-go-samples/tree/main/java_interop/service_discovery) — Nacos-based cross-language discovery + +Official guide: [Interoperability with Dubbo Java](https://cn.dubbo.apache.org/zh-cn/overview/mannual/golang-sdk/tutorial/interop-dubbo/). + +## Related Skills + +- `dubbo-go-scaffolding` — for the Go side of a polyglot service +- `dubbo-go-extensions` — when adding a Hessian2 codec, Java-aware filter, or shared registry +- `dubbo-go-migration` — when porting an existing Java service or splitting a Spring Cloud monolith +- `dubbo-go-debugging` — when the symptom is `hessian: failed to decode`, missing-provider across languages, or registry-mode mismatch diff --git a/.agents/skills/migrate/SKILL.md b/.agents/skills/migrate/SKILL.md new file mode 100644 index 0000000000..9337be6109 --- /dev/null +++ b/.agents/skills/migrate/SKILL.md @@ -0,0 +1,170 @@ +--- +# 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-migration +description: Guides migration to dubbo-go v3 from gRPC-Go, Spring Cloud, Gin / plain HTTP, dubbo-go v1/v2, YAML-heavy v3 apps, and Java-interface Hessian2 services. Use when the user asks how to migrate, port, or upgrade an existing service to dubbo-go v3. +--- + +# Migrating to dubbo-go + +First question: **where are you migrating from?** + +| User says | Jump to | +|---|---| +| "We're on gRPC-Go" | [From gRPC-Go](#from-grpc-go) | +| "Spring Cloud / Java microservices" | [From Spring Cloud](#from-spring-cloud-java) | +| "Gin / chi / net/http" | [From Gin or plain HTTP](#from-gin--plain-http) | +| "dubbo-go v1 or v2" | [From dubbo-go v1/v2 → v3](#from-dubbo-go-v1v2--v3) | +| "YAML-heavy v3 app" | [From YAML-heavy v3](#from-yaml-heavy-v3) | + +For Java-interface Hessian2 services, see `dubbo-go-java-interop`. + +Use the code API for new work; keep YAML only when preserving an existing deployment model. + +## From gRPC-Go + +**Concept mapping** + +| gRPC-Go | dubbo-go | +|---|---| +| `grpc.NewServer()` | `server.NewServer()` (or `ins.NewServer()` with `dubbo.NewInstance`) | +| `pb.RegisterXxxServer(srv, impl)` | `pb.RegisterXxxHandler(srv, impl)` (Triple) | +| `grpc.Dial(addr)` | `client.NewClient()` → `pb.NewXxxService(cli)` | +| `UnaryServerInterceptor` | Filter | +| `grpc.WithUnaryInterceptor(...)` | `server.WithFilter("my-filter")` + blank import | +| Service reflection | Built-in with Triple | + +**Proto file**: no changes needed. Triple is wire-compatible with gRPC. + +**Key difference**: dubbo-go adds service discovery on top of gRPC. For direct connection, use direct mode — no registry needed. + +```go +// gRPC-Go +conn, _ := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials())) +client := pb.NewGreetClient(conn) + +// dubbo-go (direct mode) +cli, _ := client.NewClient(client.WithClientURL("127.0.0.1:20000")) +svc, _ := pb.NewGreetService(cli) +``` + +See [rpc/grpc](https://github.com/apache/dubbo-go-samples/tree/main/rpc/grpc) and [direct](https://github.com/apache/dubbo-go-samples/tree/main/direct). + +## From Spring Cloud (Java) + +**Concept mapping** + +| Spring Cloud | dubbo-go | +|---|---| +| `@EnableDiscoveryClient` | `dubbo.WithRegistry(registry.WithNacos(), registry.WithAddress(...))` | +| `@FeignClient` | `client.NewClient()` + generated `pb.NewXxxService(cli)` | +| `@RestController` | `pb.RegisterXxxHandler(srv, impl)` + Triple/REST | +| Spring Cloud Gateway | Apache APISIX or direct | +| Hystrix / Resilience4j | Hystrix filter / Sentinel filter | +| Sleuth / Zipkin | OpenTelemetry filter | +| Actuator `/health` | `metrics/probe` (`/live`, `/ready`) | + +**Migration path**: +1. Keep Java services running. +2. Add dubbo-go providers for new Go services using Triple + Protobuf. +3. Java consumers can call Go providers via Triple (wire-compatible). +4. Migrate Java consumers to Go one by one. + +See [java_interop](https://github.com/apache/dubbo-go-samples/tree/main/java_interop) and `dubbo-go-java-interop`. + +## From Gin / plain HTTP + +**Concept mapping** + +| Gin / HTTP | dubbo-go | +|---|---| +| `gin.Default()` + `r.POST(...)` | `server.NewServer()` + `pb.RegisterXxxHandler(...)` | +| `http.Get(url)` / `http.Client` | `client.NewClient()` + `pb.NewXxxService(cli)` | +| Gin middleware | Filter | +| `r.Run(":8080")` | `srv.Serve()` | +| Route handler func | Method on a service struct | + +dubbo-go is an RPC framework, not an HTTP framework. Two coexistence options: + +**Option A — Keep Gin for HTTP, add dubbo-go for internal RPC**. No conflict; run both in the same process. + +**Option B — Mount the HTTP handler on the Triple port**: + +```go +srv.AttachHTTPHandler("/api", r) // r is your Gin router +``` + +**Option C — Use Triple's OpenAPI / REST mode** to expose RPC methods as HTTP/JSON: + +```go +server.WithServerProtocol( + protocol.WithTriple(triple.OpenAPIEnable(true)), +) +``` + +```bash +# Triple supports JSON over HTTP/1.1 — call any method without a generated client +curl -H "Content-Type: application/json" \ + -d '{"name":"world"}' \ + http://localhost:20000/greet.GreetService/Greet +``` + +See [rpc/triple/openapi](https://github.com/apache/dubbo-go-samples/tree/main/rpc/triple/openapi). + +## From dubbo-go v1/v2 → v3 + +**Breaking-change summary** + +| v1/v2 | v3 | +|---|---| +| `config.Load()` | `dubbo.NewInstance()` (code API, recommended) or `dubbo.Load()` (YAML, legacy) | +| `hessian.RegisterPOJO()` + Java-interface | Protobuf + generated Triple stubs (recommended) | +| `config.SetProviderService()` | `pb.RegisterGreetServiceHandler(srv, impl)` | +| `config.ConsumerConfig.References` | `client.NewClient()` → `pb.NewGreetService(cli)` | +| `getty` as default transport | Triple (HTTP/2) as default | +| Interface-level discovery | Application-level discovery | + +**Recommended path**: migrate to the code API. YAML via `dubbo.Load()` still works, but the YAML key structure is not guaranteed identical across versions — expect to rewrite `registries`, `protocols`, and service registration. + +`getty`-style v1/v2 Dubbo+Hessian2 services can keep working with `protocol.WithDubbo()` while interfaces are rebuilt as `.proto` for Triple. See `dubbo-go-java-interop` for the Hessian2 POJO bridge. + +## From YAML-heavy v3 + +You don't have to abandon YAML. `dubbo.Load()` is still supported. Migration to code API is incremental: + +1. Move shared config (registries, protocols) into `dubbo.NewInstance(...)`. +2. Replace `dubbo.Load()` with `ins.NewServer()` / `ins.NewClient()` per service. +3. Keep YAML for environment-specific overrides via the config center. + +See [config_yaml](https://github.com/apache/dubbo-go-samples/tree/main/config_yaml). + +## Validation + +```bash +go mod tidy +go build ./... +go test ./... +``` + +## Reference + +Official guide: [dubbo-go v3 user manual](https://cn.dubbo.apache.org/zh-cn/overview/mannual/golang-sdk/) (also available in English on the same site). + +## Related Skills + +- `dubbo-go-scaffolding` — for the target-side provider/consumer skeleton +- `dubbo-go-java-interop` — when the migration crosses the Java/Go boundary +- `dubbo-go-extensions` — when porting custom interceptors / filters +- `dubbo-go-debugging` — when the migrated service starts but does not register, route, or decode diff --git a/.agents/skills/scaffolding/SKILL.md b/.agents/skills/scaffolding/SKILL.md new file mode 100644 index 0000000000..bdc84c0411 --- /dev/null +++ b/.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)) +``` + +**HTTP/3** — experimental, requires TLS: + +```go +protocol.WithTriple(triple.Http3Enable()) +``` + +See [http3](https://github.com/apache/dubbo-go-samples/tree/main/http3). + +## Conventions + +These are non-obvious rules the samples repo follows; preserve them when scaffolding: + +- **Three import blocks**: stdlib, third-party, same-module — separated by blank lines. The repo's `tools/imports-formatter` enforces this. +- **Blank-import `imports` during development**: `_ "dubbo.apache.org/dubbo-go/v3/imports"` pulls in every built-in filter, protocol, registry, serializer. Switch to selective imports for production builds. +- **Fail fast on startup** — samples `panic` or `logger.Fatalf` on `NewInstance` / `NewServer` / `NewClient` / `Register*Handler` errors. Do not swallow them. +- **Application name matters** — v3 defaults to **application-level** service discovery, so `dubbo.WithName("...")` is what appears in the registry, not the interface FQN. + +## Quick Decision Table + +| User says | Use | +|---|---| +| "just want to try it" / "local demo" | direct mode, no registry | +| "production" / "service discovery" | `dubbo.NewInstance` + Nacos or ZK | +| "talk to Java Dubbo" | Triple + Protobuf — see `dubbo-go-java-interop` | +| "expose REST or curl-friendly endpoint" | Triple + OpenAPI, or Triple REST mode | +| "existing YAML config" | `dubbo.Load()` (legacy) — see `config_yaml` sample | + +## Reference Samples + +- [helloworld](https://github.com/apache/dubbo-go-samples/tree/main/helloworld) — minimal Triple, direct mode +- [direct](https://github.com/apache/dubbo-go-samples/tree/main/direct) — explicit `tri://` URL +- [registry/nacos](https://github.com/apache/dubbo-go-samples/tree/main/registry/nacos) +- [registry/zookeeper](https://github.com/apache/dubbo-go-samples/tree/main/registry/zookeeper) +- [registry/etcd](https://github.com/apache/dubbo-go-samples/tree/main/registry/etcd) +- [rpc/triple/openapi](https://github.com/apache/dubbo-go-samples/tree/main/rpc/triple/openapi) — runtime OpenAPI +- [http3](https://github.com/apache/dubbo-go-samples/tree/main/http3) — Triple over HTTP/3 + +## Validation + +```bash +go mod tidy +go build ./... +go test ./... +``` + +## Related Skills + +- `dubbo-go-guide` — broader concept map and best practices +- `dubbo-go-extensions` — when the new service needs a custom Filter/LB/Registry +- `dubbo-go-java-interop` — when the new service must talk to dubbo-java +- `dubbo-go-debugging` — when the new skeleton fails to start, register, or connect diff --git a/.gitignore b/.gitignore index a38e3cf5cc..5d94ee689d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ classes # go mod, go test .go-version +node_modules/ vendor/ logs/ .vscode/