Skip to content

Commit

Permalink
docs: document cluster client
Browse files Browse the repository at this point in the history
  • Loading branch information
Tochemey committed Jun 22, 2024
1 parent f0a5336 commit cf1431a
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 29 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ The project adheres to [Semantic Versioning](https://semver.org) and [Convention
- [Logging](#logging)
- [Testkit](#testkit)
- [API](#api)
- [Client](#client)
- [Clustering](#clustering)
- [Operations Guide](#operations-guide)
- [Redeployment](#redeployment)
Expand Down Expand Up @@ -342,6 +343,28 @@ The API interface helps interact with a Go-Akt actor system as kind of client. T
- `RemoteStop`: to stop an actor on a remote machine
- `RemoteSpawn`: to start an actor on a remote machine. The given actor implementation must be registered using the [`Register`](./actors/actor_system.go) method of the actor system on the remote machine for this call to succeed.

## Client

The Go-Akt client facilitates interaction with a specified Go-Akt cluster, contingent upon the activation of cluster mode.
The client operates without knowledge of the specific node within the cluster that will process the request.
This feature is particularly beneficial when interfacing with a Go-Akt cluster from an external system.
Go-Akt client is equipped with a mini load-balancer that helps route requests to the appropriate node.

### Balancer strategies

- [Round Robin](./client/round_robin.go) - a given node is chosen using the round-robin strategy
- [Random](./client/random.go) - a given node is chosen randomly
- [Least Load](./client/least_load.go) - the node with the least number of actors is chosen

### Features:

- `Kinds` - to list all the actor kinds in the cluster
- `Spawn` - to spawn an actor in the cluster
- `Kill` - to kill/stop an actor in the cluster
- `Ask` - to send a message to a given actor in the cluster and expect a response
- `Tell` - to send a fire-forget message to a given actor in the cluster
- `Whereis` - to locate and get the address of a given actor

## Clustering

The cluster engine depends upon the [discovery](./discovery/provider.go) mechanism to find other nodes in the cluster.
Expand Down
54 changes: 25 additions & 29 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,66 +165,62 @@ func (x *Client) Kinds(ctx context.Context) ([]string, error) {
// The actor name will be generated and returned when the request is successful
func (x *Client) Spawn(ctx context.Context, actor *Actor) (err error) {
x.locker.Lock()
defer x.locker.Unlock()
host, port := x.getNextRemotingHostAndPort()
return actors.RemoteSpawn(ctx, host, port, actor.Name(), actor.Kind())
remoteHost, remotePort := x.getNextRemotingHostAndPort()
x.locker.Unlock()
return actors.RemoteSpawn(ctx, remoteHost, remotePort, actor.Name(), actor.Kind())
}

// Tell sends a message to a given actor provided the actor name.
// If the given actor does not exist it will be created automatically when
// Client mode is enabled
func (x *Client) Tell(ctx context.Context, actor *Actor, message proto.Message) error {
x.locker.Lock()
defer x.locker.Unlock()

remoteHost, remotePort := x.getNextRemotingHostAndPort()
// lookup the actor address
address, err := actors.RemoteLookup(ctx, remoteHost, remotePort, actor.Name())
address, err := x.Whereis(ctx, actor)
if err != nil {
return err
}

// no address found
if address == nil || proto.Equal(address, new(goaktpb.Address)) {
return actors.ErrActorNotFound(actor.Name())
}

return actors.RemoteTell(ctx, address, message)
}

// Ask sends a message to a given actor provided the actor name and expects a response.
// If the given actor does not exist it will be created automatically when
// Client mode is enabled. This will block until a response is received or timed out.
func (x *Client) Ask(ctx context.Context, actor *Actor, message proto.Message) (reply proto.Message, err error) {
x.locker.Lock()
defer x.locker.Unlock()

remoteHost, remotePort := x.getNextRemotingHostAndPort()
// lookup the actor address
address, err := actors.RemoteLookup(ctx, remoteHost, remotePort, actor.Name())
address, err := x.Whereis(ctx, actor)
if err != nil {
return nil, err
}

// no address found
if address == nil || proto.Equal(address, new(goaktpb.Address)) {
return nil, actors.ErrActorNotFound(actor.Name())
}

response, err := actors.RemoteAsk(ctx, address, message)
if err != nil {
return nil, err
}

return response.UnmarshalNew()
}

// Kill kills a given actor in the Client
func (x *Client) Kill(ctx context.Context, actor *Actor) error {
x.locker.Lock()
defer x.locker.Unlock()
host, port := x.getNextRemotingHostAndPort()
return actors.RemoteStop(ctx, host, port, actor.Name())
remoteHost, remotePort := x.getNextRemotingHostAndPort()
x.locker.Unlock()
return actors.RemoteStop(ctx, remoteHost, remotePort, actor.Name())
}

// Whereis finds and returns the address of a given actor
func (x *Client) Whereis(ctx context.Context, actor *Actor) (*goaktpb.Address, error) {
x.locker.Lock()
remoteHost, remotePort := x.getNextRemotingHostAndPort()
x.locker.Unlock()
// lookup the actor address
address, err := actors.RemoteLookup(ctx, remoteHost, remotePort, actor.Name())
if err != nil {
return nil, err
}
// no address found
if address == nil || proto.Equal(address, new(goaktpb.Address)) {
return nil, actors.ErrActorNotFound(actor.Name())
}
return address, nil
}

// getNextRemotingHostAndPort returns the next node host and port
Expand Down

0 comments on commit cf1431a

Please sign in to comment.