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.cppfiles; 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:
reflectcpparrives transitively viaores.platform.lib(PUBLIC)Boost::program_optionsarrives viaores.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
- ORE Studio Codegen — profile catalogue and codegen usage.
- component-creator — skill for scaffolding a new component.
- component-model-creator — skill for writing the architecture docs.
- Component Documentation Guide — how to fill in each section of
component_overview.org. - Regroup C++ components — sprint 19 story that established the grouped layout.