Step 12. A small service
Why#
Most OxL programs are services: a set of operations that other programs call. Usually this means choosing a framework, describing the message schema, writing a client, configuring routing, node discovery and state persistence. In OxL all of this is part of the language:
serviceis the contract: a list ofrpcmethods, their parameters, results and errors;implis the implementation of the contract;actoris state;sharded bymeans one instance per key, spread over nodes;clustersays which roles serve which services and host which actors.
The call Inventory.reserve("pen", 2) looks the same whether the implementation lives in this process or on another node. A
service can therefore be written and tested in one process first and then spread over a cluster without changing the calling code.
Example#
A warehouse: reserving goods. The state (how many are left) is kept by a sharded actor, one per article; the service is the entrance from outside.
@error data OutOfStock { sku: str; left: i32 }
data Item { sku: str; qty: i32 }
actor Shelf sharded by sku: str { // one actor per sku; the state lives in the actor
var left: i32 = 5
msg take(n: i32) -> Item | OutOfStock {
if left < n { fail OutOfStock{ sku: sku, left: left } }
left -= n
Item{ sku: sku, qty: n }
}
msg count() -> i32 { left }
}
service Inventory { // the contract: what is visible from outside
@public rpc reserve(sku: str, n: i32) -> Item | OutOfStock
@public rpc stock(sku: str) -> i32
}
impl Inventory {
rpc reserve(sku, n) { Shelf(sku).take(n)? } // the service delegates to the actor
rpc stock(sku) { Shelf(sku).count()? }
}
cluster shop {
role all { serves Inventory; hosts Shelf } // the node serves the service and hosts the actors
}
fn main() io, net {
for n in [2, 2, 2] {
match Inventory.reserve("pen", n) {
Item i => print("reserved {i.qty} {i.sku}")
OutOfStock e => print("out of stock: {e.left} {e.sku} left")
NetError => print("network")
}
}
match Inventory.stock("pen") {
i32 n => print("left {n}")
NetError => print("network")
}
}reserved 2 pen reserved 2 pen out of stock: 1 pen left left 1
Walkthrough#
actor Shelf sharded by sku: str- the shard key is declared in the header and is available in the body as an immutable fieldsku. In record literals it has to be written in full:Item{ sku: sku, … }.msg take(n: i32) -> Item | OutOfStock- an actor message with a typed result; the error is part of the type.service Inventorydescribes the methods;@publicopens them to the outside (access is closed by default, §13.4).impl Inventory { rpc reserve(sku, n) { Shelf(sku).take(n)? } }- parameter types come from the contract;Shelf(sku)is a reference to the actor by key. The result of an actor call containsNetError(the actor may be on another node), so?passes it on too: in the contract ofreserveyou need not writeNetError- it is always added.cluster shop { role all { serves Inventory; hosts Shelf } }- the single roleallserves the service and hosts the actors. In a cluster of many nodes you would split the roles:apiserves the services,storagehosts the actors.- In
mainthe result is taken apart by all types:Item,OutOfStockandNetError. - The state survived the calls: after two reservations 1 is left, the third request got
OutOfStock.varfields of an actor are saved to the journal before the answer is sent, so the state survives a restart (§12.5).
Common mistakes#
Calling a method that is not in the contract - unknown-method:
service Users {
@public rpc count() -> i32
}
impl Users {
rpc count() { 2 }
}
fn main() io, net {
let n = Users.total()
}unknown-method
If a program has a cluster and a service is assigned to no role (serves …), the build stops with
service-not-served - otherwise nobody would start such a service.
Remember#
service= contract,impl= implementation,actor= state,cluster= placement.- A call does not know where the implementation lives; network errors are always visible in the result type.
- Start with one process; spread over nodes when needed.
What next#
- The tour: sagas, storage, cluster, deployment.
- The complete shop example - a distributed shop.
- The cookbook - dozens of ready recipes.