Skip to content

The model

Myxo separates what an application does from where the parts run. Code defines services and their interfaces. A layout places them. The runtime connects the two when a process starts.

The starting point in the literature is Ghemawat et al., Towards Modern Development of Cloud Applications (HotOS 2023).

Services

A service is a subclass of myxo.Service. It has a name, a stage that orders the start within a process, and a configuration from the layout. It may hold resources while it runs, keep data of its own, and offer an interface.

A service with no interface still does useful work: web in the Quick Start serves HTTP, and a consumer of events only reacts to them.

Interfaces and messages

An interface is a Protocol of async methods, each taking one message and returning one, or None. Messages are frozen msgspec structs whose fields are immutable all the way down.

Placement stays free because of that constraint. A message that can't change can't be shared by accident between two services in one process, so code that works in one process keeps working across a network. The runtime refuses to start a service whose messages break the rule, and myxo check reports it earlier.

A service imports another only through its interface module, which holds messages and a Protocol and no implementation. myxo check reports an import that bypasses it from another package. A process can then call a service without importing the service's code or dependencies.

Clients

runtime.client(GreeterAPI) returns a client for the service that implements the interface, wherever the layout puts it:

The callee is The call is
in the same process a method call, with a timeout
in another process an HTTP POST of JSON, over TCP or a Unix socket

Both raise the same errors and observe the same call_timeout.

Layouts

A layout is a TOML file that says which process runs which service, and gives each service its configuration. Every string in it may read the environment, so one file serves every installation:

database = "${DATABASE_URL:-sqlite:///guestbook.db}"

[processes.web]
services = ["web", "book"]
url = "${MYXO_URL_WEB:-unix:run/web.sock}"

[processes.worker]
services = ["moderation"]

[services.book]
database = "${DATABASE_URL:-sqlite:///guestbook.db}"

The framework's services keep their tables in the top-level database. A service's own database setting names the data it owns. They must be the same database for a service that publishes events, since an event commits in the service's transaction.

Processes and the runtime

myxo serve LAYOUT PROCESS starts one process. The runtime then:

  1. imports the services this process runs, and no others;
  2. checks their interfaces and messages;
  3. starts them stage by stage, each once the previous stage is ready;
  4. opens the process's listener last, if it has a url, so that a process accepting connections has its services ready;
  5. supervises everything until it receives SIGTERM or Ctrl-C.

A process's log shows these as stages: database first, then the services, then the consumers, workers and schedules (events:GROUP, jobs:TYPE:N, schedule:NAME), and the listener, rpc, last.

Two levels of supervision

Inside a process, the runtime restarts a failed service, one at a time, with a backoff. Around the process, something else restarts it if it dies: myxo up on one box, systemd, Hop3 or Kubernetes on a platform. The failures page gives the details.

Discovery

The runtime finds services through the myxo.services entry points of the installed packages, and reads their interfaces from myxo.apis. Nothing registers itself as a side effect of an import. A process that doesn't run a heavy service doesn't load the libraries it needs. An image for the other processes can leave them out.

Further