Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Architecture

This page answers the question “how do you assemble a Yuneta system above one gobj?”. Design Principles gives the reason for each separate decision. The Typed-Graph Model gives the model that holds the decisions together. This page describes the result: three layers, one message mechanism, and one word — role — that keeps the same meaning in all three.

Three layers of a Yuneta system. At the bottom the compiled behavior layer, where the gobjs of a yuno exchange events addressed by pointer and two yunos exchange ievents addressed by service name. In the middle the persisted information layer, treedb topics linked by hooks and fkeys, written with CRUDLU and answering with EV_TREEDB_NODE events. At the top the dynamic layer of services and roles - realm, service, role, user - which is the same set of linked nodes read as the structure the end user sees.

Three layers, and one message mechanism through all of them. The behavior layer is compiled: a gobj addresses another gobj by pointer, and a yuno addresses another yuno by service name. The information layer is persisted and changes while the yuno runs. The services and roles layer is not a third store — it is the same linked nodes, read as the structure the end user works with.

A class is a role

A gclass header exposes two things: GOBJ_DECLARE_GCLASS(C_FOO) and register_c_foo(). It publishes no structure. The private data of an instance stays behind an opaque pointer. Every interaction goes through the five public mechanisms: attributes, commands, events, local methods and statistics.

A class with no visible structure keeps one identity only: what it does for the other objects. That identity is a role.

Most object systems cannot make this claim. A C++ or a Java class is also a data type, so part of its identity is its structure. Yuneta hides the structure on purpose, and the class becomes the role.

The framework already uses the word one level above. A yuno has a role and a name, and the documentation writes the pair as role^name. The role is the class of the process. The name is the instance. A gclass and a gobj_name are the same pair one level lower.

The rule

Name a gclass for the role it plays.

Apply the rule in reverse to test a design. When you cannot name a gclass by its role, the gclass has no role. It is a bag of code inside a gobj wrapper, or it is two roles in one class. This is the test the coding rules already apply to functions: a function whose honest name tells the reader nothing is a function to delete, and not a function to rename. The suffixes -Manager, -Helper and -Handler are the classic symptom, because they name the absence of a role.

Examples from the current tree:

GClassWhat the name does
C_PROT_HTTP_CL, C_PROT_HTTP_SR, C_TCP_S, C_PROT_MODBUS_MThe suffix carries the role. A protocol name alone does not say client, server or master.
C_IDP_KEYCLOAK, C_DBA_POSTGRESRole first, implementation second. This is the correct form of the preference for the domain word over the vendor name. The vendor name qualifies a role, and never replaces one.
C_TIMER0, C_RESOURCE2The 0 and the 2 name an implementation and a version. The reader learns nothing about the part the gobj plays, and C_TIMER0 is a repeated source of mistakes for that reason.

Two levels of role

The rule has a limit, and the limit is useful.

C_TCP does not say what this socket does here. The instance name says it. Push the situational role into the class name and you get one gclass for each place it is used. Keep the two levels apart and one gclass serves every place.

One mechanism, two scopes

Yuneta has one way for objects to talk, and it is the message. What changes when a message leaves the process is not the mechanism. It is the address.

One rule follows from this: only a named service can be the source or the destination of an inter-yuno message. A pointer does not travel over a websocket. A name does. A routed view, a pure child, or any unnamed gobj can never hold one end of an inter-yuno conversation. The log message “gobj service not found” reports a violation of this rule. Correct the sender.

ScopeStructureMessageAddress
Inside a yunoa tree of gobjseventpointer
Between yunosa graph of treedb nodesieventservice name

The two rows repeat one shape at two scales. That repetition is deliberate. It is the fractal consistency the Inspiration page describes in a non-technical register.

TreeDB is the structure, not the channel

A frequent first reading of the table above is that treedb is how yunos talk at the upper level. It is not. TreeDB holds the structure: what exists, how it relates to the rest, and what configuration it carries. Messages remain the only channel at every scale.

The agent is the clearest example. Its yunos, binaries, configurations and realms are nodes of a treedb. The topology of a node is that graph.

Two consequences matter in practice.

The store is a meeting point. When two yunos cannot address each other directly, the shared treedb is where they meet. This is a deliberate pattern, and not a workaround.

The store sends messages of its own. A treedb publishes EV_TREEDB_NODE_CREATED, EV_TREEDB_NODE_UPDATED, EV_TREEDB_NODE_DELETED, EV_TREEDB_NODE_LINKED and EV_TREEDB_NODE_UNLINKED. A change of shared state returns through the one channel that exists. This is also why Yuneta refuses polling. The producer publishes, and the consumer subscribes.

The dynamic layer: services and roles

Everything above this section is compiled. A gclass registers at start-up. The shape of the gobj tree lives in main.c. You cannot add a role to that tree while the process runs.

On the linked topics of a treedb, Yuneta builds a second structure of services and roles, and that structure is data. It is nodes and links. You change it with CRUDLU while the system runs. No rebuild. No restart.

This third layer is not a third store. It is a reading of the second one. The same linked nodes that an operator reads as topics and hooks, an end user reads as the services available and the role held over them. An end user never sees a gclass.

Why the two layers stay apart

Merge the two and every new customer needs a new build. Keep them apart and one binary serves every installation, because the part that varies is data.

Three examples already in the tree:

The bridge between the layers

A node in the dynamic layer names a role. The compiled layer implements that role. The rule at the top of this page is what lets the two meet: role keeps one meaning on both sides, so a name that an operator writes into a treedb node resolves to a gclass that a developer registered in C.

The discipline the dynamic layer needs

A declarative JSON structure is sugar over a runtime API, and that API must stay available while the yuno runs. When a feature must change a structure that the configuration describes, and no API exists for the change, write the API. Do not work around the static configuration.

A layer you can change only at start-up is a build step with a different file format. It gives the end user nothing.

The three layers

LayerMade ofChanged byRead by
Behaviorgclasses, gobjs, state machinesa rebuildthe developer
Informationtopics, nodes, hooks, fkeysCRUDLU, while runningthe operator
Services and rolesthe same linked nodes, read as a structureCRUDLU, while runningthe end user

One mechanism crosses all three, and it is the message. One word crosses all three, and it is role. The first keeps the system observable at every scale. The second keeps the vocabulary of the end user and the vocabulary of the C developer in agreement.

Where to go next