Component architecture

Table of Contents

This is the ground-truth reference for ORE Studio's component directory layout. All tooling (codegen output paths, compass scaffolds, CI) must agree with this document. If a discrepancy is found, fix the tooling — not this document.

Return to Knowledge.

1. Two component meta-models

ORE Studio uses exactly two component layouts. Choose based on whether the component needs an API/implementation split.

1.1. Simple component

A single CMakeLists.txt at the root; include/, src/, tests/, and modeling/ directly inside. No sub-component split. Named ores.<name>. When writing about architecture, say simple component rather than "component without sub-components".

Use when: the component is an infrastructure library, a tool, or a shared utility with no need to separate public API from DB/service concerns.

projects/ores.database/
├── CMakeLists.txt
├── include/ores.database/
├── src/
├── tests/
└── modeling/

Current simple components:

Component Role
ores.database Database access layer (sqlgen, migrations)
ores.diff Structural diff utilities
ores.geo Geographic utilities (IP-to-country, etc.)
ores.logging Boost.Log setup and logger factory
ores.nats NATS client wrapper
ores.platform Platform abstraction (reflect-cpp, JSON glue)
ores.security Authentication and authorisation primitives
ores.service Shared service-layer helpers (handler_helpers, request_context)
ores.storage Object storage client
ores.testing Per-test tenant isolation and Catch2 fixtures
ores.utility General-purpose utilities (UUID, time, string)

1.2. Composite component

A parent directory projects/ores.<group>/ contains child sub-component directories (short names: api, core, service, …). Each child is an independent CMake target. When writing about architecture, say composite component rather than "component with sub-components" or "component group".

