Service
Function pools, promoted to network services — typed methods, a universal dynamic client, Kibo-generated typed proxies.
What is a Viper C++ Service?
A Service is a Viper C++ Runtime container that exposes typed business logic over a network socket. It composes DSM definitions and one or more function pools — stateless or stateful — into a single immutable bundle, then serves them through Viper C++'s RPC layer with automatic marshalling. Clients connect, discover the typed surface from runtime metadata, and call typed methods.
Stateful pool methods read and write through an abstract state interface — the Service mechanism has no opinion on what backs it. It does not persist anything and does not assume any storage tier ; stateful pools work over any backend the client provides (in-memory, plain database, or any other receptacle).
shape + pool signatures
typed code
runtime, metadata, RPC
typed network surface
The Kibo step uses the kibo-template-viper pack — the template
that produces the typed surface of the
Dual Reality
(typed Python wrappers, typed C++ proxies). Kibo itself is template-agnostic.
Built on DSM-defined types, Kibo-generated bridges, and Viper's Metadata Everywhere ; the surface a Service exposes is typed, network-callable business logic.
Anatomy of a Service
A Service is not a separate stack — it is one more per-app surface on top of the shared code base a product already ships through its UI shells. Only layer 1 is per-app ; everything below is the same code, written and maintained once.
Viper C++ Service
1 per-app surface · 5 shared layers
FunctionPool) or stateful (AttachmentFunctionPool) A Service is one more layer-1 surface on a shared core — same anatomy as the Commit Application Model, with a socket on top instead of a UI.
Two pool types
Two flavours of typed pool, declared in DSM and exposed by the Service.
FunctionPool
stateless · pure
Groups stateless functions executed on the server. Pure inputs to outputs, no access to storage, no side effects. Type-safe through the generated prototype.
function_pool Tools {...} {
int64 add(int64 a, int64 b);
Vector3 addVector(Vector3 a, Vector3 b);
}; AttachmentFunctionPool
stateful · over an abstract state interface
Groups functions that operate over a state interface passed
at call time by the client. mutable methods are
explicitly marked. Backend-neutral by construction.
attachment_function_pool PlayerModel {...} {
mutable key<Player> create(string nickname, Level level);
optional<key<Player>> has_player(string nickname);
}; Two paths to a service — Dual Reality
Same RPC layer underneath, two ways to consume it. The split is the Dual Reality pattern : an adapter between Viper's dynamic runtime (strongly typed via its metadata catalog) and the static API world where developers and IDEs are most productive. Both sides are typed ; the static surface adds editor ergonomics, not type safety.
Kibo-generated typed proxies
For IDE autocompletion and compile-time type checking, Kibo emits per-pool
typed proxies — function_pool_remotes.py and
attachment_function_pool_remotes.py in Python,
*_FunctionPoolRemotes.hpp in C++ :
from service.function_pool_remotes import Tools
TOOLS = Tools(service_remote)
if TOOLS.is_available():
result = TOOLS.add(32, 10) # typed, autocompleted The typed proxies are thin wrappers over the same dynamic dispatch — both paths are always available, the application picks per call site.
Generic universal client
ServiceRemote.connect(...) returns a handle that introspects
the service's pools at connect time. Calls dispatch through a runtime
dictionary :
from dsviper import Definitions, ServiceRemote
defs = Definitions()
s = ServiceRemote.connect("localhost", "54328", defs)
result = s.function_pool_funcs("Tools")["add"](32, 10) One client mechanism for any service, ever. No per-service
module, no compile-time schema, no codegen to install. The five-line scaffold
ships with dsviper-tools as service_client.py.
Remote-Local — business on server, mutations on client
For stateful pools, the protocol does something unusual : the business logic
executes on the server, but every state mutation lands in the client's
local AttachmentMutating. The roles
invert during execution.
PM.create(mutating, "shadow", BEGINNER)CallAttachmentMutatingUpdate server asks client to mutate ReturnVoidReturnValue (final result)
On the server, the AttachmentMutating the pool method receives is an
AttachmentMutatingRemote proxy that forwards every read and write
back to the client — fifteen callback packet types cover the surface. The
business code never knows it is talking to a remote state.
Safety by isolation
The Remote-Local pattern looks dangerous — the server sends commands that mutate
client state. The design is inherently safe : the server has no handle on the
client's authoritative state. Mutations land in an AttachmentMutating
context owned by the client, isolated from the model the client treats as ground
truth. The client alone decides whether — and when — to integrate that context
back into its authoritative state.
| Failure mode | Effect on the client's authoritative state |
|---|---|
| Network timeout | Mutation context abandoned, never integrated |
| Server exception | Mutation context abandoned, never integrated |
| Server sends invalid data | Client may discard the context before integration |
| Partial mutations | Context never integrated as a whole |
The server only manipulates a remote-mutating proxy ; it has no view of the client's authoritative state and could not write to it if it wanted to. Structural integrity at the protocol boundary is guaranteed by this isolation. Semantic integrity under multi-author reduction is a separate concern of the commit layer — see the Dual-Layer Contract.
Promote your business logic to a service
The Service Architecture page covers the full mechanism — pool types, server side, both client paths, the Remote-Local protocol, and the safety story end to end.