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:

  1. Interface (*.h.kumori): Declares the service’s external contract (srv, config, resource).
  2. Implementation (*.kumori): Provides the implementation by defining role and connect blocks.

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.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 meta field 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.