The microservice interface section (srv)

The field srv is used to describe the connectivity of a component, that is, the set of channels it exposes, or, as we refer to in Kumori Platform, its microservice interface.

A microservice interface consists of a set of client channels and a set of server channels. A client channel represents a dependency on some other microservice, whereas a server channel represents a functionality provided by the component through an endpoint.

The structure of the srv section is defined by this CUE code

srv: {
  client: [string]: #Channel
  server: [string]: #Server
}

Client channels are the fields of the structure introduced in the client field of the srv section. Server channels are the fields of the structure introduced by the server field of the srv section.

Client channels must adhere to the definition #Channel, which introduces the field protocol, to describe the channel’s protocol.

#Channel: {
  protocol: "udp" | "tcp" | *"http" | "grpc"
}

As seen in the definition, only four generic protocols are allowed. The purpose is to help developers/integrators by verifying compatibility at some level of connected channels. Currently, no verification is carried out, and a very basic ad-hoc verification is planned for the near future.

Note that in the absence of a protocol specification, the spec assumes "http" by default.

#Server: {
  #Channel
  port: uint16 | *80
}

Server channel specifications add an extra field to client channel specifications: the port number, which, by default is 80.

The following is an example of a srv section:

srv: {
  server: mainfunc: {
    protocol: "http"
    port: 8080
  }
  client: maindependency: {
    protocol: "http"
  }
}

The example defines a server channel named mainfunc, using protocol "http", operating at port 8080. It also defines a client channel, named maindependency, using protocol "http".

Duplex channels

In addition to client and server channels, a component’s microservice section can also define a duplex channel. Conceptually, duplex channels code both a client and a server channel. They are designed to model endpoints used to initiate requests as well as serve them. Duplex channels are useful in scenarios where a group of instances must carry out complex coordination protocols (e.g., consensus), and each one of those instances plays both a "client" and a "server" role.

Their definition follows the same pattern as that for the other types of channels

srv: {
  duplex: group: {
    protocol: "tcp"
    port: 8080
  }
}

Notice that duplex channels must also provide a port number (where the component binds to serve arriving requests)

Service discovery and client channels

Service discovery is the method by which a client can find the server endpoint it needs to initiate a request.

In Kumori Platform service discovery uses DNS resolution and client channels in a very straightforward way, according to the following rules:

  1. Every service endpoint is represented as a dependency channel, that is, as a client channel.

  2. A service endpoint may point to multiple versions of a dependency. Each such version is tagged by a number in the range 0…​N, where N is the number of versions available.

    • Notice that tag 0 always exists, and represents the only available version when only one version exists.

  3. Each of the available service endpoints themselves can be reached using the client channel name, pre-pended with the tag number, as the name of the node providing the service. E.g., to discover the service behind a client channel chn, for its version tagged with number 0, a component should resolve the name 0.chn

  4. The ports of those nodes providing the service can be found requesting the SRV records linked to the client channel name.

  5. When a client channel is linked to a full connector, the set of server endpoints is returned when requesting resolution of the channel name.

  6. When a client channel is linked to a lb connector, DNS resolution returns a load balanced address. However, resolution of the special subdomain set.<tag>.<channel> returns the full set of configured endpoints behind the lb connector. This is useful when load balancing is to be carried out from the client side.

  7. The set of configured, but ready endpoints can be retrieved resolving ready.<tag>.chn.

Duplex channels need also resolution given their dual nature. Duplex channels are always connected to by full connectors, and the semantics should be that all connected instances belong to the same version of the "dependency" (e.g., all speak the same protocol).

This difference with client channels means that resolving a duplex channel make sense only to discover the set of duplex endpoints connected to the same full connector belonging to the same version of the component. Thus resolution of a duplex channel should be carried out without the use of tags.

In sum, service discovery is carried out by means of DNS name resolution, being transparent to the code of a component. For client channels, it just needs to use the channel name, pre-pended with the tag for the version it needs to use, as a node name to reach the service/version it needs to satisfy the dependency.

For duplex channels it just needs to resolve the name of the channel to retrieve all the instances connected to the full connector associated with the duplex channel.

Server channels, represent endpoints that a component’s code can bind to, and they should not be resolved.
The dual nature of duplex channels makes them also amenable for a component to bind to.
Service discovery can proceed resolving either A records or SRV records. Resolution for A records only returns the IP addresses of the endpoints. The port number to be used should be understood by the client code by some other means. Resolution of SRV records, however, will also return the port number for each instance.
self is a reserved name for channels. It cannot be used to name a user defined channel but it can be resolved, returning the IP of the main network interface for the component instance.