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)