Walkthrough
The three layers, and what a change costs.
A Yuneta system is compiled at the bottom and data at the top. Change the top and count the rebuilds.
Below is one system, drawn as the three layers it is made of. The bottom
band is compiled: its gclasses are registered at start-up and its shape
lives in main.c. The middle band is a treedb, and it changes
while the process runs. The top band is what an end user sees.
The top band and the middle band render the same array of nodes. The read as switch changes the rendering, not the data. That is the claim this page exists to make: the third layer is not a third store.
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. Inside a yuno a gobj addresses another by pointer. Between yunos
the same events travel as ievents, and the address is a
name that c_ievent_srv.c resolves with
gobj_find_service().
One rule follows, and the switch above breaks it on purpose: only a named service can hold either end of an inter-yuno conversation. A pointer does not travel over a websocket. A name does.
TreeDB is the structure, not the channel
A frequent first reading is that treedb is how yunos talk at the upper
level. It is not. TreeDB holds what exists and how it relates. Messages
stay the only channel — which is why every change you made above came
back down as an EV_TREEDB_NODE_* event rather than being
polled for. The producer publishes. The consumer subscribes.
A class is a role
A gclass header exposes two things: GOBJ_DECLARE_GCLASS(C_FOO)
and register_c_foo(). It publishes no structure, and
priv is opaque. A class with no visible structure keeps one
identity only: what it does for the other objects. That identity is a
role — which is why the word survives the trip from a treedb node written
by an operator to a gclass registered by a developer in C.
Read the rule backwards to test a design. A gclass you cannot name by its role has none: it is a bag of code, or two roles fused. Names from the tree, graded:
| Name | Verdict | Why |
|---|---|---|
C_PROT_HTTP_CL · C_TCP_S · C_PROT_MODBUS_M |
says it | The suffix carries the role. A protocol name alone does not say client, server or master. |
C_TCP |
silent | The role is there by default — it is the client — and default is exactly what a reader cannot see. Compare its sibling C_TCP_S. |
C_IDP_KEYCLOAK · C_DBA_POSTGRES |
correct shape | Role first, implementation second. Preferring the domain word to the vendor name does not ban the vendor. It subordinates it. |
C_TIMER0 · C_RESOURCE2 |
no role | The 0 and the 2 name an implementation and a version. Nothing says how the part played differs, and C_TIMER0 is a repeated source of mistakes for that reason. |
C_PEPON · C_TESTON |
no role | They say nothing at all. |
The rule has a limit, and the limit is useful. A gclass
names the generic role: the part the class knows how to play
anywhere. A gobj_name names the situational role:
the part this instance plays in this tree. Push the situational role
into the class name and you get one gclass for each place it is used.
Why the two layers stay apart
The compiled layer states what the software can do. It is a capability, and it is identical in every installation of the same binary. The dynamic layer states what this installation does, and for whom. It is deployment and authorization, and it differs for each customer, each realm and each day.
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. The counter above is the whole argument in one number.
Which is also the discipline the layer demands: 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 the configuration describes and no API exists, write the API. A layer you can change only at start-up is a build step with a different file format.