Last active
March 7, 2016 08:22
-
-
Save tedsuo/862e6871df6f9a418db1 to your computer and use it in GitHub Desktop.
Why golang favors code generation for protocols
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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") | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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){ | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) | |
} | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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