- Development Servers
- Introductions
- Methods of communicating between applications
- Transport and encoding options
- Cost Savings
- Smaller hardware
- Scalability
- Horizontal vs. Vertical scaling
- Speed
- Reliability
- Failover
- Redundancy
There are infinite combinations of ways to communicate between running applications, but they boil down to two patterns:
- Message Queue
- Network Request/Response
In the message queue pattern, a service on the network sends a message on a queue. Another service listens to that queue and processes the message -- optionally sending a response on a special response queue.
- RabbitMQ
- NATS
- NSQ
- countless others
Pros:
- Simple to reason about communication - send a message on a topic.
- No concern about who/what is receiving and processing the message. Implicit trust in the queue.
- Very scalable, 1 -> N subscribers processing the queue.
- Many can be configured for message durability - guarantee message is saved.
Cons:
- One more thing to manage in your stack.
- Requires special knowledge to maintain the queue service.
- Without good monitoring, it's easy to lose messages.
- Durability guarantees require more work for sysadmin & developers.
- GOPATH/src/github.com/gophertrain/material/dcom/exercises/onenode/readme.txt
- $GOPATH/src/github.com/gophertrain/material/dcom/exercises/logger/readme.txt
There is no hard and fast rule about communicating between your services. You may even have to use multiple options to fit into your existing environment. Write your code with this in mind:
package transport
import (
"inventory"
"net"
)
type InventoryTransporter interface {
inventory.Service
Serve(net.Listener) error
}
This example service has an interface for serving the content that accepts a net.Listener
. Any network transport can satisfy this interface.
There are many flavors of RPC, and the choice you make depends on the consumers of your service and what they can support.
- All Go based - Use net/rpc with
gob
encoding - Varied consumers - use a JSON RPC protocol like net/rpc/json
RPC stands for Remote Procedure Call. Many people have been jaded by previous implementations of RPC (Who remembers CORBA??), but it's very efficient now, and extremely useful in a distributed application architecture.
Client:
func main() {
client, err := rpc.DialHTTP("tcp", "127.0.0.1:1234")
if err != nil {
log.Fatal("dialing:", err)
}
// Synchronous call
args := &stringsvc.Args{Name: "Brian"}
var reply stringsvc.Result
err = client.Call("Upper.Uppercase", args, &reply)
if err != nil {
log.Fatal("stringsvc error:", err)
}
fmt.Printf("Synchronous Call to Upper: %s=%s\n", args.Name, reply.Name)
// Asynchronous call
result := new(stringsvc.Result)
upperCall := client.Go("Upper.Uppercase", args, result, nil)
replyCall := <-upperCall.Done
if replyCall.Error != nil {
log.Fatal("async stringsvc error:", err)
}
fmt.Printf("Asynchronous Call to Upper: %s=%s\n", args.Name, result.Name)
}
Server:
func main() {
upper := new(stringsvc.Upper)
rpc.Register(upper)
rpc.HandleHTTP()
l, e := net.Listen("tcp", ":1234")
if e != nil {
log.Fatal("listen error:", e)
}
http.Serve(l, nil)
}
You have an abundance of options for encoding your messages over the wire. Once again, it's probably best to expect to be providing more than one encoding option for compatibility.
- JSON
- gob
- protocol buffers
- Thrift
- Avro
- more and more and more
Each encoding protocol has pros and cons, and potentially limits your consumers.
Recommendation:
Support JSON and protocol buffers
JSON is the standard for interop today and protocol buffers are the up-and-coming star.
"Protocol buffers are Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages."
Protocol buffers are defined in .proto files, and then client/server code is generated by the protoc compiler, using plugins for different language implementations.
Protocol Buffers use numbered fields in their message definitions, which guarantees backwards compatibility.
- You never modify existing fields
- But you may add more fields to a message
Old clients will continue to work, and new clients can use newer fields. Server-side code is epected to handle both gracefully.
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
}
Messages are strongly typed (unlike JSON), and there is a rich definition language for custom message types, optional fields, enumerations and more.
The only downside is that there is more tooling required to use protocol buffers as a developer.
By themselves, protocol buffers are a great choice for message encoding. There are PB implementations for every major language and many of the minor ones.
But as Billy Mays says... "BUT WAIT!! There's MORE!"
You can have the best of both worlds by using GRPC.
GRPC is a "high performance open source universal RPC framework".
It uses protocol buffers to represent your messages and service definitions.
GRPC also has a variety of plugins, including one that exposes your GRPC services as JSON/REST style services. So you can have one source of truth for your service definitions and support fast RPC using GRPC or legacy systems with JSON/REST.
Open $GOPATH/src/google.golang.org/grpc/examples/helloworld in two different terminals.
Inspect the source code for the client and the server.
Run the server, then from another terminal run the client. (Use the same machine for both.)