1 Service definition
A service orchestrates one or more artifacts (components or other services), mapping its own public interface to the interfaces of the artifacts it uses. Services are the primary mechanism for composing complex application topologies, enabling abstraction and reuse.
Services are declared using the service keyword and, like components, separate their interface from their implementation.
As with components, a service definition is split into two files:
- Interface (
*.h.kumori): Declares the service’s external contract (srv,config,resource). - Implementation (
*.kumori): Provides the implementation by definingroleandconnectblocks.
Both files must share the same service name and belong to the same package.
1.1 Interface
The interface of a service might define channels, configuration and resources a service publicly exposes.
A service’s interface is declared following its type alias definition:
alias ServiceInterface struct {
srv? Links
config? struct open[]
resource? struct open[Resource]
}
In plain words, the previous definition accepts a service interface composed of an optional set of service Links under the srv keyword, an optional set of configuration properties that accept any definition under the config keyword and an optional set of resource declarations, whose type is restricted to Resources type.
A service interface is defined with the service keyword inside a .h.kumori file.
service NAME { ... }
Given the previous restrictions, this would be a full example of a service interface:
// <your-service>.h.kumori
package service
import "kumori"
service MyService {
srv {
server {
server1 "tcp"
server2 "http"
server3 {
protocol "tcp"
port 3333
}
server4 {
protocol "http"
port 3334
}
}
client {
client1 "tcp"
client2 "http"
}
duplex {
duplex1 {
protocol "tcp"
port 6666
}
}
}
config {
value: string
anotherValue: number
}
resource {
volumeOne: kumori.Volume
secret: kumori.Secret
cert: kumori.Certificate
}
}
1.1.1 Links
The Links type alias defines how a component exposes its services to the outside world through channels. A channel can be either a client, a server or a duplex channel. For a deep understanding of links, please refer to the Links documentation.
alias Links struct {
client? struct open[Client]
server? struct open[Server]
duplex? struct open[Server]
}
alias Channel "udp" | "tcp" | "http" | "grpc"
alias Client Channel
alias Server Channel | struct { protocol channel, port number }
1.1.2 Resources
The Resource type alias defines the resources that a component can declare in its interface. A resource can be a volume, a certificate authority (CA), a certificate, a secret, a domain or a port. For a deep understanding of resources, please refer to the Resources documentation.
package kumori
library
alias Resource Volume | CA | Certificate | Secret | Domain | Port
type CA string
type Certificate string
type Secret string
type Domain string
type Port string
alias Volume Registered | InlineVolume | DeprecatedVolume
alias InlineVolume NonReplicated | Persisted | Volatile
type Registered string
type NonReplicated StorageSized
type Persisted StorageSized
type Volatile StorageSized
1.2 Implementation
The implementation of a service defines its internal structure using role and connect blocks.
A service is defined with the service keyword inside a .kumori file.
service NAME { ... }
After the interface has been defined, a service must be declared with the same identifier as the one used in the interface. For example, if in <your-service>.h.kumori you defined one service as service MyService, then your <your-service>.kumori file should define the same service with MyService name.
A service is defined following its type alias definition:
alias service struct {
srv? Links
config? struct open[]
resource? struct open[Resource]
role? struct open[Role]
connect? struct open[Connection]
}
As you might have noticed, at the beginning of a service implementation type definition, there are three fields exactly the same as in the ServiceInterface type alias. You could declare everything inside a Service implementation rather than having it separated in its interface since all the fields are merged while a service is being processed. However, when a service is defined with an implementation, it is mandatory to declare its interface as well (even if it is empty).
A service allows two additional fields: role and connect.
1.2.1 Roles
The Role type alias defines the roles that a service orchestrates. A role can be either a component, another service or a built-in artifact. In a service, the same component could be run playing multiple roles. Think, for instance of a PostgreSQL database. In a service we could have different PostgreSQL database microservices, each one with its own persistent volume, serving different purposes, probably configured very differently one from each other.
Kumori’s service model captures this variability through its role concept. A role is just a specification of how a microservice based on some artifact must be deployed.
Roles are defined within a service using the role keyword, being able to declare multiple roles inside the same block, each one with a different name. This is the type alias definition:
alias Role struct {
artifact any // TODO: service, component or builtin
config? struct open[]
resource? struct open[Resource]
meta? struct open[]
}
In the end, each role contains an artifact field that accepts any type of artifact, an optional config field that accepts any configuration definition and an optional resource field that accepts only resource definitions.
[!NOTE] The optional
metafield can contain arbitrary amounts of information that is made available to deployments that link to this role, be it within the same solution or from some other solution.
Given the previous definition, a role declaration block would look like this:
import "your-module/component"
[...]
role {
webAppRole {
artifact component.WebApp
config {
replicas 3
}
}
databaseRole {
artifact service.DatabaseService
resource {
dbVolume interface.resource.volume
}
}
}
1.2.2 Connections
The Connection type alias defines the connections between roles within a service. A connection can be either a LoadBalancer or a FullConnector. For a deep understanding of connections, please refer to the Connections documentation.
Connections are defined within a service using the connect keyword, being able to declare multiple connections inside the same block, each one with a different name. This is the type alias definition:
alias Connection LoadBalancer | FullConnector
type LoadBalancer struct {
from struct {
target string
channel string
}
to struct {
target string
channel string
}
meta? any
}
type FullConnector struct {
target string
channel string
meta? any
}
Given the previous definition, a connect declaration block would look like this:
connect {
webAppToDatabase kumori.LoadBalancer({
from {
target "webAppRole"
channel "tcp"
}
to {
target "databaseRole"
channel "tcp"
}
})
webAppFullConnector kumori.FullConnector({
target "webAppRole"
channel "http"
})
}
The self keyword can be used to reference the service’s own interface when defining connections.
```connect { externalToWebApp kumori.LoadBalancer({ from { target “self” channel “http” }
to {
target "webAppRole"
channel "http"
}
})
} ```
The Service Examples section contains several complete service definitions showcasing different configurations and use cases.