Entity naming

Table of Contents

1. Summary

An entity has one name, and the layers spell it in a fixed set of ways. The table is the plural with a _tbl suffix, the C++ type is the singular, and every function, trigger, index, policy, message, subject, command and test file derived from it follows a pattern stated here.

The patterns are not a convention someone chose for this page. They are what the code generator emits: 200 archetype pages and 153 tangled templates produce them, and this page is the index of that output. Where the generator does two things, this page says so rather than choosing.

Read Codegen patterns: choosing a model shape before you pick a shape, and this page before you name anything that comes out of it.

2. The base names

Everything below is built from three strings: {product}, {component} and {plural}, usually joined as {product}_{component}_{plural}. A model may override the table name with :tablename:, in which case that string is the base instead.

2.1. The table

A table is "{{product}}_{{component}}_{{plural}}_tbl".

ores_reporting_report_instances_tbl
ores_iam_run_grants_tbl
ores_dq_datasets_tbl

_tbl is on every table and says nothing about the shape: a bi-temporal table and a current-state table both take it.

2.2. The entity and its repository

The C++ type is the singular in snake case. The repository, the collection and the wire entity take the plural.

Singular Plural
file_type file_types
report_instance report_instances
report_definition report_definitions

2.3. Lookups

A lookup names the thing it classifies, and takes _kind or _type.

Suffix Example
_kind report_instance_kind
_type analytic_type, configuration_type, file_type

Undecided. One job, two spellings. Nothing in the generator distinguishes them, so a new lookup may take either until the policy is settled.

2.4. Events

The _event suffix names a fact about an instant: a row written once and never changed. Such an entity states :current_state: and :immutable:, so it has no validity window and no audit tail. Because it has no audit tail, the actor and the time are columns of its own.

ores.iam.auth_event is the worked example: it records who signed in, and carries event_time, account_id and username for that reason.

3. What an entity derives

Every row below is emitted by the generator. sql_name_base is {product}_{component}_{plural} and rls_table_base is the same string with the leading {product}_{component}_ removed.

3.1. SQL

Artefact Pattern
Table {sql_name_base}_tbl
Primary key (tenant_id, {pk}, valid_from, valid_to), tenant first only when has_tenant_in_pk
Exclusion constraint Unnamed exclude using gist on (tenant_id, {pk}, tstzrange(valid_from, valid_to))
Check constraints Unnamed: valid_from < valid_to, one per uuid PK column, one per text PK column
Foreign key {sql_name_base}_{fk_column}_fk, overridable with :constraint_name:
Unique index, composite natural key {index_name_prefix}_{keys}_uniq_idx
Unique index, single natural key {index_name_prefix}_{column}_uniq_idx
Version unique index {index_name_prefix}_version_uniq_idx
Id unique index {index_name_prefix}_id_uniq_idx
Tenant index {index_name_prefix}_tenant_idx
Model-declared index {index_name_prefix}_{index.name}_idx
Insert function {sql_name_base}_insert_fn()
Insert trigger {sql_name_base}_insert_trg
Delete function {sql_name_base}_delete_fn()
Delete trigger {sql_name_base}_delete_trg
Delete rule {sql_name_base}_delete_rule
Cascade-delete function {sql_name_base}_cascade_delete_fn()
Immutable function {sql_name_base}_immutable_fn()
Immutable trigger {sql_name_base}_immutable_trg
Immutable truncate trigger {sql_name_base}_immutable_truncate_trg
Touch-version function {sql_name_base}_touch_version_fn()
Hierarchy function {sql_name_base}_hierarchy_fn()
Notify function {sql_name_base}_notify_fn()
Notify trigger {sql_name_base}_notify_trg
pg_notify channel '{sql_name_base}'
RLS tenant-isolation policy {rls_table_base}_tbl_tenant_isolation_policy
RLS party-isolation policy {rls_table_base}_tbl_party_isolation_policy
Validation function {product}_{component}_validate_{singular}_fn()

Note the RLS exception: rls_table_base deliberately drops the {product}_{component}_ prefix, so an RLS policy is the one SQL family named off the short base.

3.2. C++

Artefact Pattern
Domain type struct {singular} final in ores::{component}::domain
Domain file include/ores.{component_include}/domain/{singular}.hpp
Entity type struct {singular}_entity in ores::{component}::repository
Entity file repository/{singular}_entity.hpp
Table renderer convert_to_table(const std::vector<{singular}>&)
Table IO operator<<(std::ostream&, const std::vector<{singular}>&)
JSON IO operator<<(std::ostream&, const {singular}&)
Mapper class ORES_{COMPONENT_CORE_UPPER}_EXPORT {singular}_mapper
History field mapper render_{singular}_fields(const domain::{singular}&)
Repository class ORES_{COMPONENT_CORE_UPPER}_EXPORT {singular}_repository
Generator generate_synthetic_{singular}() and generate_synthetic_{plural}(n, ...)
Service class ORES_{COMPONENT_CORE_UPPER}_EXPORT {singular}_service
Protocol header include/ores.{component_include}/messaging/{singular}_protocol.hpp
Event type struct entity_event_traits<...::{singular}_event>, subject_prefix = "{component}.v1.{plural}_events"
Event registrar register_{singular}_event_mapping(...)
Sub registrar register_{singular}_handlers(...)
History provider registrar register_{singular}_history_provider(...)
Event cache class {singular}_cache
Event cache registrar warm_and_subscribe_{singular}_cache(...)
HTTP route class class {singular}_routes in ores::http::routes::{component}
Shell command class class {singular}_commands
Operations command class class {singular}_operations_commands

