Domain Type Creator
When to use this skill
When you need to add a new domain type to the ORE Studio project with complete support for JSON I/O, table I/O, test data generation, and database persistence with CRUD operations.
How to use this skill
Recommended approach: Use code generation first. The ores.codegen project can
generate all domain type artefacts from JSON models, ensuring consistency and
reducing boilerplate errors.
Priority order
- Use code generation: Create a JSON model and generate all artefacts using the appropriate profiles. See ORE Studio Codegen for details.
- Update templates: If the domain type doesn't fit existing templates, modify
the Mustache templates in
library/templates/to support the new pattern. - Manual creation: Only create artefacts manually as a last resort when code generation cannot support the required pattern.
Code generation workflow
- Create a JSON model in
projects/ores.codegen/models/{component}/ Generate all C++ artefacts:
cd projects/ores.codegen # Generate all C++ facets (domain, repository, service, protocol, generator) ./run_generator.sh models/{component}/{entity}_schema.json output/ --profile all-cpp
- Review the generated output in
output/ - Copy files to the appropriate
projects/ores.{component}/directories - Update
CMakeLists.txtif needed (files picked up automatically via GLOB) - Build and verify
- Raise PRs at designated checkpoints
Manual workflow (last resort)
- Gather information about the domain type (name, fields, project location).
- Follow the detailed instructions to create all required artefacts in order.
- Build and test the new domain type.
- Raise PRs at designated checkpoints and wait for review before proceeding.
Related skills
- cmake-runner: For building and testing the component.
- plantuml-class-modeler: Update class diagrams for the component.
- feature-branch-manager: Manage branch transitions between phases.
- pr-manager: Create a new Pull Request (PR).
PR strategy
Creating a complete domain type involves significant work across multiple files. To keep PRs reviewable, the implementation is split into phases with designated checkpoints.
| Phase | Steps | Focus | PR Title Template |
|---|---|---|---|
| 1 | 1-3 | Domain class and I/O support | [COMPONENT] Add <Entity> domain type |
| 2 | 4 | Test data generator | [COMPONENT] Add <Entity> generator |
| 3 | 5-6 | Repository and persistence | [COMPONENT] Add <Entity> repository |
| 4 | 7-10 | Tests and documentation | [COMPONENT] Add <Entity> tests and diagrams |
Phase checkpoints
After completing each phase:
- Using cmake-runner skill, build and verify there are no errors.
- Commit all changes with an appropriate message.
- Push the branch and use pr-manager skill to raise a PR with the suggested title.
- Wait for review feedback and address any comments.
- Once approved, merge the PR back to main.
- Use the feature-branch-manager skill to transition to the next phase.
Detailed instructions
The following sections describe the step-by-step process for creating a complete domain type.
Gather requirements
Before starting, gather the following information from the user:
- Domain type name: the name of the new type (e.g.,
currency,account). - Component location: which component the domain type belongs to (e.g.,
ores.refdata,ores.accounts). - Fields: list of fields with their types and descriptions.
Phase 1: Domain class and I/O support
Step 1: Create the domain class definition
Follow the instructions in Domain Type Class Definition Facet.
Step 2: Create JSON I/O support
Follow the instructions in Domain Type JSON I/O Facet.
Step 3: Create table I/O support
Follow the instructions in Domain Type Table I/O Facet.
Phase 1 checkpoint
At this point you have a complete domain type with JSON and table I/O support.
- Build the component library to verify there are no errors.
- Commit with message:
[COMPONENT] Add <Entity> domain type - Push and raise PR:
[COMPONENT] Add <Entity> domain type - Wait for review and merge before continuing.
- Use feature-branch-manager to transition to Phase 2.
Phase 2: Test data generator
Step 4: Create generator support
Follow the instructions in Domain Type Generator Support Facet.
Phase 2 checkpoint
At this point you have a test data generator for the domain type.
- Build the component library to verify there are no errors.
- Commit with message:
[COMPONENT] Add <Entity> generator - Push and raise PR:
[COMPONENT] Add <Entity> generator - Wait for review and merge before continuing.
- Use feature-branch-manager to transition to Phase 3.
Phase 3: Repository and persistence
Step 5: Create repository entity and mapper
Follow the instructions in Domain Type Repository Entity and Mapper Facet.
Step 6: Create repository with CRUD operations
Follow the instructions in Domain Type Repository CRUD Operations Facet.
Phase 3 checkpoint
At this point you have full repository support for the domain type.
- Build the component library to verify there are no errors.
- Commit with message:
[COMPONENT] Add <Entity> repository - Push and raise PR:
[COMPONENT] Add <Entity> repository - Wait for review and merge before continuing.
- Use feature-branch-manager to transition to Phase 4.
Phase 4: Tests and documentation
Step 7: Update CMakeLists.txt
Follow the instructions in Domain Type CMakeLists Update Facet.
Step 8: Create tests
Follow the instructions in Domain Type Test Creation Facet.
Step 9: Update UML diagrams
Update the component's UML diagrams to include the new domain type:
- Use the plantuml-class-modeler skill to update class diagrams showing the new domain type and its relationships.
- If repository support was added, use the plantuml-er-modeler skill to update the database ER diagram with the new table.
- Build the diagrams using the appropriate CMake targets.
Step 9: Build and verify
Using the cmake-runner skill:
- Configure the project (reconfigure to pick up new files).
- Build the component library (
COMPONENT.libtarget). - Build and run the component tests (
test_COMPONENT.teststarget).
Step 10: Update UML diagrams
Update the component's UML diagrams to include the new domain type:
- Use the plantuml-class-modeler skill to update class diagrams showing the new domain type and its relationships.
- Build the diagrams using the appropriate CMake targets.
Phase 4 checkpoint
This is the final phase. At this point you have complete domain type support with tests and documentation.
- Build and run all tests to verify everything works.
- Commit with message:
[COMPONENT] Add <Entity> tests and diagrams - Push and raise PR:
[COMPONENT] Add <Entity> tests and diagrams - Wait for review and merge.
The domain type implementation is now complete.
Using the Code Generator
The code generator can automatically create most domain type artefacts from JSON model definitions. This is the recommended approach for new domain types.
Generator location
projects/ores.codegen/
├── run_generator.sh # Main entry point
├── src/generator.py # Generator implementation
├── library/templates/ # Mustache templates
└── models/ # Model definitions
└── dq/
├── dataset_bundle_domain_entity.json
└── dataset_bundle_member_junction.json
Model types
Domain Entity models (*_domain_entity.json)
For types with UUID primary key and natural key constraints.
Junction models (*_junction.json)
For association tables with composite text primary keys.
Available templates
| Template | Output | Purpose |
|---|---|---|
cpp_domain_type_class.hpp |
domain/{entity}.hpp |
Domain class definition |
cpp_domain_type_json_io.hpp |
io/{entity}_json_io.hpp |
JSON serialization header |
cpp_domain_type_json_io.cpp |
io/{entity}_json_io.cpp |
JSON serialization impl |
cpp_domain_type_table.hpp |
io/{entity}_table.hpp |
Table I/O header |
cpp_domain_type_table.cpp |
io/{entity}_table.cpp |
Table I/O impl |
cpp_domain_type_table_io.hpp |
io/{entity}_table_io.hpp |
Table model header |
cpp_domain_type_table_io.cpp |
io/{entity}_table_io.cpp |
Table model impl |
cpp_domain_type_generator.hpp |
generator/{entity}_generator.hpp |
Test data generator header |
cpp_domain_type_generator.cpp |
generator/{entity}_generator.cpp |
Test data generator impl |
cpp_domain_type_entity.hpp |
repository/{entity}_entity.hpp |
Repository entity header |
cpp_domain_type_entity.cpp |
repository/{entity}_entity.cpp |
Repository entity impl |
cpp_domain_type_mapper.hpp |
repository/{entity}_mapper.hpp |
Entity/domain mapper header |
cpp_domain_type_mapper.cpp |
repository/{entity}_mapper.cpp |
Entity/domain mapper impl |
cpp_domain_type_repository.hpp |
repository/{entity}_repository.hpp |
Repository CRUD header |
cpp_domain_type_repository.cpp |
repository/{entity}_repository.cpp |
Repository CRUD impl |
cpp_service.hpp |
service/{entity}_service.hpp |
Service layer header |
cpp_service.cpp |
service/{entity}_service.cpp |
Service layer impl |
cpp_protocol.hpp |
messaging/{entity}_protocol.hpp |
Binary protocol header |
cpp_protocol.cpp |
messaging/{entity}_protocol.cpp |
Binary protocol impl |
sql_schema_domain_entity_create |
sql/{component}_{entity}_create.sql |
SQL table schema |
sql_schema_junction_create |
sql/{component}_{entity}_create.sql |
SQL junction schema |
Running the generator
cd projects/ores.codegen # Generate all C++ files for a domain entity ./run_generator.sh models/dq/dataset_bundle_domain_entity.json output/ # Generate specific template only ./run_generator.sh models/dq/dataset_bundle_domain_entity.json output/ \ --template cpp_domain_type_class.hpp.mustache # Generate service and protocol (commonly needed for messaging) ./run_generator.sh models/dq/dataset_bundle_domain_entity.json output/ \ --template cpp_service.hpp.mustache ./run_generator.sh models/dq/dataset_bundle_domain_entity.json output/ \ --template cpp_protocol.hpp.mustache
Workflow with code generation
- Create a JSON model in
projects/ores.codegen/models/{component}/ - Run the generator to produce C++ files
- Review the output in
output/ - Copy files to the appropriate
projects/ores.{component}/directories - Update
CMakeLists.txtif needed (files picked up automatically via GLOB) - Build and verify
Model structure
Example domain entity model with full C++ support:
{
"domain_entity": {
"component": "dq",
"entity_singular": "dataset_bundle",
"entity_plural": "dataset_bundles",
"entity_title": "Dataset Bundle",
"brief": "A named collection of datasets.",
"description": "Detailed multi-line description...",
"primary_key": {
"column": "id",
"type": "uuid",
"cpp_type": "boost::uuids::uuid",
"description": "UUID uniquely identifying this bundle."
},
"natural_keys": [
{
"column": "code",
"type": "text",
"cpp_type": "std::string",
"description": "Unique code for stable referencing.",
"generator_expr": "std::string(faker::word::noun()) + \"_bundle\""
}
],
"columns": [
{
"name": "description",
"type": "text",
"cpp_type": "std::string",
"nullable": false,
"description": "Detailed description of the bundle.",
"generator_expr": "std::string(faker::lorem::sentence())"
}
],
"sql": {"tablename": "dq_dataset_bundles_tbl"},
"repository": {
"entity_singular_short": "bundle",
"entity_plural_short": "bundles"
},
"cpp": {
"includes": {
"domain": ["<chrono>", "<string>", "<boost/uuid/uuid.hpp>"],
"entity": ["<string>", "\"sqlgen/Timestamp.hpp\""]
},
"iterator_var": "b"
}
}
}
Benefits of code generation
- Consistency across all domain types
- Reduces boilerplate errors
- Single source of truth for entity definitions
- Easy to update all instances when patterns change
- Supports both domain entity and junction table patterns
Common patterns and conventions
Naming conventions
- Use snake_case for all C++ identifiers (classes, methods, variables)
- File names should match the class name exactly
- Headers use
.hppextension - Implementation files use
.cppextension
Code organization
- Group related functionality in logically named files
- Standard library includes first, then third-party, then project headers
- Use namespace aliases to reduce verbosity
Documentation
- Add doxygen comments for all public APIs
- Explain the "why" in comments, not the "what"
- Include
@brieftags for all documented items
Service layer patterns
When creating a service class to expose repository operations, follow these patterns:
Use save_* for upsert semantics
The repository write() method has upsert semantics - it creates a new record
or updates an existing one. The service layer should expose this same behavior
through save_* methods:
// CORRECT: Single save_* method with upsert semantics void currency_service::save_currency(const domain::currency& currency) { currency_repo_.write(currency); }
DO NOT create separate create_* and update_* methods at the service level:
// WRONG: Separate create/update methods duplicate repository semantics void currency_service::create_currency(const domain::currency& c); // Don't do this void currency_service::update_currency(const domain::currency& c); // Don't do this
Service method naming convention
| Operation | Method Name Pattern | Notes |
|---|---|---|
| Create or update | save_<entity> |
Upsert via repository write() |
| Delete | remove_<entity> |
Soft delete via repository |
| Find by key | find_<entity> |
Returns std::optional |
| List all | list_<entities> |
Returns vector |
| List with filter | list_<entities>_by_<key> |
Filtered list |
| List since time | list_<entities>_since |
Incremental loading support |
| Get history | get_<entity>_history |
All versions of an entity |
Reference implementation
See projects/ores.refdata/src/service/currency_service.cpp for a correct
implementation of the service layer pattern.