Generic invocation is a special way of calling in Dubbo-Go that allows intermediary nodes to pass call information without interface information, commonly used in testing and gateway scenarios. Generic invocation supports both Dubbo and Triple protocols, but currently, the serialization scheme only supports Hessian.
For better understanding, this document introduces generic invocation with a gateway usage scenario. Let’s first consider a normal invocation (non-generic invocation). The diagram below includes two key roles: consumer and provider (hereinafter referred to as endpoint, representing either a consumer or a provider), each having a definition of the org.apache.dubbo.sample.User interface. Suppose the org.apache.dubbo.sample.User interface needs to be used in the calling behavior.
RPC requires transmission over a network medium; therefore, data cannot be transmitted as go struct and must be transmitted in binary form. This requires that the consumer side serialize the struct implementing the org.apache.dubbo.sample.User interface into binary format before transmission. Likewise, for the provider side, the binary data needs to be deserialized into struct information. In summary, normal invocation requires that interface information must have the same definition at each endpoint to ensure that the results of serialization and deserialization meet expectations.
In gateway scenarios, it is impractical for the gateway to store all interface definitions. For example, if a gateway needs to forward 100 service calls, each requiring 10 interfaces, normal invocation would necessitate storing 1000 (100 * 10) interface definitions in advance, which is clearly difficult. So is there a way that does not require prior storage of interface definitions while still forwarding calls correctly? The answer is yes, which is the reason for using generic invocation.
Generic invocation essentially transforms a complex structure into a generic structure, where the generic structure refers to maps, strings, etc., which the gateway can smoothly parse and pass.
Currently, Dubbo-go v3 only supports Map generic invocation (default). Taking the User interface as an example, its definition is as follows:
// definition
type User struct {
ID string
Name string
Age int32
}
func (u *User) JavaClassName() string {
return "org.apache.dubbo.sample.User"
}
Suppose a service call requires a user as an input parameter, which is defined as follows:
// an instance of the User
user := &User{
ID: "1",
Name: "Zhangsan",
Age: 20,
}
Then under the Map generic invocation, the user will be automatically converted to Map format as follows:
usermap := map[interface{}]interface{} {
"iD": "1",
"name": "zhangsan",
"age": 20,
"class": "org.apache.dubbo.sample.User",
}
Note that:
Generic invocation is transparent to the provider side, meaning the provider does not need any explicit configuration to handle generic requests correctly.
The generic invocation based on Filter is transparent to the consumer, and a typical application scenario is a gateway. This method requires that the Dubbo URL contains a generic invocation identifier, as follows.
dubbo://127.0.0.1:20000/org.apache.dubbo.sample.UserProvider?generic=true&...
The semantics expressed by this Dubbo URL are:
The consumer’s Filter will automatically convert normal calls to generic calls based on the configuration carried by the Dubbo URL, but note that in this method, response results will return in generic format and will not automatically convert to the corresponding object. For example, in map generic invocation, if a User class needs to be returned, the consumer will receive a map corresponding to the User class.
Manual generic invocation requests are not processed through a filter; thus, the consumer must explicitly initiate a generic invocation, a typical application scenario for testing. In dubbo-go-samples, manual invocation is used for ease of testing.
Generic invocation does not require creating a configuration file (dubbogo.yaml), but requires manual configuration of the registry, reference, and other information in the code. The initialization method is encapsulated in the newRefConf method, as follows.
func newRefConf(appName, iface, protocol string) config.ReferenceConfig {
registryConfig := &config.RegistryConfig{
Protocol: "zookeeper",
Address: "127.0.0.1:2181",
}
refConf := config.ReferenceConfig{
InterfaceName: iface,
Cluster: "failover",
Registry: []string{"zk"},
Protocol: protocol,
Generic: "true",
}
rootConfig := config.NewRootConfig(config.WithRootRegistryConfig("zk", registryConfig))
_ = rootConfig.Init()
_ = refConf.Init(rootConfig)
refConf.GenericLoad(appName)
return refConf
}
The newRefConf method takes three parameters:
In the above method, for simplicity, the registry is set as a fixed value, using ZooKeeper at 127.0.0.1:2181 as the registry, which can be customized according to actual conditions in practice.
We can easily obtain a ReferenceConfig instance, temporarily named refConf.
refConf := newRefConf("example.dubbo.io", "org.apache.dubbo.sample.UserProvider", "tri")
Next, we can invoke the GetUser method of the org.apache.dubbo.sample.UserProvider service using generic invocation.
resp, err := refConf.
GetRPCService().(*generic.GenericService).
Invoke(
context.TODO(),
"GetUser",
[]string{"java.lang.String"},
[]hessian.Object{"A003"},
)
The Invoke method of GenericService takes four parameters:
[]string{"type1", "type2", ...}
. If the method is parameterless, you need to fill in an empty array []string{}
;[]hessian.Object{}
as a placeholder.Note: In the current version, parameterless calls may cause crashing issues.
Related reading: 【Dubbo-go Service Proxy Model】