The parent holds a CMakeLists.txt whose whole job is to add its parts, and projects/CMakeLists.txt adds the component rather than each part, so the parts of a component are listed in exactly one place. This reverses decision D5 (sprint 19, PR #997), which had the parent hold no CMakeLists.txt and every part registered at the projects level.

Every composite now follows this shape, and codegen generates the file: the ores.cmake.component.composite_root archetype renders the parts a composite declares in #+parts:. The order is declared rather than derived, because dependency order is not alphabetical — ores.shell needs api ahead of the parts that link it, and ores.eventing lists modeling among its parts because its modeling/ carries a diagram target.

Sub-component names follow the conventions in 2. Child CMake targets keep the fully-qualified name: ores.<group>.api, ores.<group>.core, etc.

projects/ores.refdata/
├── api/
│   ├── CMakeLists.txt
│   ├── include/ores.refdata.api/
│   ├── src/
│   ├── tests/
│   └── modeling/
├── core/
│   ├── CMakeLists.txt
│   ├── include/ores.refdata.core/
│   ├── src/
│   ├── tests/
│   └── modeling/
├── service/
│   └── …
└── modeling/          ← group-level codegen entity models

Use when: the component owns domain types used by other components (api), DB repositories (core), and/or a runnable message-handler host (service).

Current composite components and their active sub-components:

Group Sub-components
ores.analytics api, core, quant, service, modeling
ores.assets api, core, service
ores.compute api, client, core, service, wrapper, modeling
ores.controller api, core, service, modeling
ores.dq api, core, service, modeling
ores.eventing api, core, modeling
ores.http api, core, server
ores.iam api, client, core, service, modeling
ores.marketdata api, core, service
ores.ore api, core, service
ores.refdata api, client, core, service, modeling
ores.reporting api, core, service, modeling
ores.scheduler api, core, service, modeling
ores.shell api, application, iam, refdata, synthetic, trading, modeling
ores.synthetic api, core, service, modeling
ores.telemetry core, service, database
ores.trading api, core, service, modeling
ores.variability api, core, service
ores.workflow api, core, service, modeling
ores.workspace api, core, service, modeling

⚠ Stale top-level directories (decommissioned in sprint 19, not yet deleted): projects/ores.refdata.api/, projects/ores.refdata.core/, projects/ores.trading.api/, projects/ores.trading.core/. These receive wrong codegen output and must be ignored. See 6.

2. Sub-component catalogue

Exhaustive list of every sub-component name in use and its canonical role.

2.1. Naming and scope

A sub-component name is scoped to its parent component. Two components can therefore use the same sub-component name, and a sub-component can carry the name of a component.

The adapter component ores.shell uses this rule. Each part of an adapter projects one domain area, and the part takes the name of the domain component it projects: ores.shell.trading takes its name from ores.trading. The rule covers every domain area, refdata, compute and workflow included.

Read the full name to identify a part. ores.shell.trading is a part of ores.shell. ores.trading is a component in its own right. This is the fractal case: the same name appears at two levels of the tree, and the full name resolves which one is meant.

The command facet follows the same rule. A generated command unit for a trading entity writes into ores.shell.trading, not into ores.shell directly, so only the adapter that owns the domain links the domain's protocol types.

2.2. Standard roles (most groups use these three)

Name Full name Role
api ores.<group>.api Public domain types (POCOs), JSON I/O, table I/O, generators, NATS protocol structs, eventing event types. No DB or service deps. Consumed by all other sub-components and by external consumers.
core ores.<group>.core Repositories (sqlgen entities, mappers, CRUD), business-logic service layer, DB access. Depends on api. Not runnable.
service ores.<group>.service Runnable host: NATS message handlers, startup wiring, main entry point. Depends on core.

2.3. Specialist roles

Name Used in Role
server ores.http HTTP/web server entrypoint — wires HTTP routes and runs the Boost.Asio event loop. Equivalent to service but for HTTP rather than NATS.
client ores.iam, ores.marketdata, ores.refdata Client-side library — typed, DB-free NATS request facades and (for ores.iam) session/authentication helpers, for UI and service consumers. Provides the consumer-facing API without coupling consumers to core internals; also the home for consumer-side entity-mirror caches (nats-event-cache facet, cached_by) shared by multiple UI-layer consumers — see Where should an entity-mirror cache live?.
wrapper ores.compute External process wrapper/bridge — executes ORE risk runs as a child process and reports results back to core.
database ores.telemetry Database persistence layer — owns the DB schema and repository for the group when the persistence concern is large enough to separate from core.
quant ores.analytics Dependency-light quantitative math library — CRM spanning-tree topology, the derived-rate engine and stochastic price processes. It carries no database or NATS dependency and is consumed for its mathematics alone, by ores.refdata.core, the ores.synthetic api and service, and ores.marketdata.service. No part of ores.analytics links it today, so its placement here records where the mathematics belongs by subject rather than an existing dependency.

2.4. Application-layer roles (ores.shell)

ores.shell is the remaining Application-layer composite: its children project domain areas, so the sub-component names here are domain-area names, not the standard roles. The removed desktop client followed the same scheme, with one plugin per domain area.

Name Role
application The runnable host of an adapter component: the process entry point, config parser, REPL loop and script runner, and it builds ores.shell.exe.
api Shared infrastructure of an adapter component, used across all of its parts: the argument parsing, feedback, pagination and request plumbing.
trading Trading module (ores.shell.trading), one domain area projected into the shell.

2.5. Modeling directory (not a CMake target)

Name Role
modeling Codegen entity org models and component documentation (component_overview.org, PlantUML diagrams). Not a compilable target; used only by codegen and documentation tools. May appear at the group level (projects/ores.refdata/modeling/) or as a child of a simple component or sub-component.

3. Facet placement

Code is organised into facets within include/ and src/. Each facet belongs in exactly one sub-component:

Facet api core service Description
domain ✓     Domain types (POCOs), JSON I/O, table I/O
generators ✓     Test-data generators
messaging ✓ ✓   NATS protocol structs in api; handler class in core
eventing ✓     Event types
repository   ✓   sqlgen entities, mappers, repositories
service   ✓   Business-logic service layer
presentation   ✓   Drawers and renderers the UI reads; the entity's table display
log   ✓   Parsers for a log format the component owns
net ✓     Wire-format helpers for an external endpoint
workflow ✓     Workflow definitions the component registers
app     ✓ Executable hosting and startup
config     ✓ Argument parsing and configuration structs

A one-segment component (ores.platform, ores.storage, ores.diff) has no part split, so its facets sit directly under include/ores.<component>/.

3.1. Local facets

The table is the shared floor, not a closed list. A component may add a facet when it holds code that no shared facet covers, and the name states what the code does rather than which library it wraps. ores.ore.core is the worked example: xml for the importer, exporter and round trip; market for the ORE market data readers and the series key registry; scanner for the directory walk; planner for the import plan and its choices; and hierarchy for the portfolio and book node builder. Each name says what the facet holds, and five facets in one part is the signal that the part is doing five jobs.

4. CMake dependency chain

Typical target_link_libraries per part (generated by scaffold profiles; adjust to actual deps after generation):

Part PUBLIC PRIVATE
api ores.eventing.lib, ores.platform.lib ores.utility.lib, faker-cxx, libfort
core ores.<group>.api.lib, ores.nats.lib ores.service.lib, ores.platform.lib, ores.utility.lib, ores.security.lib, faker-cxx, libfort
service ores.service.lib, ores.security.lib, ores.<group>.core.lib, ores.eventing.lib, ores.nats.lib, ores.database.lib, ores.utility.lib, ores.telemetry.lib —

CMake visibility rules:

  • PUBLIC: headers exposed in the component's own public API; consumers also see (and link) the dependency.
  • PRIVATE: used only in .cpp files; consumers do not inherit it.
  • INTERFACE: expose to consumers without linking directly (avoids duplicate-library warnings on macOS).

Common transitive deps — do not add explicitly:

  • reflectcpp arrives transitively via ores.platform.lib (PUBLIC)
  • Boost::program_options arrives via ores.telemetry.lib (PUBLIC)

5. Scaffold kinds

Codegen (--address ores.cpp, plus --address ores.cmake.component for the CMake files) generates all boilerplate for each part; the overview's #+component_kind: frontmatter picks the variant. A component that has no models yet opts into ores.cpp.scaffold for a placeholder domain type, its test and the umbrella header; that facet is #+default: disabled, so a component that has real code never has them recreated. See ORE Studio Codegen for the template list.

#+component_kind: Part Files generated
api api CMakeLists (root, src, tests, modeling), the two component file lists, export.hpp and the test main; with the ores.cpp.scaffold opt-in, the component header, the stub header+impl and the stub test
core core the same set with core-flavoured CMake deps
service service the core set plus app/config headers+impls and main.cpp; with the opt-in, the component header and the stub test
flat (default) standalone the api set with generic CMake deps
composite composite root the root CMakeLists that adds the declared parts. The root holds no code of its own.
adapter adapter part the two file lists only, src/ and tests/component_files.cmake. The part carries hand-authored CMakeLists files.

After generation, register each part in projects/CMakeLists.txt in dependency order (api → core → service). Each part also needs a .puml diagram stub in modeling/ — not yet generated by codegen, so add it manually.

An adapter part projects a domain into a presentation surface, as ores.shell.trading does. Its link libraries and its target shape belong to that surface, so the part keeps hand-authored build files and no build-file archetype serves the adapter kind. An undeclared kind selects flat instead, and the flat archetypes overwrite the hand-authored CMakeLists files with a generic scaffold. Declare the kind on every part of a presentation composite.

6. Known issues

Issue Status
Stale top-level directories ores.refdata.api/, ores.refdata.core/, ores.trading.api/, ores.trading.core/ — decommissioned in sprint 19 but not yet deleted. Codegen currently writes to these stale paths instead of ores.refdata/api/ etc. Open — tracked in Refactor ores.codegen C++ generation
Facet catalogue uses projects/ores.{component_include}/ and projects/ores.{component_core}/ as output roots. Should be projects/ores.{component}/api/ and projects/ores.{component}/core/. Open — same story

7. See also

Emacs 29.3 (Org mode 9.6.15)