A grouped entity also puts {group_prefix} and {audit_prefix} in front of the member names. A grouped entity that omits :entity_singular_short: or :entity_plural_short: renders an empty segment rather than falling back, so the identifier silently collapses; declare both.

3.3. Protocol messages and subjects

Artefact Pattern
Key, write, change, removal, lookup {singular}_key, {singular}_write, {singular}_change, {singular}_removal, {singular}_lookup
Filter {plural}_filter
Event {singular}_event
Version key, versions filter {facet_stem}_version_key, {facet_stem}_versions_filter
List list_{plural}_request / _response on {component}.v1.{plural}.list
Get get_{singular}_request / _response on {component}.v1.{plural}.get
Get many get_many_{plural}_request / _response on {component}.v1.{plural}.get_many
Put put_{singular}_request / _response on {component}.v1.{plural}.put
Put many put_many_{plural}_request / _response on {component}.v1.{plural}.put_many
Delete delete_{singular}_request / _response on {component}.v1.{plural}.delete
Delete many delete_many_{plural}_request / _response on {component}.v1.{plural}.delete_many
Scoped list list_by_{relation}_{plural}_request / _response on {component}.v1.{plural}.list_by_{relation}
Versions list_{facet_stem}_versions_request / _response on {component}.v1.{plural}_versions.list
Single version get_{facet_stem}_version_request / _response on {component}.v1.{plural}_versions.get
Event subjects namespace {singular}_event_subjects with created, updated, deleted

The subject grammar is four segments for a request, {component}.v1.{plural}.{verb}, and five for an event, {component}.v1.events.{plural}.{created,updated,deleted}. See NATS entity protocol specification for the grammar itself.

3.4. Shell, HTTP, TypeScript, docs and tests

Artefact Pattern
Shell verbs list, get, get-many, add, set, put-many, delete, delete-many, versions, version, by-{relation}
Shell menu {plural}
Shell recipe script projects/ores.shell/scripts/library/{menu}/{menu}-{command}.ores
Shell recipe doc doc/recipes/shell/{menu}/{singular}.org
HTTP route paths /api/v1/{component}/{plural} with /{{{key}}}, /versions, /versions/{version}, /get-many, /put-many, /delete-many, /by-{relation}/{relation}
HTTP recipe doc doc/recipes/http/{plural}/{singular}.org
TypeScript domain interface export interface {Pascal} in .../generated/{component}/domain/{singular}.ts
TypeScript protocol interfaces export interface {Pascal(message_name)} in .../protocol/{singular}_protocol.ts
TypeScript maps subjects, requiresSession, eventSubjects
Entity model file projects/ores.{component}/modeling/ores.{component}.{entity}.org
CMake source list projects/{component_dir}/src/component_files.cmake
CMake test list projects/{component_dir}/tests/component_files.cmake
Eventing integration test tests/{singular}_eventing_integration_tests.cpp
Shell command test projects/ores.shell/{component}/tests/{singular}_commands_tests.cpp
HTTP route test projects/ores.http/{component}/tests/{singular}_routes_tests.cpp
ER diagram projects/ores.sql/modeling/ores_schema.{component}.puml

Two dead or misleading names are worth knowing. The archetype ores.plantuml.er.diagram emits output/{entity}_er.puml, and no code references it; the live diagrams are the group .puml files above. And there is no generated domain_{entity}_tests.cpp: the file exists for report_instance but is hand-written.

3.5. Junctions

A junction replaces {plural} with the junction's {name}.

Artefact Pattern
Table {product}_{component}_{name}_tbl
Functions and triggers {product}_{component}_{name}_insert_fn, _insert_trg, _delete_rule
Indexes {name}_{column}_idx, {name}_uniq_idx, {name}_tenant_idx
RLS policies {name}_tbl_tenant_isolation_policy, {name}_tbl_party_isolation_policy
Notify {events_channel}_notify_fn, _notify_trg, _notify_close_trg
Messages get_{name_singular}_request and the rest, on {component}.v1.{name}.{verb}
History row struct {name_singular}_history_row

4. Where an entity's name is decided

The generator is the authority, not this page. Each name above comes from one archetype page and its tangled template.

5. Where the code does not agree

Two append-only logs record a fact about an instant and do not take _event.

Entity What it records Name under this page
ores.iam.run_token_issue one run grant exchange, issued or refused run_token_issue_event
ores.dq.publication one dataset publication publication_event

ores.dq.publication also states its table name rather than letting the generator derive it, because the entity is a publication and the table is ores_dq_dataset_publications_tbl. See the capture Ensure all entities follow the entity naming convention for the review this opens.

6. See also

Emacs 29.3 (Org mode 9.6.15)