Skip to content

Instantly share code, notes, and snippets.

@tedsuo
Last active March 7, 2016 08:22
Show Gist options
  • Save tedsuo/862e6871df6f9a418db1 to your computer and use it in GitHub Desktop.
Save tedsuo/862e6871df6f9a418db1 to your computer and use it in GitHub Desktop.
Why golang favors code generation for protocols
// When writing an application, we want to stick to the details that are specific
// to our application domain. We don't want to write generic code that deals with
// contemporary issues, such as "what type of client would like to connect to us today?"
package bank
// The Account interface represents our application's protocol.
// We'd like to never have to write the generic transport protocol code.
type Account interface{
HowFarInTheHoleAmI() Farthings
}
type Farthings int
// NewAccount creates the only object that does any real work around here.
func NewAccount(money Farthings) Account {
return &account{cash:money,Mutex:sync.Mutex{}}
}
// account defines the logic and memory layout specific to our application.
type account struct {
Sync.Mutex
cash Farthings
}
func(a *account)HowFarInTheHoleAmI() Farthings{
// Actually we'd rather not care about this concurrency business either,
// but that's another story...
a.Lock()
defer a.Unlock()
return a.cash
}
// In our ideal world, code like the following could create a client by dynamically combining
// any interface with any protocol. However, this program can't be implemented in Go today.
package main
import (
"bank"
"rpc"
)
func main(){
var account bank.Account // this cannot compile if ConnectClient takes *interface{}
var config rpc.Config = rpc.NewConfigFromFlags(flags.CommandLine)
err := rpc.ConnectClient(&account, config)
if err != nil {
panic(err)
}
println("You are ", account.HowFarInTheHoleAmI()," farthings deep mate")
}
// A generic rpc client can't be implemented via reflection.
package rpc
import "bank"
// This method will compile but has no useful type information
func ConnectGenericClient(client *interface{}, config Config){
}
// This method can be generated very easily at compile time, by reading in a Go interface as source,
// But it cannot be dynamically generated, as there's no way to design and implement an interfaces at runtime.
func ConnectBankClient(client *bank.Account, config Config){
}
// In the end, code generation isn't so bad for remote interfaces.
// This not only works, but generates clients across multiple languages.
service Account {
rpc HowFarInTheHoleAmI() returns (Farthings);
}
// Is it currently possible to do dynamic rpc in Go?
// Why yes it is! Go's stdlib comes with an rpc implementation.
package main
import (
"bank"
"net/rpc"
)
// The RPC Service can be dynamically generated from the application object.
func main(){
// because the underlying struct contains the method definitions
// the rpc package can extract type info from interface{}
var account interface{} = bank.NewAccount()
rpc.Register(account)
// tell rpc we want tp expose an http service
rpc.HandleHTTP()
// start the http server
err := http.ListenAndServe(":1234", nil)
if err != nil {
panic(err)
}
}
// Implementing the rpc client is also straight forwards, but not as elegant as the
// original application interface.
package main
import (
"bank"
"net/rpc"
)
func main(){
client, err := rpc.DialHTTP("tcp", "127.0.0.1:1234")
if err != nil {
panic(err)
}
const MethodName = "Account.HowFarInTheHoleAmI"
var resp bank.Farthings
// If all your code looked like this, you would hate life. This approach is intended only for situations where
// the domain itself involves the dynamic generation of rpc calls, in which case your code already looks like this.
err = client.Call(MethodName, nil, &resp)
if err != nil {
panic(err)
}
println("You are ", resp, " farthings deep mate")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment