Internal server functionality in Go typically uses one or more goroutines as its backend. This can be done globally in a package or with multiple instances using an own type with methods. In this case a struct containing the server status as well as the channels is used. If the server has only a small external interface with a small number of methods one channel per every different command can be used. This is the most typesafe way. But also produces a higher number of command types to define, one for each command and one for each reply. Additionally the according number of channels has to be declared and instantiated. package authenticationtype SessionKey unit64type cmdLogin struct { login string password string replyChan chan SessionKey}type cmdLogout struct { ... }type cmdChangePassword { ... }var ( cmdLoginChan chan cmdLogin cmdLogoutChan chan cmdLogout cmdChangePassword chan cmdChangePassword)// Automatically start the backend goroutine.func init() { cmdLoginChan = make(chan cmdLogin) go backend()}// Make a login, returning a session key and true for a valid login.func Login(login, password string) (SessionKey, bool) { rc := make(chan SessionKey) cmdLoginChan <- cmdLogin{login, password, rc} sk := <- rc if sk == 0 { return 0, false } return sk, true}// Backend goroutine.func backend() { for { select { case cmd := <-cmdLoginChan: // Handle login ... cmd.replyChan <- sessionKey case cmd := <-cmdLogoutChan: ... case cmd := <-cmdChangePasswordChan: ... } }}An alternative way would be the usage of generic request and response types. They have to be implemented once in an own package and can then be used everywhere for any internal server communication. The payload for each request or response is flexible and can be any kind of data. On the other hand the payload data has to be casted to the expected type. This is a possible source for errors like in all languages using duck typing. package rrcom// Request type.type Request struct { subject string payload map[string]interface{} responseChan chan *Response}// Create a request.func Request(subject string, response bool) *Request { r := &Request{subject, make(map[string]interface{}), nil} if response { r.responseChan = make(chan *Response) } return r}// Set a payload.func (r *Request) SetPayload(key string, data interface{}) { r.payload[key] = data }// Get a payload.func (r *Request) Payload(key string) (interface{}, bool) { if data, ok := r.payload[key]; ok { return data, true } return nil, false}// Get the subject.func (r *Request) Subject() string { return r.subject }// Respond to a request, has to be called by the receiver.func (r *Request) Respond(response *Response) { r.responseChan <- response}// Send the request.
func (r *Request) Send(rc chan *Request) { rc <- r
}// Get the response.func (r *Request) Response() *Response { return <-r.responseChan}// Send the request and wait for the response
func (r *Request) SendAndWait(rc chan *Request) *Response { r.Send(rc) return r.Response()
}// Response type.type Response struct { ok bool payload map[string]interface{}}// Create a response.func Response(ok bool) *Response { return &Response{ok, make(map[string]interface{})}}// Set and get payload like above ...// Test if the request went ok.func (r *Response) IsOk() bool { return r.ok }This construct can be optimized or changed, e.g. through a lazy creation of the payload maps only when needed. Or through using a slice of empty interfaces for the payload, accessing it using an index. But the overall construct stays the same. Now the login of the example above looks like this: func Login(login, password string) (SessionKey, bool) { request := rrcom.Request("login", true) request.SetPayload("login", login) request.SetPayload("password", password) response := request.SendAndWait(requestChan) if response.IsOk() { sk, ok := response.Payload("sessionKey").(uint64) return sk, true } return 0, false}Additionally the server code has to be changed: func backend() { for { request := <-requestChan switch request.Subject() { case "login": // Handle login ... response := rrcom.Response(true) response.SetPayload("sessionKey", sessionKey) request.Respond(response) default: // Handle illegal request ... } }}The default statement helps to handle unknown request subjects. |