github.com/godevsig/adaptiveservice@v0.9.23/README.md (about) 1 # Adaptiveservice 2 3 Adaptiveservice is a message oriented micro service framework. 4 5 [adaptiveservice go doc](https://pkg.go.dev/github.com/godevsig/adaptiveservice) 6 7 # Hello example 8 9 ## Demo 10 11 ``` 12 $ cd examples/hello/ 13 14 # start server, waiting requests from clients, ctrl+c to exit 15 $ go run server/helloserver.go 16 17 # start client in another terminal 18 # client discovers the server then sends request and prints the reply 19 $ go run client/helloclient.go 20 I am hello server, John 21 ``` 22 23 We can also start client before server to get the same result, client will wait in 24 discovering stage before server successfully started into service ready. 25 26 ## Client side programing 27 28 The client imports the server's messages definitions: 29 30 ``` 31 import msg "github.com/godevsig/adaptiveservice/examples/hello/message" 32 ``` 33 34 Then we new a client which discovers the service identified by `{"example", "hello"}` 35 where "example" is the publisher of the service, "hello" is the name of the service. 36 When successfully discovered, a none-nil connection towards that service is established. 37 38 ``` 39 c := as.NewClient() 40 41 conn := <-c.Discover("example", "hello") 42 if conn == nil { 43 fmt.Println(as.ErrServiceNotFound("example", "hello")) 44 return 45 } 46 defer conn.Close() 47 ``` 48 49 Then we can use the connection to send our request `msg.HelloRequest`, asking 50 "I am John, who are you?" and wait for the reply `msg.HelloReply`, 51 then we print the `reply.Answer`. 52 53 ``` 54 request := msg.HelloRequest{Who: "John", Question: "who are you"} 55 var reply msg.HelloReply 56 if err := conn.SendRecv(request, &reply); err != nil { 57 fmt.Println(err) 58 return 59 } 60 fmt.Println(reply.Answer) 61 ``` 62 63 Here the client knows the protocol with the sersver that `msg.HelloReply` is the reply 64 type of `msg.HelloRequest`. The knowledge of message protocol is "business logic", 65 defined in server side, clients use such knowledge by importing the "message" package 66 of the server. 67 68 ### Client discovers service by name 69 70 `c.Discover()` discovers the wanted named service in all available scopes, which are: 71 72 - Process scope: client and server run in same process. 73 This scope uses go channel as transport layer, avoids data copy and serialization 74 cost thus can improve performance significantly. See [gshellos](https://github.com/godevsig/gshellos) 75 to make client and server run in process scope. 76 - OS scope: client and server run in same OS, which can be same host OS, or same VM, or same container. 77 This scope uses unix socket as transport layer. 78 - LAN scope: client and server run in same LAN, using LAN broadcasting to discover services. 79 - WAN scope: This scope requires a "root registry" presents in one "public" node in the network 80 that client and server can access. The root registry also setups proxy when necessary for the server 81 if the server's network is invisible by the client. 82 83 By default the client will wait forever in discovering the wanted service, 84 [Client.SetDiscoverTimeout](https://pkg.go.dev/github.com/godevsig/adaptiveservice#Client.SetDiscoverTimeout) 85 can be used set a timeout to discover. 86 87 `c.Discover()` returns a channel from which user can get connections established to the server. 88 There could be many instances providing the same service in the network. 89 90 The connection can be multiplexed to get streams that provides independent context to transfer messages. 91 See also 92 [gshellos usage](https://github.com/godevsig/gshellos/blob/master/docs/adaptiveservice.md#client-side-multiplexed-connection) 93 94 ## Server side programing 95 96 Server needs to define messages it can handle, and the messages it returns to clients as reply: 97 98 ``` 99 import as "github.com/godevsig/adaptiveservice" 100 101 // HelloRequest is the request from clients 102 type HelloRequest struct { 103 Who string 104 Question string 105 } 106 107 // HelloReply is the reply of HelloRequest to clients 108 type HelloReply struct { 109 Answer string 110 } 111 112 // Handle handles msg. 113 func (msg HelloRequest) Handle(stream as.ContextStream) (reply interface{}) { 114 answer := "I don't know" 115 116 question := strings.ToLower(msg.Question) 117 switch { 118 case strings.Contains(question, "who are you"): 119 answer = "I am hello server" 120 case strings.Contains(question, "how are you"): 121 answer = "I am good" 122 } 123 return HelloReply{answer + ", " + msg.Who} 124 } 125 126 func init() { 127 as.RegisterType(HelloRequest{}) 128 as.RegisterType(HelloReply{}) 129 } 130 ``` 131 132 Here `HelloRequest` is a [known message](https://pkg.go.dev/github.com/godevsig/adaptiveservice#KnownMessage). 133 A known message has a `Handle` method, which defines how to process the msg sent by clients. 134 In the `init` function, the instances of known message and the reply message are registered to `Adaptiveservice` 135 type system which are used to route the incoming request message(`HelloRequest` here) to its handler. 136 137 - Incoming message are handled concurrently in a goroutine worker pool 138 - The reply returned by the handler will be delivered to the client if it is none-nil. 139 - The reply can also be an error, which will be delivered as err in `Recv(msgPtr) err` of client. 140 - `as.ContextStream` can be optionally used: 141 - Each stream has its own [context](https://pkg.go.dev/github.com/godevsig/adaptiveservice#Context) 142 which can be used to set/get context of the same stream. 143 - Inside the message handler, the server can exchange messages with the client. 144 See [subsequent message](https://github.com/godevsig/gshellos/blob/master/docs/adaptiveservice.md#subsequent-message) 145 146 ### Server publishes service 147 148 The messages are defined in a package, server main package imports `examples/hello/message`. 149 150 ``` 151 import ( 152 "fmt" 153 154 as "github.com/godevsig/adaptiveservice" 155 msg "github.com/godevsig/adaptiveservice/examples/hello/message" 156 ) 157 158 func main() { 159 s := as.NewServer().SetPublisher("example") 160 161 knownMsgs := []as.KnownMessage{msg.HelloRequest{}} 162 if err := s.Publish("hello", knownMsgs); err != nil { 163 fmt.Println(err) 164 return 165 } 166 167 if err := s.Serve(); err != nil { // ctrl+c to exit 168 fmt.Println(err) 169 } 170 } 171 ``` 172 173 By calling `as.NewServer()` followed by `s.Publish`, the server registers the known messages to service 174 `{"example", "hello"}`, then server enters `Serve` to service the requests. 175 176 See also [publish a service](https://github.com/godevsig/gshellos/blob/master/docs/adaptiveservice.md#publish-a-service) 177 178 See also a more useful example that has multiplexed streams and implements PUB/SUB pattern: 179 [echo example](https://github.com/godevsig/adaptiveservice/tree/master/examples/echo) 180 181 ## 3 API to transfer messages 182 183 Messages are server defined structs, or base types in golang. Unlike protobuf that users define .proto files 184 and compile them into langueage source code, `Adaptiveservice` uses plain go file: servers define them, client 185 imports them. We found in this way it is simpler to maintain the code, but of course it only supports native 186 golang. 187 188 - `Send(msg)` and `Recv(msgPtr)`: send or receive "one-way" data 189 - `SendRecv(msg, msgPtr)`: send and wait for reply from peer 190 191 See also [sned recv and sendrecv](https://github.com/godevsig/gshellos/blob/master/docs/adaptiveservice.md#send-recv-and-sendrecv) 192 193 # Block diagram 194 195 ![adaptiveservice architecture](doc/architecture.PNG) 196 197 # Performance 198 199 In [gshellos](https://github.com/godevsig/gshellos) based [grepo](https://github.com/godevsig/grepo), 200 there is a [asbench test](https://github.com/godevsig/grepo/tree/master/benchmark/asbench) that mimics 201 a download test: 202 203 ``` 204 c := as.NewClient(as.WithLogger(lg), as.WithScope(Scope)).SetDiscoverTimeout(3) 205 conn := <-c.Discover(asbench.Publisher, asbench.Service) 206 if conn == nil { 207 return as.ErrServiceNotFound(asbench.Publisher, asbench.Service) 208 } 209 defer conn.Close() 210 211 var running = true 212 213 dld := func() int64 { 214 stream := conn.NewStream() 215 req := asbench.DownloadRequest{Name: "testdld", ID: int32(1), Size: int32(*size)} 216 //fmt.Println("request:", req) 217 var rep []byte 218 var counter int64 219 for running { 220 if err := stream.SendRecv(&req, &rep); err != nil { 221 panic(err) 222 } 223 counter++ 224 } 225 //fmt.Println("reply:", rep) 226 return counter 227 } 228 ``` 229 230 The performance result is: 231 ![adaptiveservice performance](doc/performance.PNG)